#include "externals.hh" #include "common.hh" #include "c-sync.hh" #include "c-undo.hh" #include "ui.hh" #include "ui-app.hh" #include "ui-imgui-sdl.hh" #include "ui-texture.hh" #include /*= T_UIApp =================================================================*/ namespace { #include "font-awesome.inl" static const ImWchar IconsRanges_[] = { ICON_MIN_FA , ICON_MAX_FA , 0 }; } // namespace /*----------------------------------------------------------------------------*/ T_UIApp::T_UIApp( ) { SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER ); SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER , 1 ); SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE , 24 ); SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE , 8 ); SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION , 2 ); SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION , 2 ); SDL_DisplayMode current; SDL_GetCurrentDisplayMode( 0 , ¤t ); window_ = SDL_CreateWindow( "Tourista", SDL_WINDOWPOS_CENTERED , SDL_WINDOWPOS_CENTERED , 1280 , 720 , SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE ); gl_ = SDL_GL_CreateContext( window_ ); glewInit( ); if ( !GLEW_VERSION_4_5 ) { fprintf( stderr , "OpenGL 4.5 required\n" ); exit( 1 ); } ImGui_ImplSdl_Init( window_ ); using namespace ImGui; StyleColorsDark( ); ImGuiIO& io{ GetIO( ) }; io.IniFilename = nullptr; { ImFontConfig cfg; cfg.SizePixels = 13.0f; defaultFont_ = io.Fonts->AddFontDefault( &cfg ); ImFontConfig icons; icons.MergeMode = true; icons.PixelSnapH = true; io.Fonts->AddFontFromMemoryCompressedBase85TTF( FontAwesome__compressed_data_base85 , 13.f , &icons , IconsRanges_ ); } { ImFontConfig cfg; cfg.SizePixels = 11.0f; smallFont_ = io.Fonts->AddFontDefault( &cfg ); ImFontConfig icons; icons.MergeMode = true; icons.PixelSnapH = true; icons.DstFont = smallFont_; io.Fonts->AddFontFromMemoryCompressedBase85TTF( FontAwesome__compressed_data_base85 , 9.f , &icons , IconsRanges_ ); } //---------------------------------------------------------------------- // Keyboard shortcuts for undo/redo/quit/etc. addAction( T_UIAction{ "Undo" , []() { Common::Undo( ).undo( ); } }.setEnabledCheck( []() { return Common::Undo( ).canUndo( ); } ).setIcon( ICON_FA_UNDO ) .setShortcut( T_KeyboardShortcut{ 'z' , E_KbdMod::CTRL } ) ); addAction( T_UIAction{ "Redo" , []() { Common::Undo( ).redo( ); } }.setEnabledCheck( []() { return Common::Undo( ).canRedo( ); } ).setShortcut( T_KeyboardShortcut{ 'z' , { E_KbdMod::CTRL , E_KbdMod::SHIFT } } ) ); addAction( T_UIAction{ "Play" , []() { Common::Sync( ).playing( ) = true; } }.setEnabledCheck( []() { auto const& s{ Common::Sync( ) }; return !s.playing( ) && !s.finished( ); } ).setIcon( ICON_FA_PLAY ) .setShortcut( T_KeyboardShortcut{ ' ' } ) ); addAction( T_UIAction{ "Stop" , []() { Common::Sync( ).playing( ) = false; } }.setEnabledCheck( []() { auto const& s{ Common::Sync( ) }; return s.playing( ); } ).setIcon( ICON_FA_STOP ) .setShortcut( T_KeyboardShortcut{ ' ' } ) ); addAction( T_UIAction{ "Quit" , [this]() { exiting_ = true; } }.setShortcut( T_KeyboardShortcut{ 'q' , E_KbdMod::CTRL } ) ); } T_UIApp::~T_UIApp( ) { ImGui_ImplSdl_Shutdown( ); SDL_GL_DeleteContext( gl_ ); SDL_DestroyWindow( window_ ); SDL_Quit( ); } /*----------------------------------------------------------------------------*/ void T_UIApp::addAction( T_UIAction action ) noexcept { if ( action.shortcut ) { const auto idx{ shortcuts_.indexOf( *action.shortcut ) }; if ( idx == T_HashIndex::INVALID_INDEX ) { using namespace ebcl; T_Set< T_String > nSet{ UseTag< ArrayBacked< 8 > >( ) }; nSet.add( action.id ); shortcuts_.add( *action.shortcut , std::move( nSet ) ); } else { shortcuts_[ idx ].add( action.id ); } } actions_.set( std::move( action ) ); } void T_UIApp::actionMenu( T_String const& id ) const noexcept { auto const* const a{ actions_.get( id ) }; if ( a ) { a->menuItem( ); } } void T_UIApp::actionButton( T_String const& id ) const noexcept { auto const* const a{ actions_.get( id ) }; if ( a ) { a->tbButton( ); } } /*----------------------------------------------------------------------------*/ void T_UIApp::handleEvents( ) noexcept { SDL_Event event; mMove_ = ImVec2( ); kbKeys_.clear( ); while ( SDL_PollEvent( &event ) ) { ImGui_ImplSdl_ProcessEvent( &event ); if ( event.type == SDL_QUIT ) { #warning FIXME request confirmation exiting_ = true; return; } if ( mCapture_ && event.type == SDL_MOUSEMOTION ) { mMove_.x += event.motion.xrel; mMove_.y += event.motion.yrel; } if ( event.type == SDL_KEYDOWN ) { const auto sym{ event.key.keysym.sym }; if ( sym >= 0 && sym <= 127 ) { kbKeys_.add( char( sym ) ); } } } ImGui_ImplSdl_NewFrame( window_ , mCapture_ , mInitial_ ); auto& io( ImGui::GetIO( ) ); io.MouseDrawCursor = true; kbMods_ = ( ([&io]() { T_KbdMods kb; if ( io.KeyCtrl ) { kb |= E_KbdMod::CTRL; } if ( io.KeyShift ) { kb |= E_KbdMod::SHIFT; } if ( io.KeyAlt ) { kb |= E_KbdMod::ALT; } return kb; })() ); handleKeyboardShortcuts( ); handleMouseCapture( ); } void T_UIApp::render( ) noexcept { handleDialogs( ); glUseProgram( 0 ); glBindProgramPipeline( 0 ); UI::Textures( ).reset( ); glClearColor( 0 , 0 , 0 , 1 ); ImGui::Render( ); } void T_UIApp::swap( ) const noexcept { SDL_GL_SwapWindow( window_ ); } /*----------------------------------------------------------------------------*/ void T_UIApp::handleMouseCapture( ) noexcept { using namespace ImGui; auto const& io( GetIO( ) ); const T_MouseButtons mb( ([]() {; T_MouseButtons mb; if ( IsMouseDown( 0 ) ) { mb |= E_MouseButton::LEFT; } if ( IsMouseDown( 1 ) ) { mb |= E_MouseButton::RIGHT; } if ( IsMouseDown( 2 ) ) { mb |= E_MouseButton::MIDDLE; } return mb; })() ); const bool appCanGrab( !( ImGui::IsMouseHoveringAnyWindow( ) || io.WantCaptureMouse || io.WantCaptureKeyboard ) ); if ( mCapture_ && !( mb && mDelegate_ ) ) { mCapture_ = false; CaptureMouseFromApp( false ); SDL_SetRelativeMouseMode( SDL_FALSE ); SDL_WarpMouseInWindow( window_ , mInitial_.x , mInitial_.y ); SetMouseCursor( ImGuiMouseCursor_Arrow ); } else if ( mCapture_ && mDelegate_ ) { SetMouseCursor( ImGuiMouseCursor_Move ); mDelegate_->handleDragAndDrop( mMove_ , kbMods_ , mb ); } else if ( appCanGrab && mb && mDelegate_ ) { mCapture_ = true; mInitial_ = GetMousePos( ); CaptureMouseFromApp( true ); SDL_SetRelativeMouseMode( SDL_TRUE ); SetMouseCursor( ImGuiMouseCursor_Move ); } if ( ( appCanGrab || mCapture_ ) && io.MouseWheel && mDelegate_ ) { mDelegate_->handleWheel( io.MouseWheel , kbMods_ , mb ); } } void T_UIApp::handleKeyboardShortcuts( ) noexcept { auto const& io{ ImGui::GetIO( ) }; if ( io.WantCaptureKeyboard || io.WantTextInput || !modals_.empty( ) ) { return; } for ( auto i = 0u ; i < kbKeys_.size( ) ; i ++ ) { const T_KeyboardShortcut k{ kbKeys_[ i ] , kbMods_ }; auto const* const ksSet{ shortcuts_.get( k ) }; if ( !ksSet ) { continue; } const auto nsk{ ksSet->size( ) }; for ( auto j = 0u ; j < nsk ; j ++ ) { T_String const& s{ (*ksSet)[ j ] }; auto const* const a{ actions_.get( s ) }; if ( a && ( !a->enabled || a->enabled( ) ) ) { a->handler( ); break; } } } } void T_UIApp::handleDialogs( ) noexcept { const auto nDialogs{ modals_.size( ) }; for ( auto i = 0u ; i < nDialogs ; i ++ ) { const bool keep{ modals_[ i ]->draw( ) }; assert( keep || i + 1 == nDialogs ); if ( !keep ) { modals_.removeLast( ); } } }