UI - Better modal dialogs

+ Buttons can be configured
+ Message box implementation
+ Added confirmation dialogs for some of the stuff that needed them.
This commit is contained in:
Emmanuel BENOîT 2017-11-23 12:24:40 +01:00
parent 7105b7e7d1
commit da570c14bf
4 changed files with 226 additions and 53 deletions

25
main.cc
View file

@ -209,12 +209,31 @@ void T_Main::makeUI( )
if ( BeginMainMenuBar( ) ) { if ( BeginMainMenuBar( ) ) {
if ( BeginMenu( "File" ) ) { if ( BeginMenu( "File" ) ) {
if ( MenuItem( "Save curves" , "C-s" , false , sync.curvesModified( ) ) ) { if ( MenuItem( "Save curves" , "C-s" , false , sync.curvesModified( ) ) ) {
// FIXME: confirmation dialog if file changed 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( ); sync.saveCurves( );
} }
}
if ( MenuItem( "Reload curves" , "C-R" , false , sync.curvesModified( ) ) ) { if ( MenuItem( "Reload curves" , "C-R" , false , sync.curvesModified( ) ) ) {
// FIXME: confirmation dialog Globals::Window( ).msgbox(
sync.loadCurves( ); "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( ); Separator( );
if ( MenuItem( "Undo" , "C-z" , false , undo.canUndo( ) ) ) { if ( MenuItem( "Undo" , "C-z" , false , undo.canUndo( ) ) ) {

View file

@ -70,8 +70,8 @@ class T_ChangeDurationDialog_ : public A_ModalDialog
bool scale_{ true }; bool scale_{ true };
protected: protected:
bool drawDialog( ) noexcept override; uint8_t drawDialog( ) noexcept override;
void onOK( ) noexcept override; bool onButton( uint8_t id ) noexcept override;
public: public:
T_ChangeDurationDialog_( T_ChangeDurationDialog_(
@ -90,9 +90,11 @@ T_ChangeDurationDialog_::T_ChangeDurationDialog_(
units_{ units0_ } , uSize_{ uSize0_ } , uPerMinute_{ uPerMinute0_ } units_{ units0_ } , uSize_{ uSize0_ } , uPerMinute_{ uPerMinute0_ }
{ {
setInitialSize( 300.f , 180.f ); setInitialSize( 300.f , 180.f );
addButton( "OK" );
addButton( "Cancel" );
} }
bool T_ChangeDurationDialog_::drawDialog( ) noexcept uint8_t T_ChangeDurationDialog_::drawDialog( ) noexcept
{ {
using namespace ImGui; using namespace ImGui;
@ -137,14 +139,19 @@ bool T_ChangeDurationDialog_::drawDialog( ) noexcept
PopStyleVar( ); 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
{ {
if ( button == 0 ) {
SyncEditor::SetDuration( units_ , SyncEditor::SetDuration( units_ ,
uPerMinute_ != uPerMinute0_ ? uSize_ : uSize0_ , uPerMinute_ != uPerMinute0_ ? uSize_ : uSize0_ ,
scale_ ); scale_ );
}
return true;
} }

142
window.cc
View file

@ -11,20 +11,46 @@ A_ModalDialog::A_ModalDialog(
: id_{ id , uint32_t( strlen( id ) + 1 ) } : 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( ) 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::initDialog( ) noexcept
{} {}
void A_ModalDialog::onCancel( ) noexcept
{ }
bool A_ModalDialog::draw( ) noexcept bool A_ModalDialog::draw( ) noexcept
{ {
using namespace ImGui; using namespace ImGui;
if ( !open_ ) { if ( !open_ ) {
OpenPopup( id_.data( ) ); assert( buttons_.size( ) );
OpenPopup( &id_[ 0 ] );
open_ = true; open_ = true;
} }
@ -33,49 +59,115 @@ bool A_ModalDialog::draw( ) noexcept
SetNextWindowSize( *initialSize_ , ImGuiCond_Appearing ); SetNextWindowSize( *initialSize_ , ImGuiCond_Appearing );
} }
bool ok{ false }; BeginPopupModal( &id_[ 0 ] , nullptr , ImGuiWindowFlags_NoResize );
bool keep{ BeginPopupModal( id_.data( ) , nullptr , ImGuiWindowFlags_NoResize ) }; const auto btMask{ drawDialog( ) };
if ( keep ) { assert( btMask != 0 );
const bool canUseOK{ drawDialog( ) };
const ImVec2 ws( GetWindowContentRegionMax( ) ); const ImVec2 ws( GetWindowContentRegionMax( ) );
auto const& style( GetStyle( ) ); auto const& style( GetStyle( ) );
const float innerWidth{ ws.x - 2 * style.FramePadding.x }; const float innerWidth{ ws.x - 2 * style.FramePadding.x };
constexpr float nButtons{ 2.f }; const auto nButtons{ buttons_.size( ) };
const ImVec2 buttonSize{ std::max( 40.f , const ImVec2 buttonSize{
( innerWidth - nButtons * style.FramePadding.x ) / nButtons ) , std::max( 40.f , ( innerWidth - nButtons * style.FramePadding.x ) / nButtons ) ,
0.f 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 ); PushItemFlag( ImGuiItemFlags_Disabled , true );
PushStyleVar( ImGuiStyleVar_Alpha , PushStyleVar( ImGuiStyleVar_Alpha ,
GetStyle( ).Alpha * .5f ); GetStyle( ).Alpha * .5f );
} }
if ( Button( "OK" , buttonSize ) ) { if ( Button( &buttons_[ i ][ 0 ] , buttonSize ) ) {
ok = true; assert( clicked == -1 );
keep = false; clicked = i;
} }
if ( !canUseOK ) { if ( d ) {
PopItemFlag( ); PopItemFlag( );
PopStyleVar( ); PopStyleVar( );
} }
SameLine( 0 ); }
keep = !Button( "Cancel" , buttonSize ) && keep; const bool close( clicked != -1 && onButton( clicked ) );
if ( close ) {
if ( !keep ) {
CloseCurrentPopup( ); CloseCurrentPopup( );
} }
EndPopup( ); EndPopup( );
return !close;
}
/*= 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;
} }
if ( !keep ) { buttonMask_ |= ( 1 << nb );
if ( ok ) { nb ++;
onOK( ); 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;
}
}
}
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 { } else {
onCancel( ); 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 keep; return true;
} }

View file

@ -9,29 +9,34 @@
class A_ModalDialog class A_ModalDialog
{ {
private: private:
const T_String id_; ebcl::T_Buffer< char > id_;
bool open_{ false }; bool open_{ false };
T_Optional< ImVec2 > initialSize_; T_Optional< ImVec2 > initialSize_;
T_StaticArray< ebcl::T_Buffer< char > , 4 > buttons_;
protected: protected:
A_ModalDialog( char const* id ) noexcept; A_ModalDialog( char const* id ) noexcept;
A_ModalDialog( T_String const& id ) noexcept;
void setInitialSize( const float width , void setInitialSize( const float width ,
const float height ) noexcept const float height ) noexcept
{ initialSize_.setNew( width , height ); } { 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. // Initialisation, called right before the window is opened.
// Does nothing by default. // Does nothing by default.
virtual void initDialog( ) noexcept; virtual void initDialog( ) noexcept;
// Draw the dialog box's contents, returns true if the OK button // Draw the dialog box's contents, returns a mask that determines
// should be enabled. // which buttons should be enabled.
virtual bool drawDialog( ) noexcept = 0; virtual uint8_t drawDialog( ) noexcept = 0;
// Handlers for both OK and Cancel. The former must be overridden, // Button click handler. Returns true if the dialog should be
// the latter defaults to "do nothing". // closed.
virtual void onOK( ) noexcept = 0; virtual bool onButton( uint8_t button ) noexcept = 0;
virtual void onCancel( ) noexcept;
public: public:
NO_COPY( A_ModalDialog ); NO_COPY( A_ModalDialog );
@ -45,6 +50,44 @@ class A_ModalDialog
using P_ModalDialog = T_OwnPtr< 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 struct T_Window
{ {
T_Window( ); T_Window( );
@ -58,6 +101,18 @@ struct T_Window
void pushDialog( P_ModalDialog dialog ) noexcept void pushDialog( P_ModalDialog dialog ) noexcept
{ modals_.add( std::move( dialog ) ); } { 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; void handleDialogs( ) noexcept;
ImFont* defaultFont( ) const noexcept ImFont* defaultFont( ) const noexcept