diff --git a/Makefile b/Makefile index b0be399..36d1554 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,7 @@ COMMON = \ filewatcher.cc \ window.cc \ dialogs.cc \ + ui-actions.cc \ globals.cc \ profiling.cc \ shaders.cc \ diff --git a/main.cc b/main.cc index 376c075..a27992a 100644 --- a/main.cc +++ b/main.cc @@ -50,6 +50,45 @@ T_Main::T_Main( ) { Globals::Init( ); prevSize = ImVec2( -1 , -1 ); + + // FIXME: should be somewhere else + T_UIAction saveCurves{ "Save curves" , []() { + if ( Globals::Sync( ).curvesFileChanged( ) ) { + Globals::Window( ).msgbox( + "Curves file changed" , + "The file containing the curves has been modified " + "on the disk. These changes will be overwritten. " + "Do you want to continue?" , + []( auto b ) { + if ( b == T_MessageBox::BT_YES ) { + Globals::Sync( ).saveCurves( ); + } + } , { T_MessageBox::BT_YES , T_MessageBox::BT_NO } ); + } else { + Globals::Sync( ).saveCurves( ); + } + } }; + saveCurves.setEnabledCheck( []() { return Globals::Sync( ).curvesModified( ); } ) + .setIcon( ICON_FA_FLOPPY_O ) + .setShortcut( T_KeyboardShortcut{ 's' , E_KeyboardModifier::CTRL } ); + Globals::Window( ).addAction( std::move( saveCurves ) ); + + T_UIAction reloadCurves{ "Reload curves" , []() { + Globals::Window( ).msgbox( + "Reload curves?" , + "Changes you made to the curves will be lost. Do you " + "want to continue?" , + []( auto b ) { + if ( b == T_MessageBox::BT_YES ) { + Globals::Sync( ).loadCurves( ); + } + } , { T_MessageBox::BT_YES , T_MessageBox::BT_NO } ); + } }; + reloadCurves.setEnabledCheck( []() { return Globals::Sync( ).curvesModified( ); } ) + .setIcon( ICON_FA_DOWNLOAD ) + .setShortcut( T_KeyboardShortcut{ 'r' , + { E_KeyboardModifier::CTRL , E_KeyboardModifier::SHIFT } } ); + Globals::Window( ).addAction( std::move( reloadCurves ) ); } void T_Main::mainLoop( ) @@ -208,33 +247,8 @@ void T_Main::makeUI( ) bool eSequencer{ sequencer }; if ( BeginMainMenuBar( ) ) { if ( BeginMenu( "File" ) ) { - if ( MenuItem( "Save curves" , "C-s" , false , sync.curvesModified( ) ) ) { - if ( sync.curvesFileChanged( ) ) { - Globals::Window( ).msgbox( - "Curves file changed" , - "The file containing the curves has been modified " - "on the disk. These changes will be overwritten. " - "Do you want to continue?" , - []( auto b ) { - if ( b == T_MessageBox::BT_YES ) { - Globals::Sync( ).saveCurves( ); - } - } , { T_MessageBox::BT_YES , T_MessageBox::BT_NO } ); - } else { - sync.saveCurves( ); - } - } - if ( MenuItem( "Reload curves" , "C-R" , false , sync.curvesModified( ) ) ) { - Globals::Window( ).msgbox( - "Reload curves?" , - "Changes you made to the curves will be lost. Do you " - "want to continue?" , - []( auto b ) { - if ( b == T_MessageBox::BT_YES ) { - Globals::Sync( ).loadCurves( ); - } - } , { T_MessageBox::BT_YES , T_MessageBox::BT_NO } ); - } + Globals::Window( ).actionMenu( "Save curves" ); + Globals::Window( ).actionMenu( "Reload curves" ); Separator( ); if ( MenuItem( "Undo" , "C-z" , false , undo.canUndo( ) ) ) { undo.undo( ); diff --git a/ui-actions.cc b/ui-actions.cc new file mode 100644 index 0000000..3444f70 --- /dev/null +++ b/ui-actions.cc @@ -0,0 +1,89 @@ +#include "externals.hh" +#include "ui-actions.hh" + +#include + + +/*= T_KeyboardShortcut =======================================================*/ + +void T_KeyboardShortcut::toString( + char* const buffer , + const size_t size ) const noexcept +{ + const char ch[ 2 ] = { + char( ( modifiers & E_KeyboardModifier::SHIFT ) + ? toupper( character ) : tolower( character ) ) , + 0 + }; + snprintf( buffer , size , "%s%s%s" , + ( modifiers & E_KeyboardModifier::CTRL ) ? "C-" : "" , + ( modifiers & E_KeyboardModifier::ALT ) ? "A-" : "" , + [&]( ) { + switch ( character ) { + case ' ': return ""; + case '\n': return ""; + default: return ch; + } + }( ) ); +} + + +/*= T_UIAction ===============================================================*/ + +void T_UIAction::menuItem( ) const noexcept +{ + const bool e{ enabled ? enabled( ) : true }; + + T_String const& title{ text ? text : id }; + const uint32_t ts{ title.size( ) }; + char name[ ts + 1 ]; + memcpy( name , title.data( ) , ts ); + name[ ts ] = 0; + + char kbs[ 20 ]; + char const* kbsp = shortcut ? kbs : nullptr; + if ( shortcut ) { + shortcut->toString( kbs , 20 ); + } + + if ( ImGui::MenuItem( name , kbsp , false , e ) ) { + handler( ); + } +} + +void T_UIAction::tbButton( ) const noexcept +{ + using namespace ImGui; + static T_StringBuilder sb; + + sb.clear( ) << ( icon ? icon : "" ) << "##" << id << '\0'; + + const bool e{ enabled ? enabled( ) : true }; + if ( !e ) { + PushItemFlag( ImGuiItemFlags_Disabled , true ); + PushStyleVar( ImGuiStyleVar_Alpha , + GetStyle( ).Alpha * .5f ); + } + const bool rv{ Button( sb.data( ) , ImVec2{ 20 , 0 } ) }; + if ( e && IsItemHovered( ) ) { + sb.clear( ) << ( text ? text : id ); + if ( shortcut ) { + char kbs[ 20 ]; + shortcut->toString( kbs , 20 ); + sb << ' ' << kbs; + } + sb << '\0'; + + BeginTooltip( ); + Text( "%s" , sb.data( ) ); + EndTooltip( ); + } + if ( !e ) { + PopItemFlag( ); + PopStyleVar( ); + } + + if ( rv ) { + handler( ); + } +} diff --git a/ui-actions.hh b/ui-actions.hh new file mode 100644 index 0000000..10f23c4 --- /dev/null +++ b/ui-actions.hh @@ -0,0 +1,57 @@ +#pragma once +#include "ui-input.hh" + + +/*= COMMON ACTION DEFINITIONS ================================================*/ + +struct T_KeyboardShortcut +{ + char character; + T_KeyboardModifiers modifiers; + + explicit constexpr T_KeyboardShortcut( + char character , + T_KeyboardModifiers modifiers = { } ) noexcept + : character{ character } , modifiers{ modifiers } + {} + + // Writes the keyboard shortcut to a buffer + void toString( char* buffer , size_t size ) const noexcept; +}; + +struct T_UIAction +{ + // Function to call when the action is executed + using F_ActionHandler = std::function< void( void ) >; + // Function that checks whether the action is enabled + using F_ActionChecker = std::function< bool( void ) >; + + T_String id; + T_String text; // Text, if different from the identifier + char const* icon{ nullptr }; + T_Optional< T_KeyboardShortcut > shortcut; + F_ActionHandler handler; + F_ActionChecker enabled; + + T_UIAction( char const* id , + F_ActionHandler handler ) noexcept + : id( T_String::Pooled( id ) ) , + handler( std::move( handler ) ) + { assert( this->handler ); } + T_UIAction( T_String const& id , + F_ActionHandler handler ) noexcept + : id( id ) , handler( std::move( handler ) ) + { assert( this->handler ); } + + T_UIAction& setShortcut( const T_KeyboardShortcut sc ) noexcept + { shortcut = sc; return *this; } + T_UIAction& setText( T_String const& t ) noexcept + { text = t; return *this; } + T_UIAction& setIcon( char const* const i ) noexcept + { icon = i; return *this; } + T_UIAction& setEnabledCheck( F_ActionChecker check ) noexcept + { enabled = std::move( check ); return *this; } + + void menuItem( ) const noexcept; + void tbButton( ) const noexcept; +}; diff --git a/window.cc b/window.cc index 8332e34..66c58c4 100644 --- a/window.cc +++ b/window.cc @@ -80,6 +80,30 @@ T_Window::~T_Window( ) SDL_Quit( ); } +void T_Window::addAction( + T_UIAction action ) noexcept +{ + actions_.set( std::move( action ) ); +} + +void T_Window::actionMenu( + T_String const& id ) const noexcept +{ + auto const* const a{ actions_.get( id ) }; + if ( a ) { + a->menuItem( ); + } +} + +void T_Window::actionButton( + T_String const& id ) const noexcept +{ + auto const* const a{ actions_.get( id ) }; + if ( a ) { + a->tbButton( ); + } +} + void T_Window::startFrame( const bool capture , ImVec2 const& mouseInitial ) const diff --git a/window.hh b/window.hh index 8e30c22..4e284e6 100644 --- a/window.hh +++ b/window.hh @@ -1,6 +1,7 @@ #pragma once #include "dialogs.hh" #include "mousectrl.hh" +#include "ui-actions.hh" /*= WINDOW MANAGEMENT ========================================================*/ @@ -28,6 +29,12 @@ struct T_Window //---------------------------------------------------------------------- + void addAction( T_UIAction action ) noexcept; + void actionMenu( T_String const& id ) const noexcept; + void actionButton( T_String const& id ) const noexcept; + + //---------------------------------------------------------------------- + ImFont* defaultFont( ) const noexcept { return defaultFont_; } ImFont* smallFont( ) const noexcept @@ -48,5 +55,9 @@ struct T_Window ImFont* defaultFont_; ImFont* smallFont_; T_AutoArray< P_ModalDialog , 8 > modals_; + T_ObjectTable< T_String , T_UIAction > actions_{ + []( T_UIAction const& a ) -> T_String { + return a.id; + } }; };