diff --git a/TODO b/TODO index 4b5e671..159fe77 100644 --- a/TODO +++ b/TODO @@ -37,7 +37,6 @@ Sync / inputs: * Value changes * Duration changes * Point insertion - * Total duration / duration unit changes * Compaction * Local changes vs file update * Editing single values diff --git a/main.cc b/main.cc index 13c2c1b..9b7ab74 100644 --- a/main.cc +++ b/main.cc @@ -10,8 +10,7 @@ #include "rendertarget.hh" #include "sync.hh" #include "syncview.hh" - -using ebcl::T_Optional; +#include "undo.hh" /*= T_Main ===================================================================*/ @@ -205,9 +204,17 @@ void T_Main::handleCapture( ) void T_Main::makeUI( ) { using namespace ImGui; + auto& undo( Globals::Undo( ) ); bool eSequencer{ sequencer }; if ( BeginMainMenuBar( ) ) { if ( BeginMenu( "File" ) ) { + if ( MenuItem( "Undo" , "C-z" , false , undo.canUndo( ) ) ) { + undo.undo( ); + } + if ( MenuItem( "Redo" , "C-Z" , false , undo.canRedo( ) ) ) { + undo.redo( ); + } + Separator( ); if ( MenuItem( "Quit" ) ) { done = true; } diff --git a/sync.hh b/sync.hh index 0472e42..983e5b1 100644 --- a/sync.hh +++ b/sync.hh @@ -348,6 +348,9 @@ struct T_SyncManager : public virtual A_MouseCtrl void setCurve( T_SyncCurve curve ); void removeCurve( T_String const& curve ) noexcept; + T_Array< T_SyncCurve > const& curves( ) const noexcept + { return curves_.curves.values( ); } + private: void curvesChanged_( ); bool loadCurves_( bool& missing ); diff --git a/syncedit.cc b/syncedit.cc index 787ff5f..f94d668 100644 --- a/syncedit.cc +++ b/syncedit.cc @@ -3,7 +3,7 @@ #include "globals.hh" -/*= T_UndoSyncChanges ==========================================================*/ +/*= T_UndoSyncChanges ========================================================*/ void T_UndoSyncChanges::undo( ) const noexcept { @@ -33,7 +33,7 @@ void T_UndoSyncChanges::redo( ) const noexcept } } -/*------------------------------------------------------------------------------*/ +/*----------------------------------------------------------------------------*/ T_UndoSyncChanges& T_UndoSyncChanges::curveCreation( T_SyncCurve curve ) noexcept @@ -81,3 +81,91 @@ T_UndoSyncChanges& T_UndoSyncChanges::curveReplacement( changes_.addNew( std::move( before ) , std::move( after ) ); return *this; } + + +/*= T_UndoDurationChanges ====================================================*/ + +T_UndoDurationChanges::T_UndoDurationChanges( + const uint32_t units , + const uint32_t oldUnits , + const float unitSize , + const float oldUnitSize ) noexcept + : T_UndoSyncChanges( ) , + unitsBefore_{ oldUnits } , + unitsAfter_{ units } , + uSizeBefore_{ oldUnitSize } , + uSizeAfter_{ unitSize } +{ } + +void T_UndoDurationChanges::undo( ) const noexcept +{ + Globals::Sync( ).setDuration( uSizeBefore_ , unitsBefore_ ); + T_UndoSyncChanges::undo( ); +} + +void T_UndoDurationChanges::redo( ) const noexcept +{ + Globals::Sync( ).setDuration( uSizeAfter_ , unitsAfter_ ); + T_UndoSyncChanges::redo( ); +} + + +/*= SyncEditor ===============================================================*/ + +namespace { + +T_SyncCurve SESDScaleCurve_( + const float uSizeBefore , + const float uSizeAfter , + T_SyncCurve const& curve ) noexcept +{ + T_SyncCurve nCurve; + nCurve.name = curve.name; + for ( auto const& segment : curve.segments ) { + const auto nsid{ nCurve.segments.add( segment ) }; + T_SyncSegment& nSeg{ nCurve.segments[ nsid ] }; + const auto nd{ nSeg.durations.size( ) }; + for ( auto i = 0u ; i < nd ; i ++ ) { + const float duration{ nSeg.durations[ i ] * uSizeBefore }; + nSeg.durations[ i ] = std::max( 1u , uint32_t( std::roundf( + duration / uSizeAfter ) ) ); + printf( "initial duration %f (%d units) ; new duration %f (%d units)\n" , + duration , segment.durations[ i ] , + uSizeAfter * nSeg.durations[ i ] , + nSeg.durations[ i ] ); + } + } + return nCurve; +} + +} // namespace + +void SyncEditor::SetDuration( + const uint32_t units , + const float uSize , + const bool scaleCurves ) noexcept +{ + auto& sync{ Globals::Sync( ) }; + const float oldUnitSize{ sync.durationUnitSize( ) }; + const uint32_t oldUnits{ sync.durationUnits( ) }; + if ( oldUnits == units && oldUnitSize == uSize ) { + return; + } + + auto& undo{ dynamic_cast< T_UndoDurationChanges& >( + Globals::Undo( ).add< T_UndoDurationChanges >( + units , oldUnits , + uSize , oldUnitSize ) ) }; + sync.setDuration( uSize , units ); + if ( !scaleCurves || uSize == oldUnitSize ) { + return; + } + + auto const& curves( sync.curves( ) ); + for ( auto const& curve : curves ) { + auto nc{ SESDScaleCurve_( oldUnitSize , uSize , curve ) }; + undo.curveReplacement( curve , nc ); + sync.setCurve( std::move( nc ) ); + } +} + diff --git a/syncedit.hh b/syncedit.hh index 828eafa..754c3c7 100644 --- a/syncedit.hh +++ b/syncedit.hh @@ -3,7 +3,7 @@ #include "undo.hh" -/*= GENERAL STRUCTURE FOR SYNC EDITOR UNDOS ====================================*/ +/*= GENERAL STRUCTURE FOR SYNC EDITOR UNDOS ==================================*/ class T_UndoSyncChanges : public A_UndoAction { @@ -51,3 +51,42 @@ class T_UndoSyncChanges : public A_UndoAction T_SyncCurve before , T_SyncCurve after ) noexcept; }; + + +/*= DURATION CHANGES =========================================================*/ + +class T_UndoDurationChanges final : public T_UndoSyncChanges +{ + private: + uint32_t unitsBefore_ , unitsAfter_; + float uSizeBefore_ , uSizeAfter_; + + public: + T_UndoDurationChanges( + uint32_t units , + uint32_t oldUnits , + float unitSize , + float oldUnitSize ) noexcept; + + T_UndoDurationChanges( ) noexcept = delete; + DEF_MOVE( T_UndoDurationChanges ); + NO_COPY( T_UndoDurationChanges ); + + void undo( ) const noexcept override; + void redo( ) const noexcept override; +}; + + +/*= EDITION FUNCTIONS ========================================================*/ + +struct SyncEditor final +{ + // Change the duration of the demo using the specified unit count and + // size. If scaleCurves is true and the unit size has changed, the + // curves' durations will be scaled so that the timings match as + // closely as possible. + static void SetDuration( + uint32_t units , + float uSize , + bool scaleCurves ) noexcept; +}; diff --git a/syncview.cc b/syncview.cc index 9aae7dc..9422bed 100644 --- a/syncview.cc +++ b/syncview.cc @@ -2,38 +2,130 @@ #include "syncview.hh" #include "globals.hh" #include "window.hh" +#include "syncedit.hh" #define IMGUI_DEFINE_MATH_OPERATORS #include using namespace ebcl; -/*= FAKE TABS HELPER ===========================================================*/ - namespace { - bool FakeTab_( - char const* const name , - const bool disabled , - const float width = 0.f ) - { - using namespace ImGui; - if ( disabled ) { - PushItemFlag( ImGuiItemFlags_Disabled , true ); - PushStyleVar( ImGuiStyleVar_Alpha , - GetStyle( ).Alpha * .5f ); - } - const bool rv( Button( name , ImVec2{ width , 0.f } ) ); - if ( disabled ) { - PopItemFlag( ); - PopStyleVar( ); - } - return rv; + +/*= FAKE TABS HELPER =========================================================*/ + +bool FakeTab_( + char const* const name , + const bool disabled , + const float width = 0.f ) +{ + using namespace ImGui; + if ( disabled ) { + PushItemFlag( ImGuiItemFlags_Disabled , true ); + PushStyleVar( ImGuiStyleVar_Alpha , + GetStyle( ).Alpha * .5f ); } -} // namespace + const bool rv( Button( name , ImVec2{ width , 0.f } ) ); + if ( disabled ) { + PopItemFlag( ); + PopStyleVar( ); + } + return rv; +} -/*= T_SyncViewImpl_ ============================================================*/ -namespace { +/*= T_ChangeDurationDialog_ ==================================================*/ + +class T_ChangeDurationDialog_ : public A_ModalDialog +{ + private: + const uint32_t units0_; + const float uSize0_; + const uint32_t uPerMinute0_; + uint32_t units_; + float uSize_; + uint32_t uPerMinute_; + bool scale_{ true }; + + protected: + bool drawDialog( ) noexcept override; + void onOK( ) noexcept override; + + public: + T_ChangeDurationDialog_( + uint32_t units , + float uSize ) noexcept; +}; + +/*----------------------------------------------------------------------------*/ + +T_ChangeDurationDialog_::T_ChangeDurationDialog_( + const uint32_t units , + const float uSize ) noexcept + : A_ModalDialog{ "Set demo duration...##duration-dialog" } , + units0_{ units } , uSize0_{ std::max( 1.f / 60.f , uSize ) } , + uPerMinute0_{ uint32_t( ImClamp( roundf( 60.f / uSize0_ ) , 1.f , 3600.f ) ) } , + units_{ units0_ } , uSize_{ uSize0_ } , uPerMinute_{ uPerMinute0_ } +{ + setInitialSize( 300.f , 180.f ); +} + +bool T_ChangeDurationDialog_::drawDialog( ) noexcept +{ + using namespace ImGui; + + int tUnits( units_ ); + if ( DragInt( "Duration##units" , &tUnits , .1f , 1 , INT32_MAX , "%.0f unit(s)" ) ) { + units_ = uint32_t( std::min( INT32_MAX , std::max( 1 , tUnits ) ) ); + } + + float tDuration( units_ * uSize_ ); + if ( DragFloat( "##seconds" , &tDuration , 1.f , .001f , FLT_MAX , "%.3f second(s)" ) ) { + units_ = std::min( uint32_t( INT32_MAX ) , std::max( 1u , + uint32_t( roundf( tDuration / uSize_ ) ) ) ); + } + + Separator( ); + + int tUsize( floorf( uSize_ * 1000.f ) ); + if ( SliderInt( "Units" , &tUsize , 16 , 2000 , "%.0f ms" ) ) { + const float pDur{ uSize_ * units_ }; + uSize_ = std::min( 2.f , std::max( 1.f / 60.f , + .001f * tUsize ) ); + uPerMinute_ = roundf( 60.f / uSize_ ); + units_ = uint32_t( roundf( pDur / uSize_ ) ); + } + + int tPerMin( uPerMinute_ ); + if ( SliderInt( "Units/minute" , &tPerMin , 30 , 3600 ) ) { + const float pDur{ uSize_ * units_ }; + uPerMinute_ = std::max( 30u , std::min( 3600u , uint32_t( tPerMin ) ) ); + uSize_ = 60.f / uPerMinute_; + units_ = uint32_t( roundf( pDur / uSize_ ) ); + } + + if ( uPerMinute0_ == uPerMinute_ ) { + PushItemFlag( ImGuiItemFlags_Disabled , true ); + PushStyleVar( ImGuiStyleVar_Alpha , + GetStyle( ).Alpha * .5f ); + } + Checkbox( "Scale curves" , &scale_ ); + if ( uPerMinute0_ == uPerMinute_ ) { + PopItemFlag( ); + PopStyleVar( ); + } + + return units_ != units0_ || uPerMinute_ != uPerMinute0_; +} + +void T_ChangeDurationDialog_::onOK( ) noexcept +{ + SyncEditor::SetDuration( units_ , + uPerMinute_ != uPerMinute0_ ? uSize_ : uSize0_ , + scale_ ); +} + + +/*= T_SyncViewImpl_ ==========================================================*/ struct T_SyncViewImpl_ { @@ -94,7 +186,7 @@ struct T_SyncViewImpl_ constexpr float T_SyncViewImpl_::BarWidth; -/*------------------------------------------------------------------------------*/ +/*----------------------------------------------------------------------------*/ bool T_SyncViewImpl_::display( ) noexcept { @@ -168,6 +260,20 @@ bool T_SyncViewImpl_::display( ) noexcept VerticalSeparator( ); SameLine( ); + if ( Button( ICON_FA_CLOCK_O , BtSize ) ) { + Globals::Window( ).pushDialog( NewOwned< T_ChangeDurationDialog_ >( + sync.durationUnits( ) , sync.durationUnitSize( ) ) ); + } + if ( IsItemHovered( ) ) { + BeginTooltip( ); + Text( "Change duration and time units." ); + EndTooltip( ); + } + + SameLine( ); + VerticalSeparator( ); + SameLine( ); + if ( Button( ICON_FA_LINE_CHART ) ) { const bool displaySelector{ sub == SW_CURVE_SELECTOR || sub == SW_OVERRIDE_SELECTOR }; @@ -425,7 +531,7 @@ void T_SyncViewImpl_::sequencerBody( } } -/*------------------------------------------------------------------------------*/ +/*----------------------------------------------------------------------------*/ void T_SyncViewImpl_::displayCurveSelectorWindow( ) noexcept { @@ -597,9 +703,10 @@ void T_SyncViewImpl_::displayOverrideSelector( ) noexcept EndChild( ); } +} // namespace -} // namespace -/*= T_SyncView =================================================================*/ + +/*= T_SyncView ===============================================================*/ T_SyncView::T_SyncView( ) noexcept : A_PrivateImplementation( new T_SyncViewImpl_( ) )