diff --git a/c-sync.hh b/c-sync.hh index dadf700..bee7d38 100644 --- a/c-sync.hh +++ b/c-sync.hh @@ -315,6 +315,8 @@ struct T_SyncManager : public virtual A_ProjectPathListener bool addInput( T_String const& name , const float initial = 0.f ) noexcept { return values_.addValue( name , initial ); } + bool hasInput( T_String const& name ) const noexcept + { return values_.identifiers.contains( name ); } T_Array< T_String > const& inputNames( ) const noexcept { return values_.identifiers; } diff --git a/c-syncedit.cc b/c-syncedit.cc index abb5301..89797ab 100644 --- a/c-syncedit.cc +++ b/c-syncedit.cc @@ -172,6 +172,94 @@ void SyncEditor::SetDuration( /*----------------------------------------------------------------------------*/ +void SyncEditor::DeleteCurve( + T_String const& id ) noexcept +{ + auto const* const curve{ Common::Sync( ).getCurve( id ) }; + if ( !curve ) { + return; + } + + // Create undo entry + auto& undo{ dynamic_cast< T_UndoSyncChanges& >( + Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; + undo.curveDeletion( *curve ); + + // Delete curve + Common::Sync( ).removeCurve( id ); +} + +/*----------------------------------------------------------------------------*/ + +void SyncEditor::AppendSegment( + T_String const& id , + uint32_t nsDuration ) noexcept +{ + assert( nsDuration > 0 ); + + auto& sync{ Common::Sync( ) }; + auto const* const curve{ sync.getCurve( id ) }; + auto& undo{ dynamic_cast< T_UndoSyncChanges& >( + Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; + + if ( curve && !curve->segments.empty( ) ) { + const float lastValue{ [&](){ + auto const& lSeg{ curve->segments.last( ) }; + return lSeg.values.last( ); + }( ) }; + auto nCurve{ *curve }; + auto& nSeg{ nCurve.segments.addNew( ) }; + nSeg.type = T_SyncSegment::LINEAR; + nSeg.values.add( lastValue ); + nSeg.values.add( lastValue ); + nSeg.durations.add( nsDuration ); + + undo.curveReplacement( *curve , nCurve ); + sync.setCurve( std::move( nCurve ) ); + } else { + T_SyncCurve nCurve; + nCurve.name = id; + + const auto value{ sync.inputs( )[ sync.inputPos( id ) ] }; + auto& nSeg{ nCurve.segments.addNew( ) }; + nSeg.type = T_SyncSegment::LINEAR; + nSeg.values.add( value ); + nSeg.values.add( value ); + nSeg.durations.add( nsDuration ); + + undo.curveCreation( nCurve ); + sync.setCurve( std::move( nCurve ) ); + } +} + +/*----------------------------------------------------------------------------*/ + +void SyncEditor::DeleteSegment( + T_String const& id , + const uint32_t segmentIndex ) noexcept +{ + auto& sync{ Common::Sync( ) }; + + auto const* const curve{ sync.getCurve( id ) }; + if ( !curve || segmentIndex >= curve->segments.size( ) ) { + return; + } + + auto& undo{ dynamic_cast< T_UndoSyncChanges& >( + Common::Undo( ).add< T_UndoSyncChanges >( ) ) }; + if ( curve->segments.size( ) == 1 ) { + undo.curveDeletion( *curve ); + sync.removeCurve( id ); + } else { + auto nCurve{ *curve }; + nCurve.segments.remove( segmentIndex ); + undo.curveReplacement( *curve , nCurve ); + sync.setCurve( std::move( nCurve ) ); + } +} + +/*----------------------------------------------------------------------------*/ + void SyncEditor::SetSegmentType( T_SyncCurve const& initial , const uint32_t segmentIndex , diff --git a/c-syncedit.hh b/c-syncedit.hh index 867d541..ead9da8 100644 --- a/c-syncedit.hh +++ b/c-syncedit.hh @@ -90,6 +90,25 @@ struct SyncEditor final float uSize , bool scaleCurves ) noexcept; + //---------------------------------------------------------------------- + + // Delete a curve's record completely. + static void DeleteCurve( + T_String const& id ) noexcept; + + //---------------------------------------------------------------------- + + // Append a segment with the specified amount of units at the end of + // the curve. If the curve does not exist it will be created. + static void AppendSegment( + T_String const& id , + uint32_t nsDuration ) noexcept; + + // Delete a segment from a curve. + static void DeleteSegment( + T_String const& id , + uint32_t segmentIndex ) noexcept; + // Change the type of a segment in a curve. static void SetSegmentType( T_SyncCurve const& initial , diff --git a/ui-sequencer.cc b/ui-sequencer.cc index 486ad84..c80fe5c 100644 --- a/ui-sequencer.cc +++ b/ui-sequencer.cc @@ -205,6 +205,7 @@ struct T_SyncViewImpl_ SW_NONE , SW_INPUT_SELECTOR , SW_OVERRIDE_SELECTOR , + SW_TRACK , SW_SEGMENT , }; @@ -215,8 +216,8 @@ struct T_SyncViewImpl_ //---------------------------------------------------------------------- // Sequencer widget methods - void computeMetrics( float innerWidth ) noexcept; void sequencerWidget( ) noexcept; + void computeMetrics( float innerWidth ) noexcept; void sequencerHeader( ImRect const& bb ) noexcept; void sequencerBody( ImRect const& bb ) noexcept; void sequencerTracks( @@ -242,6 +243,7 @@ struct T_SyncViewImpl_ void displayInputSelector( ) noexcept; void displayOverrideSelector( ) noexcept; + void displayTrackWindow( ) noexcept; void displaySegmentWindow( ) noexcept; //---------------------------------------------------------------------- @@ -285,7 +287,7 @@ struct T_SyncViewImpl_ // Selected item T_String selId{ }; bool selIsOverride; - uint32_t selSegment; + T_Optional< uint32_t > selSegment; T_Optional< uint32_t > selPoint; // Sub-windows @@ -337,6 +339,10 @@ bool T_SyncViewImpl_::display( ) noexcept displayTrackSelectorWindow( ); break; + case SW_TRACK: + displayTrackWindow( ); + break; + case SW_SEGMENT: displaySegmentWindow( ); break; @@ -548,6 +554,15 @@ void T_SyncViewImpl_::sequencerWidget( ) noexcept curZoomPixel = mPixels; } + } else if ( mp.type == E_MousePosType::TRACK ) { + auto const& dTrack{ dspTracks[ mp.index ] }; + if ( io.MouseDown[ 0 ] || io.MouseDown[ 1 ] ) { + selId = dTrack.id; + selIsOverride = dTrack.isOverride; + selSegment = {}; + selPoint = {}; + sub = E_SubWindow::SW_TRACK; + } } else if ( mp.type == E_MousePosType::SEGMENT ) { auto const& dSeg{ dspSegments[ mp.index ] }; auto const& dTrack{ dspTracks[ dSeg.track ] }; @@ -1135,6 +1150,102 @@ void T_SyncViewImpl_::displayOverrideSelector( ) noexcept /*----------------------------------------------------------------------------*/ +void T_SyncViewImpl_::displayTrackWindow( ) noexcept +{ + using namespace ImGui; + auto const& dspSize( GetIO( ).DisplaySize ); + + // Window set-up + SetNextWindowSize( ImVec2( dspSize.x * .25f , dspSize.y * .66f - 20 ) , ImGuiSetCond_Appearing ); + SetNextWindowPos( ImVec2( dspSize.x * .75f , 20 ) , ImGuiSetCond_Appearing ); + bool displayed{ true }; + Begin( "Selected track" , &displayed , ImGuiWindowFlags_NoCollapse ); + if ( !displayed ) { + End( ); + sub = SW_NONE; + return; + } + + // Get the curve + auto& sync{ Common::Sync( ) }; + auto const* const curve{ sync.getCurve( selId ) }; + + Text( "Curve:" ); + SameLine( 110 ); + Text( "%s" , selId.toOSString( ).data( ) ); + if ( !sync.hasInput( selId ) ) { + Text( " " ); + SameLine( 110 ); + Text( "No matching input" ); + } + + Text( "Segments:" ); + SameLine( 110 ); + Text( "%d" , curve ? curve->segments.size( ) : 0 ); + Separator( ); + + // Compute total duration + const uint32_t duration{ [&](){ + uint32_t t{ 0 }; + if ( !curve ) { + return 0u; + } + for ( auto const& s : curve->segments ) { + for ( auto const& d : s.durations ) { + t += d; + } + } + return t; + }() }; + const float tDuration{ duration * sync.durationUnitSize( ) }; + + Text( "Duration:" ); + SameLine( 110 ); + Text( "%d units" , duration ); + + Text( " " ); + SameLine( 110 ); + Text( "%.3f seconds" , tDuration ); + + const float dDuration{ sync.duration( ) }; + if ( tDuration == 0.f ) { + Text( " " ); + SameLine( 110 ); + Text( "Empty track" ); + } else if ( tDuration == dDuration ) { + Text( " " ); + SameLine( 110 ); + Text( "Covers the whole demo" ); + } else if ( tDuration > dDuration ) { + Text( " " ); + SameLine( 110 ); + Text( "Longer than the demo" ); + } + + if ( ( curve && !curve->segments.empty( ) ) || tDuration < dDuration ) { + Separator( ); + } + + if ( curve && !curve->segments.empty( ) ) { + Text( " " ); + SameLine( 110 ); + if ( Button( "Clear" , ImVec2{ -1 , 0 } ) ) { + SyncEditor::DeleteCurve( selId ); + } + } + if ( tDuration < dDuration ) { + Text( " " ); + SameLine( 110 ); + if ( Button( "Append segment" , ImVec2{ -1 , 0 } ) ) { + const uint32_t ns{ std::max( 1u , + ( sync.durationUnits( ) - duration ) / 2 ) }; + SyncEditor::AppendSegment( selId , ns ); + } + } + + End( ); +} + void T_SyncViewImpl_::displaySegmentWindow( ) noexcept { using namespace ImGui; @@ -1151,25 +1262,28 @@ void T_SyncViewImpl_::displaySegmentWindow( ) noexcept return; } + const uint32_t sid{ *selSegment }; + Text( "Curve:" ); SameLine( 110 ); Text( "%s" , selId.toOSString( ).data( ) ); Text( "Index:" ); SameLine( 110 ); - Text( "%d" , selSegment ); + Text( "%d" , sid ); Separator( ); + // Access curve and segment auto& sync{ Common::Sync( ) }; - auto* const curve{ sync.getCurve( selId ) }; - auto& segment{ curve->segments[ selSegment ] }; + auto const* const curve{ sync.getCurve( selId ) }; + auto const& segment{ curve->segments[ sid ] }; // Find start and duration uint32_t start{ 0 } , duration{ 0 }; - for ( auto i = 0u ; i <= selSegment ; i ++ ) { + for ( auto i = 0u ; i <= sid ; i ++ ) { auto const& s{ curve->segments[ i ] }; - auto& tgt{ i == selSegment ? duration : start }; + auto& tgt{ i == sid ? duration : start }; for ( auto d : s.durations ) { tgt += d; } @@ -1240,8 +1354,21 @@ void T_SyncViewImpl_::displaySegmentWindow( ) noexcept SameLine( 110 ); Text( "%f" , sMax ); + Separator( ); + + Text( " " ); + SameLine( 110 ); + if ( Button( "Delete segment" , ImVec2{ -1 , 0 } ) ) { + if ( curve->segments.size( ) > 1 ) { + selSegment = ( sid == 0 ? 0 : ( sid - 1 ) ); + } else { + selSegment = {}; + } + SyncEditor::DeleteSegment( selId , sid ); + } + if ( change ) { - SyncEditor::SetSegmentType( *curve , selSegment , + SyncEditor::SetSegmentType( *curve , sid , T_SyncSegment::E_SegmentType( t ) ); }