diff --git a/c-sync.cc b/c-sync.cc index 26cc2b4..bbd255e 100644 --- a/c-sync.cc +++ b/c-sync.cc @@ -254,6 +254,40 @@ float T_SyncCurve::computeValue( return it->computeValue( timeUnits - prevSegStart ); } +E_SyncCurveMatch T_SyncCurve::matches( + T_SyncCurve const& other ) const noexcept +{ + const auto tSegs{ this->segments.size( ) }; + const auto oSegs{ other.segments.size( ) }; + const auto nSegs{ std::min( tSegs , oSegs ) }; + for ( auto i = 0u ; i < nSegs ; i ++ ) { + auto const& tDur{ this->segments[ i ].durations }; + auto const& oDur{ other.segments[ i ].durations }; + const bool lastSeg{ i == nSegs - 1 }; + if ( !lastSeg && tDur.size( ) != oDur.size( ) ) { + return E_SyncCurveMatch::MISMATCH; + } + + const auto nd{ std::min( tDur.size( ) , oDur.size( ) ) }; + for ( auto j = 0u ; j < nd ; j ++ ) { + if ( tDur[ j ] != oDur[ j ] ) { + return E_SyncCurveMatch::MISMATCH; + } + } + if ( tDur.size( ) != oDur.size( ) ) { + return tDur.size( ) < oDur.size( ) + ? E_SyncCurveMatch::LASTSEG_SHORT + : E_SyncCurveMatch::LASTSEG_LONG; + } + } + if ( tSegs == oSegs ) { + return E_SyncCurveMatch::IDENTICAL; + } + return tSegs < oSegs + ? E_SyncCurveMatch::MATCHING_SHORT + : E_SyncCurveMatch::MATCHING_LONG; +} + /*= T_SyncCurves =============================================================*/ diff --git a/c-sync.hh b/c-sync.hh index b2b8b15..62487be 100644 --- a/c-sync.hh +++ b/c-sync.hh @@ -51,6 +51,34 @@ struct T_SyncSegment }; M_LSHIFT_OP( T_StringBuilder , T_SyncSegment::E_SegmentType ); +/*----------------------------------------------------------------------------*/ + +// Possible results when trying to check whether two curves have a similar +// structure. +enum class E_SyncCurveMatch +{ + // Both curves have the same structure. + IDENTICAL , + + // One of the curves has less information than the other, but the + // information in the shortest curve matches the corresponding + // information in the longest curve (i.e. it is only missing + // segments). _SHORT/_LONG indicate that the curve for which the + // comparison method was called is the shortest/longest (resp.) + MATCHING_SHORT , + MATCHING_LONG , + + // One of the curves has less information than the other, and its + // last segment is shorter than the corresponding segment in the + // longest record (but its end matches a point in the longest curve's + // segment). + LASTSEG_SHORT , + LASTSEG_LONG , + + // The curves do not match. + MISMATCH +}; + // An input curve struct T_SyncCurve { @@ -69,8 +97,14 @@ struct T_SyncCurve // Compute the value at the specified time. float computeValue( float timeUnits ) const noexcept; + + // Compare this curve to another. + E_SyncCurveMatch matches( + T_SyncCurve const& other ) const noexcept; }; +/*----------------------------------------------------------------------------*/ + // All configured curves. Some may not actually correspond to an input and may // have been defined for inputs that have been removed temporarily (e.g. // because some include was commented out), in which case we don't want to diff --git a/ui-sequencer.cc b/ui-sequencer.cc index 7e8cd16..907d4e6 100644 --- a/ui-sequencer.cc +++ b/ui-sequencer.cc @@ -259,6 +259,7 @@ struct T_SyncViewImpl_ void displayInputSelector( ) noexcept; void displayOverrideSelector( ) noexcept; + // Selection display/edition windows void displayTrackWindow( ) noexcept; void displaySegmentWindow( ) noexcept; void displayPointWindow( ) noexcept; @@ -1321,6 +1322,24 @@ void T_SyncViewImpl_::displayOverrideSelector( ) noexcept { using namespace ImGui; + /* + * An override can be selected directly if the inputs that are part of + * it are consistent. + * + * A pair of inputs are consistent if + * - one or both inputs do not have curve information attached; + * - the durations and segment boundaries of the shortest curve + * attached to the inputs match the boundaries and durations from the + * other input's curve OR the last segment is shorter but its end + * matches the location of one of the points in the corresponding + * segment of the other curve. + * + * If the inputs are not consistent, it will be necessary to reset + * some of their curves; best option would be to try and be clever, but + * a temporary measure (resetting all curves to match the structure of + * the first longest curve) would work. + */ + BeginChild( "content" ); Common::Sync( ).visitOverrides( [&]( T_SyncOverrideVisitor::T_Element element , const bool exit ) { if ( element.hasType< T_SyncOverrideSection* >( ) ) { @@ -1330,47 +1349,79 @@ void T_SyncViewImpl_::displayOverrideSelector( ) noexcept } if ( exit ) { TreePop( ); - } else { - return TreeNodeEx( &sos.cTitle[ 0 ] , - ImGuiTreeNodeFlags_DefaultOpen ); + return true; } - } else if ( exit ) { - auto const& ov{ *element.value< A_SyncOverride* >( ) }; - auto const& id{ ov.id( ) }; - auto const& in{ ov.inputNames( ) }; - const bool present{ sOverrides.contains( id ) }; - const bool hasCurves{ !present && [&](){ - for ( auto i = 0u ; i < in.size( ) ; i ++ ) { - if ( sCurves.contains( in[ i ] ) ) { - return true; - } - } - return false; - }() }; + return TreeNodeEx( &sos.cTitle[ 0 ] , + ImGuiTreeNodeFlags_DefaultOpen ); + } + if ( ! exit ) { + return false; + } - if ( hasCurves ) { - PushDisabled( ); - } - - bool select{ present }; - if ( Checkbox( ov.title( ) , &select ) ) { - if ( select ) { - sOverrides.add( id ); - for ( auto i = 0u ; i < in.size( ) ; i ++ ) { - sCurves.add( in[ i ] , true ); - } - } else { - sOverrides.remove( id ); - for ( auto i = 0u ; i < in.size( ) ; i ++ ) { - sCurves.remove( in[ i ] ); - } + auto const& ov{ *element.value< A_SyncOverride* >( ) }; + auto const& id{ ov.id( ) }; + auto const& in{ ov.inputNames( ) }; + const bool present{ sOverrides.contains( id ) }; + const bool hasCurves{ !present && [&](){ + for ( auto i = 0u ; i < in.size( ) ; i ++ ) { + if ( sCurves.contains( in[ i ] ) ) { + return true; } } + return false; + }() }; - if ( hasCurves ) { - PopDisabled( ); + // FIXME MOVE THIS \/ // + bool bad{ false }; + for ( auto i = 0u ; i < in.size( ) - 1 && !bad ; i ++ ) { + T_SyncCurve const* const c0{ + Common::Sync( ).getCurve( in[ i ] ) + }; + if ( !c0 ) { + continue; + } + for ( auto j = i + 1 ; j < in.size( ) && !bad ; j ++ ) { + T_SyncCurve const* const c1{ + Common::Sync( ).getCurve( in[ j ] ) + }; + if ( !c1 ) { + continue; + } + const auto res{ c0->matches( *c1 ) }; + bad = ( res == E_SyncCurveMatch::MISMATCH ); } } + // FIXME MOVE THIS /\ // + + if ( hasCurves ) { + PushDisabled( ); + } + if ( bad ) { + PushStyleColor( ImGuiCol_Text , 0xff0000ff ); + } + + bool select{ present }; + if ( Checkbox( ov.title( ) , &select ) ) { + if ( select ) { + sOverrides.add( id ); + for ( auto i = 0u ; i < in.size( ) ; i ++ ) { + sCurves.add( in[ i ] , true ); + } + } else { + sOverrides.remove( id ); + for ( auto i = 0u ; i < in.size( ) ; i ++ ) { + sCurves.remove( in[ i ] ); + } + } + } + + if ( bad ) { + PopStyleColor( ); + } + if ( hasCurves ) { + PopDisabled( ); + } + return true; } ); EndChild( );