From da570c14bfa5a92a910c83e853ab5451f7531727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Thu, 23 Nov 2017 12:24:40 +0100 Subject: [PATCH] UI - Better modal dialogs + Buttons can be configured + Message box implementation + Added confirmation dialogs for some of the stuff that needed them. --- main.cc | 27 +++++++-- syncview.cc | 23 +++++--- window.cc | 158 +++++++++++++++++++++++++++++++++++++++++----------- window.hh | 71 ++++++++++++++++++++--- 4 files changed, 226 insertions(+), 53 deletions(-) diff --git a/main.cc b/main.cc index 4ac0054..376c075 100644 --- a/main.cc +++ b/main.cc @@ -209,12 +209,31 @@ void T_Main::makeUI( ) if ( BeginMainMenuBar( ) ) { if ( BeginMenu( "File" ) ) { if ( MenuItem( "Save curves" , "C-s" , false , sync.curvesModified( ) ) ) { - // FIXME: confirmation dialog if file changed - sync.saveCurves( ); + 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( ) ) ) { - // FIXME: confirmation dialog - sync.loadCurves( ); + 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 } ); } Separator( ); if ( MenuItem( "Undo" , "C-z" , false , undo.canUndo( ) ) ) { diff --git a/syncview.cc b/syncview.cc index 5547bc8..481fe03 100644 --- a/syncview.cc +++ b/syncview.cc @@ -70,8 +70,8 @@ class T_ChangeDurationDialog_ : public A_ModalDialog bool scale_{ true }; protected: - bool drawDialog( ) noexcept override; - void onOK( ) noexcept override; + uint8_t drawDialog( ) noexcept override; + bool onButton( uint8_t id ) noexcept override; public: T_ChangeDurationDialog_( @@ -90,9 +90,11 @@ T_ChangeDurationDialog_::T_ChangeDurationDialog_( units_{ units0_ } , uSize_{ uSize0_ } , uPerMinute_{ uPerMinute0_ } { setInitialSize( 300.f , 180.f ); + addButton( "OK" ); + addButton( "Cancel" ); } -bool T_ChangeDurationDialog_::drawDialog( ) noexcept +uint8_t T_ChangeDurationDialog_::drawDialog( ) noexcept { using namespace ImGui; @@ -137,14 +139,19 @@ bool T_ChangeDurationDialog_::drawDialog( ) noexcept PopStyleVar( ); } - return units_ != units0_ || uPerMinute_ != uPerMinute0_; + const bool eo{ units_ != units0_ || uPerMinute_ != uPerMinute0_ }; + return eo ? 3 : 2; } -void T_ChangeDurationDialog_::onOK( ) noexcept +bool T_ChangeDurationDialog_::onButton( + const uint8_t button ) noexcept { - SyncEditor::SetDuration( units_ , - uPerMinute_ != uPerMinute0_ ? uSize_ : uSize0_ , - scale_ ); + if ( button == 0 ) { + SyncEditor::SetDuration( units_ , + uPerMinute_ != uPerMinute0_ ? uSize_ : uSize0_ , + scale_ ); + } + return true; } diff --git a/window.cc b/window.cc index 572138f..89611d8 100644 --- a/window.cc +++ b/window.cc @@ -11,20 +11,46 @@ A_ModalDialog::A_ModalDialog( : id_{ id , uint32_t( strlen( id ) + 1 ) } {} +A_ModalDialog::A_ModalDialog( + T_String const& id ) noexcept +{ + if ( id && id[ id.length( ) - 1 ] == 0 ) { + id_ = ebcl::T_Buffer< char >{ id.data( ) , id.size( ) }; + } else { + id_ = id.toOSString( ); + } +} + A_ModalDialog::~A_ModalDialog( ) { } +uint8_t A_ModalDialog::addButton( + char const* name ) noexcept +{ + buttons_.addNew( name , strlen( name ) + 1 ); + return buttons_.size( ) - 1; +} + +uint8_t A_ModalDialog::addButton( + T_String const& name ) noexcept +{ + if ( name && name[ name.length( ) - 1 ] == 0 ) { + buttons_.addNew( name.data( ) , name.size( ) ); + } else { + buttons_.add( name.toOSString( ) ); + } + return buttons_.size( ); +} + void A_ModalDialog::initDialog( ) noexcept {} -void A_ModalDialog::onCancel( ) noexcept -{ } - bool A_ModalDialog::draw( ) noexcept { using namespace ImGui; if ( !open_ ) { - OpenPopup( id_.data( ) ); + assert( buttons_.size( ) ); + OpenPopup( &id_[ 0 ] ); open_ = true; } @@ -33,49 +59,115 @@ bool A_ModalDialog::draw( ) noexcept SetNextWindowSize( *initialSize_ , ImGuiCond_Appearing ); } - bool ok{ false }; - bool keep{ BeginPopupModal( id_.data( ) , nullptr , ImGuiWindowFlags_NoResize ) }; - if ( keep ) { - const bool canUseOK{ drawDialog( ) }; + BeginPopupModal( &id_[ 0 ] , nullptr , ImGuiWindowFlags_NoResize ); + const auto btMask{ drawDialog( ) }; + assert( btMask != 0 ); - const ImVec2 ws( GetWindowContentRegionMax( ) ); - auto const& style( GetStyle( ) ); - const float innerWidth{ ws.x - 2 * style.FramePadding.x }; - constexpr float nButtons{ 2.f }; - const ImVec2 buttonSize{ std::max( 40.f , - ( innerWidth - nButtons * style.FramePadding.x ) / nButtons ) , - 0.f - }; + const ImVec2 ws( GetWindowContentRegionMax( ) ); + auto const& style( GetStyle( ) ); + const float innerWidth{ ws.x - 2 * style.FramePadding.x }; + const auto nButtons{ buttons_.size( ) }; + const ImVec2 buttonSize{ + std::max( 40.f , ( innerWidth - nButtons * style.FramePadding.x ) / nButtons ) , + 0.f + }; - if ( !canUseOK ) { + int32_t clicked{ -1 }; + for ( auto i = 0u ; i < buttons_.size( ) ; i ++ ) { + const auto m{ 1 << i }; + const bool d{ ( btMask & m ) == 0 }; + if ( i ) { + SameLine( 0 ); + } + if ( d ) { PushItemFlag( ImGuiItemFlags_Disabled , true ); PushStyleVar( ImGuiStyleVar_Alpha , GetStyle( ).Alpha * .5f ); } - if ( Button( "OK" , buttonSize ) ) { - ok = true; - keep = false; + if ( Button( &buttons_[ i ][ 0 ] , buttonSize ) ) { + assert( clicked == -1 ); + clicked = i; } - if ( !canUseOK ) { + if ( d ) { PopItemFlag( ); PopStyleVar( ); } - SameLine( 0 ); - keep = !Button( "Cancel" , buttonSize ) && keep; + } + const bool close( clicked != -1 && onButton( clicked ) ); + if ( close ) { + CloseCurrentPopup( ); + } + EndPopup( ); + return !close; +} - if ( !keep ) { - CloseCurrentPopup( ); + +/*= T_MessageBox =============================================================*/ + +void T_MessageBox::setButtons( + const T_Buttons buttons ) noexcept +{ + assert( buttons ); + for ( auto i = 0u , nb = 0u ; i < 4 ; i ++ ) { + const E_Button b{ (E_Button) i }; + const bool hasButton{ buttons & T_Buttons{ b } }; + if ( !hasButton ) { + continue; } - EndPopup( ); - } - if ( !keep ) { - if ( ok ) { - onOK( ); - } else { - onCancel( ); + buttonMask_ |= ( 1 << nb ); + nb ++; + buttons_.add( b ); + switch ( b ) { + case BT_OK: addButton( "OK" ); break; + case BT_CANCEL: addButton( "Cancel" ); break; + case BT_YES: addButton( "Yes" ); break; + case BT_NO: addButton( "No" ); break; } } - return keep; +} + +T_MessageBox::T_MessageBox( + char const* const title , + char const* const text , + F_Handler handler , + const T_Buttons buttons ) noexcept + : A_ModalDialog( title ) , text_{ text , strlen( text ) + 1 } , + handler_{ std::move( handler ) } +{ + setButtons( buttons ); + setInitialSize( 300.f , 100.f ); +} + +T_MessageBox::T_MessageBox( + T_String const& title , + T_String const& text , + F_Handler handler , + const T_Buttons buttons ) noexcept + : A_ModalDialog( title ) , handler_{ std::move( handler ) } +{ + if ( text && text[ text.length( ) - 1 ] == 0 ) { + text_ = ebcl::T_Buffer< char >{ text.data( ) , text.size( ) }; + } else { + text_ = text.toOSString( ); + } + setButtons( buttons ); + setInitialSize( 300.f , 100.f ); +} + +uint8_t T_MessageBox::drawDialog( ) noexcept +{ + ImGui::TextWrapped( "%s" , &text_[ 0 ] ); + return buttonMask_; +} + +bool T_MessageBox::onButton( + uint8_t button ) noexcept +{ + assert( button < buttons_.size( ) ); + if ( handler_ ) { + handler_( buttons_[ button ] ); + } + return true; } diff --git a/window.hh b/window.hh index 9007e91..7d8b2ce 100644 --- a/window.hh +++ b/window.hh @@ -9,29 +9,34 @@ class A_ModalDialog { private: - const T_String id_; + ebcl::T_Buffer< char > id_; bool open_{ false }; T_Optional< ImVec2 > initialSize_; + T_StaticArray< ebcl::T_Buffer< char > , 4 > buttons_; protected: A_ModalDialog( char const* id ) noexcept; + A_ModalDialog( T_String const& id ) noexcept; void setInitialSize( const float width , const float height ) noexcept { initialSize_.setNew( width , height ); } + // Add a button w/ the specified name, return its identifier + uint8_t addButton( char const* name ) noexcept; + uint8_t addButton( T_String const& name ) noexcept; + // Initialisation, called right before the window is opened. // Does nothing by default. virtual void initDialog( ) noexcept; - // Draw the dialog box's contents, returns true if the OK button - // should be enabled. - virtual bool drawDialog( ) noexcept = 0; + // Draw the dialog box's contents, returns a mask that determines + // which buttons should be enabled. + virtual uint8_t drawDialog( ) noexcept = 0; - // Handlers for both OK and Cancel. The former must be overridden, - // the latter defaults to "do nothing". - virtual void onOK( ) noexcept = 0; - virtual void onCancel( ) noexcept; + // Button click handler. Returns true if the dialog should be + // closed. + virtual bool onButton( uint8_t button ) noexcept = 0; public: NO_COPY( A_ModalDialog ); @@ -45,6 +50,44 @@ class A_ModalDialog using P_ModalDialog = T_OwnPtr< A_ModalDialog >; +class T_MessageBox : public A_ModalDialog +{ + public: + enum E_Button { + BT_OK , + BT_CANCEL , + BT_YES , + BT_NO , + }; + using T_Buttons = T_Flags< E_Button >; + + // Result handler + using F_Handler = std::function< void( E_Button ) >; + + private: + ebcl::T_Buffer< char > text_; + F_Handler handler_; + uint8_t buttonMask_{ 0 }; + T_StaticArray< E_Button , 4 > buttons_; + + void setButtons( T_Buttons buttons ) noexcept; + + protected: + uint8_t drawDialog( ) noexcept override; + bool onButton( uint8_t button ) noexcept override; + + public: + T_MessageBox( char const* title , + char const* text , + F_Handler handler = { } , + T_Buttons buttons = BT_OK ) noexcept; + T_MessageBox( T_String const& title , + T_String const& text , + F_Handler handler = { } , + T_Buttons buttons = BT_OK ) noexcept; +}; + + struct T_Window { T_Window( ); @@ -58,6 +101,18 @@ struct T_Window void pushDialog( P_ModalDialog dialog ) noexcept { modals_.add( std::move( dialog ) ); } + + void msgbox( char const* title , + char const* text , + T_MessageBox::F_Handler handler = { } , + T_MessageBox::T_Buttons buttons = T_MessageBox::BT_OK ) + { modals_.add( NewOwned< T_MessageBox >( title , text , handler , buttons ) ); } + void msgbox( T_String const& title , + T_String const& text , + T_MessageBox::F_Handler handler = { } , + T_MessageBox::T_Buttons buttons = T_MessageBox::BT_OK ) + { modals_.add( NewOwned< T_MessageBox >( title , text , handler , buttons ) ); } + void handleDialogs( ) noexcept; ImFont* defaultFont( ) const noexcept