From b8b75c3001afb59413290ef43d7a78667f6195ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 14 Aug 2022 02:17:03 -0300 Subject: [PATCH] UIEditor temporal layouts. eterm optimizations. Minor fixes. --- include/eepp/system/filesystem.hpp | 3 + .../eepp/ui/tools/uicodeeditorsplitter.hpp | 4 +- src/eepp/system/filesystem.cpp | 4 + src/eepp/ui/css/stylesheetspecification.cpp | 14 ++ src/eepp/ui/tools/uicodeeditorsplitter.cpp | 30 ++- src/eepp/ui/uiwidgetcreator.cpp | 1 + .../src/eterm/terminal/pseudoterminal.cpp | 2 +- .../src/eterm/terminal/terminalemulator.cpp | 4 +- src/tools/eterm/eterm.cpp | 21 +- src/tools/uieditor/uieditor.cpp | 238 +++++++++++++++--- src/tools/uieditor/uieditor.hpp | 22 +- 11 files changed, 287 insertions(+), 56 deletions(-) diff --git a/include/eepp/system/filesystem.hpp b/include/eepp/system/filesystem.hpp index 76f7bda0b..8023a97da 100644 --- a/include/eepp/system/filesystem.hpp +++ b/include/eepp/system/filesystem.hpp @@ -70,6 +70,9 @@ class EE_API FileSystem { /** Write a file in binary mode and close it. */ static bool fileWrite( const std::string& filepath, const std::vector& data ); + /** Write a file in binary mode and close it. */ + static bool fileWrite( const std::string& filepath, const std::string& data ); + /** Deletes a file from the file system. */ static bool fileRemove( const std::string& filepath ); diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index 940494cc9..040836ed5 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -2,6 +2,7 @@ #define EE_UI_TOOLS_UICODEEDITORSPLITTER_HPP #include +#include #include #include #include @@ -95,7 +96,7 @@ class EE_API UICodeEditorSplitter { std::function onLoaded = std::function() ); - void loadFileFromPathInNewTab( const std::string& path ); + std::pair loadFileFromPathInNewTab( const std::string& path ); void loadAsyncFileFromPathInNewTab( const std::string& path, std::shared_ptr pool, @@ -189,6 +190,7 @@ class EE_API UICodeEditorSplitter { bool mHideTabBarOnSingleTab{ true }; bool mFirstCodeEditor{ true }; UICodeEditor* mAboutToAddEditor{ nullptr }; + UIMessageBox* mTryCloseMsgBox{ nullptr }; UICodeEditorSplitter( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode, const std::vector& colorSchemes, diff --git a/src/eepp/system/filesystem.cpp b/src/eepp/system/filesystem.cpp index f4474ae7a..a036c14e2 100644 --- a/src/eepp/system/filesystem.cpp +++ b/src/eepp/system/filesystem.cpp @@ -194,6 +194,10 @@ bool FileSystem::fileWrite( const std::string& filepath, const std::vector( &data[0] ), (Uint32)data.size() ); } +bool FileSystem::fileWrite( const std::string& filepath, const std::string& data ) { + return fileWrite( filepath, (const Uint8*)data.c_str(), (Uint32)data.size() ); +} + bool FileSystem::fileRemove( const std::string& filepath ) { return 0 == remove( filepath.c_str() ); } diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index 61c211eee..420965163 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -646,6 +646,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { mShorthandParsers["box"] = []( const ShorthandDefinition* shorthand, std::string value ) -> std::vector { value = String::trim( value ); + if ( value.empty() ) + return {}; std::vector properties; const std::vector propNames( shorthand->getProperties() ); if ( propNames.size() != 4 ) { @@ -670,6 +672,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { []( const ShorthandDefinition* shorthand, std::string value ) -> std::vector { value = String::trim( value ); + if ( value.empty() ) + return {}; std::vector properties; const std::vector propNames( shorthand->getProperties() ); for ( auto& prop : propNames ) { @@ -681,6 +685,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { mShorthandParsers["vector2"] = []( const ShorthandDefinition* shorthand, std::string value ) -> std::vector { value = String::trim( value ); + if ( value.empty() ) + return {}; std::vector properties; const std::vector propNames( shorthand->getProperties() ); if ( propNames.size() != 2 ) { @@ -703,6 +709,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { mShorthandParsers["border-box"] = []( const ShorthandDefinition* shorthand, std::string value ) -> std::vector { value = String::trim( value ); + if ( value.empty() ) + return {}; std::vector properties; const std::vector propNames( shorthand->getProperties() ); auto ltrbSplit = String::split( value, " ", "", "(\"" ); @@ -718,6 +726,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { mShorthandParsers["radius"] = []( const ShorthandDefinition* shorthand, std::string value ) -> std::vector { value = String::trim( value ); + if ( value.empty() ) + return {}; std::vector properties; const std::vector propNames( shorthand->getProperties() ); auto splits = String::split( value, '/' ); @@ -742,6 +752,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { []( const ShorthandDefinition* shorthand, std::string value ) -> std::vector { value = String::trim( value ); + if ( value.empty() ) + return {}; std::vector properties; const std::vector propNames( shorthand->getProperties() ); std::vector values = String::split( value, ',' ); @@ -794,6 +806,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { mShorthandParsers["background"] = [&]( const ShorthandDefinition* shorthand, std::string value ) -> std::vector { value = String::trim( value ); + if ( value.empty() ) + return {}; if ( "none" == value ) return {}; std::vector properties; diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index 0046bd42a..fb8db631e 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -396,12 +396,13 @@ void UICodeEditorSplitter::loadAsyncFileFromPath( #endif } -void UICodeEditorSplitter::loadFileFromPathInNewTab( const std::string& path ) { +std::pair +UICodeEditorSplitter::loadFileFromPathInNewTab( const std::string& path ) { auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( mCurEditor ) ); UITabWidget* tabWidget = d.first->getTabWidget(); - UITab* addedTab = d.first; loadFileFromPath( path, d.second ); - tabWidget->setTabSelected( addedTab ); + tabWidget->setTabSelected( d.first ); + return d; } void UICodeEditorSplitter::loadAsyncFileFromPathInNewTab( @@ -738,19 +739,24 @@ bool UICodeEditorSplitter::tryTabClose( UIWidget* widget ) { if ( widget->isType( UI_TYPE_CODEEDITOR ) ) { UICodeEditor* editor = widget->asType(); if ( nullptr != editor && editor->isDirty() ) { - UIMessageBox* msgBox = UIMessageBox::New( + if ( nullptr != mTryCloseMsgBox ) + return false; + mTryCloseMsgBox = UIMessageBox::New( UIMessageBox::OK_CANCEL, - "Do you really want to close this tab?\nAll changes will be lost." ); - msgBox->addEventListener( Event::MsgBoxConfirmClick, - [&, editor]( const Event* ) { closeTab( editor ); } ); - msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { - msgBox = nullptr; + widget->getUISceneNode()->getTranslatorString( + "@string/confirm_close_tab", + "Do you really want to close this tab?\nAll changes will be lost." ) ); + mTryCloseMsgBox->addEventListener( + Event::MsgBoxConfirmClick, [&, editor]( const Event* ) { closeTab( editor ); } ); + mTryCloseMsgBox->addEventListener( Event::OnClose, [&]( const Event* ) { + mTryCloseMsgBox = nullptr; if ( mCurEditor ) mCurEditor->setFocus(); } ); - msgBox->setTitle( "Close Tab?" ); - msgBox->center(); - msgBox->show(); + mTryCloseMsgBox->setTitle( widget->getUISceneNode()->getTranslatorString( + "@string/ask_close_tab", "Close Tab?" ) ); + mTryCloseMsgBox->center(); + mTryCloseMsgBox->show(); return false; } else { closeTab( editor ); diff --git a/src/eepp/ui/uiwidgetcreator.cpp b/src/eepp/ui/uiwidgetcreator.cpp index 1a5955e0a..2e433b0a8 100644 --- a/src/eepp/ui/uiwidgetcreator.cpp +++ b/src/eepp/ui/uiwidgetcreator.cpp @@ -101,6 +101,7 @@ void UIWidgetCreator::createBaseWidgetList() { registeredWidget["hslider"] = UISlider::NewHorizontal; registeredWidget["vscrollbar"] = UIScrollBar::NewVertical; registeredWidget["hscrollbar"] = UIScrollBar::NewHorizontal; + registeredWidget["button"] = UIPushButton::New; sBaseListCreated = true; } diff --git a/src/modules/eterm/src/eterm/terminal/pseudoterminal.cpp b/src/modules/eterm/src/eterm/terminal/pseudoterminal.cpp index 33957fad4..61a631c58 100644 --- a/src/modules/eterm/src/eterm/terminal/pseudoterminal.cpp +++ b/src/modules/eterm/src/eterm/terminal/pseudoterminal.cpp @@ -152,7 +152,7 @@ int PseudoTerminal::read( char* s, size_t n, bool block ) { return 0; } if ( i < 0 ) { - perror( "PseudoTerminal::Reader(poll)" ); + perror( "PseudoTerminal::read(poll)" ); return -1; } } diff --git a/src/modules/eterm/src/eterm/terminal/terminalemulator.cpp b/src/modules/eterm/src/eterm/terminal/terminalemulator.cpp index beee2370c..c5afef8a7 100644 --- a/src/modules/eterm/src/eterm/terminal/terminalemulator.cpp +++ b/src/modules/eterm/src/eterm/terminal/terminalemulator.cpp @@ -2659,7 +2659,7 @@ void TerminalEmulator::resize( int columns, int rows ) { redraw(); } -#define MAX_TTY_READS ( 1024 * 40 ) +#define MAX_TTY_READS ( 1024 ) bool TerminalEmulator::update() { if ( mStatus == TerminalEmulator::STARTING ) { @@ -2669,7 +2669,7 @@ bool TerminalEmulator::update() { } int read = MAX_TTY_READS; - while ( ttyread() > 0 && read--) + while ( ttyread() > 0 && --read ) ; if ( read != MAX_TTY_READS || mDirty ) diff --git a/src/tools/eterm/eterm.cpp b/src/tools/eterm/eterm.cpp index e23f08a56..14e7c552f 100644 --- a/src/tools/eterm/eterm.cpp +++ b/src/tools/eterm/eterm.cpp @@ -4,6 +4,8 @@ EE::Window::Window* win = NULL; std::shared_ptr terminal = nullptr; +Clock lastRender; +Time frameTime{ Time::Zero }; void inputCallback( InputEvent* event ) { if ( !terminal || event->Type == InputEvent::EventsSent ) @@ -81,10 +83,13 @@ void mainLoop() { if ( terminal ) termNeedsUpdate = !terminal->update(); - if ( terminal && terminal->isDirty() ) { - win->clear(); - terminal->draw(); - win->display(); + if ( terminal && terminal->isDirty() && !termNeedsUpdate ) { + if ( lastRender.getElapsedTime() >= frameTime ) { + lastRender.restart(); + win->clear(); + terminal->draw(); + win->display(); + } } else if ( !termNeedsUpdate ) { win->getInput()->waitEvent( Milliseconds( win->hasFocus() ? 16 : 100 ) ); } @@ -117,6 +122,10 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { "close the application when the executable exits", { 'c', "close" } ); args::ValueFlag executeInShell( parser, "execute-in-shell", "execute program in shell", { 'e', "execute" }, "" ); + args::Flag vsync( parser, "vsync", "Enable vsync", { "vsync" } ); + args::ValueFlag maxFPS( parser, "max-fps", + "Maximum rendering frames per second of the terminal", + { "--max-fps" }, 60 ); try { parser.ParseCLI( argc, argv ); @@ -163,7 +172,7 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { WindowBackend::Default, 32, resPath + "icon/ee.png", pixelDenstiyConf ? pixelDenstiyConf.Get() : currentDisplay->getPixelDensity() ), - ContextSettings( false ) ); + ContextSettings( vsync.Get() ) ); if ( win->isOpen() ) { win->setClearColor( RGB( 0, 0, 0 ) ); @@ -189,6 +198,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) { ->loadFromFile( resPath + "fonts/NotoEmoji-Regular.ttf" ); } + frameTime = Milliseconds( 1000.f / (Float)maxFPS.Get() ); + if ( !terminal || terminal->hasTerminated() ) { FileInfo file( wd ? wd.Get() : FileSystem::getCurrentWorkingDirectory() ); terminal = TerminalDisplay::create( diff --git a/src/tools/uieditor/uieditor.cpp b/src/tools/uieditor/uieditor.cpp index a90bce7ee..49f25fa1c 100644 --- a/src/tools/uieditor/uieditor.cpp +++ b/src/tools/uieditor/uieditor.cpp @@ -227,22 +227,38 @@ void App::setUserDefaultTheme() { mUISceneNode->setStyleSheet( mTheme->getStyleSheet() ); } +void App::loadBaseStyleSheet() { + if ( !mUseDefaultTheme ) + return; + + if ( mBaseStyleSheetWatch == 0 ) { + std::string baseFolder( FileSystem::fileRemoveFileName( mBaseStyleSheet ) ); + mBaseStyleSheetWatch = mFileWatcher->addWatch( baseFolder, mListener ); + } + + setUserDefaultTheme(); + + if ( !mSplitter->isDocumentOpen( mBaseStyleSheet ) && mUseDefaultTheme ) { + SceneManager::instance()->setCurrentUISceneNode( mAppUISceneNode ); + mSplitter->loadFileFromPathInNewTab( mBaseStyleSheet ); + SceneManager::instance()->setCurrentUISceneNode( mUISceneNode ); + } +} + void App::loadStyleSheet( std::string cssPath, bool updateCurrentStyleSheet ) { + if ( NULL == mUISceneNode ) + return; CSS::StyleSheetParser parser; - if ( NULL != mUISceneNode && parser.loadFromFile( cssPath ) ) { + loadBaseStyleSheet(); + + if ( NULL != mUISceneNode && !cssPath.empty() && parser.loadFromFile( cssPath ) ) { if ( mUseDefaultTheme ) { - setUserDefaultTheme(); mUISceneNode->combineStyleSheet( parser.getStyleSheet() ); } else { mUISceneNode->setStyleSheet( parser.getStyleSheet() ); } - if ( mBaseStyleSheetWatch == 0 && mUseDefaultTheme ) { - std::string baseFolder( FileSystem::fileRemoveFileName( mBaseStyleSheet ) ); - mBaseStyleSheetWatch = mFileWatcher->addWatch( baseFolder, mListener ); - } - if ( updateCurrentStyleSheet ) { mCurrentStyleSheet = cssPath; @@ -264,8 +280,6 @@ void App::loadStyleSheet( std::string cssPath, bool updateCurrentStyleSheet ) { if ( !mSplitter->isDocumentOpen( cssPath ) ) { SceneManager::instance()->setCurrentUISceneNode( mAppUISceneNode ); - if ( mUseDefaultTheme ) - mSplitter->loadFileFromPathInNewTab( mBaseStyleSheet ); mSplitter->loadFileFromPathInNewTab( cssPath ); SceneManager::instance()->setCurrentUISceneNode( mUISceneNode ); } @@ -273,36 +287,42 @@ void App::loadStyleSheet( std::string cssPath, bool updateCurrentStyleSheet ) { } } -void App::loadLayout( std::string file, bool updateCurrentLayout ) { +void App::tryUpdateWatch( const std::string& file ) { + std::string folder( FileSystem::fileRemoveFileName( file ) ); + bool keepWatch = false; + + for ( auto& directory : mFileWatcher->directories() ) { + if ( directory == folder ) + keepWatch = true; + } + + if ( !keepWatch ) { + if ( mWatch != 0 ) + mFileWatcher->removeWatch( mWatch ); + mWatch = mFileWatcher->addWatch( folder, mListener ); + } +} + +std::pair App::loadLayout( std::string file, bool updateCurrentLayout ) { mUIContainer->getContainer()->childsCloseAll(); mUISceneNode->update( Time::Zero ); mUISceneNode->loadLayoutFromFile( file, mUIContainer ); if ( updateCurrentLayout ) { - std::string folder( FileSystem::fileRemoveFileName( file ) ); - - bool keepWatch = false; - - for ( auto& directory : mFileWatcher->directories() ) { - if ( directory == folder ) - keepWatch = true; - } - - if ( !keepWatch ) { - if ( mWatch != 0 ) - mFileWatcher->removeWatch( mWatch ); - mWatch = mFileWatcher->addWatch( folder, mListener ); - } + tryUpdateWatch( file ); mCurrentLayout = file; if ( !mSplitter->isDocumentOpen( file ) ) { SceneManager::instance()->setCurrentUISceneNode( mAppUISceneNode ); - mSplitter->loadFileFromPathInNewTab( file ); + auto d = mSplitter->loadFileFromPathInNewTab( file ); SceneManager::instance()->setCurrentUISceneNode( mUISceneNode ); + return d; } } + + return std::make_pair( nullptr, nullptr ); } void App::saveTmpDocument( TextDocument& doc, @@ -914,7 +934,7 @@ void App::mainLoop() { mWindow->display(); } else { - Sys::sleep( Milliseconds( 8 ) ); + mWindow->getInput()->waitEvent( Milliseconds( mWindow->hasFocus() ? 16 : 100 ) ); } } @@ -950,6 +970,85 @@ void App::showFileDialog( const String& title, const std::functionshow(); } +String App::i18n( const std::string& key, const String& def ) { + return mUISceneNode->getTranslatorStringFromKey( key, def ); +} + +void App::updateEditorState() { + if ( mSplitter->curEditorExistsAndFocused() ) { + updateEditorTitle( mSplitter->getCurEditor() ); + } +} + +UIFileDialog* App::saveFileDialog( UICodeEditor* editor, bool focusOnClose ) { + if ( !editor ) + return nullptr; + UIFileDialog* dialog = + UIFileDialog::New( UIFileDialog::DefaultFlags | UIFileDialog::SaveDialog, "." ); + dialog->setWinFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL ); + dialog->setTitle( i18n( "save_file_as", "Save File As" ) ); + dialog->setCloseShortcut( KEY_ESCAPE ); + std::string filename( editor->getDocument().getFilename() ); + if ( FileSystem::fileExtension( editor->getDocument().getFilename() ).empty() ) + filename += editor->getSyntaxDefinition().getFileExtension(); + dialog->setFileName( filename ); + dialog->addEventListener( Event::SaveFile, [&, editor]( const Event* event ) { + if ( editor ) { + std::string path( event->getNode()->asType()->getFullPath() ); + if ( !path.empty() && !FileSystem::isDirectory( path ) && + FileSystem::fileCanWrite( FileSystem::fileRemoveFileName( path ) ) ) { + std::string oldPath( editor->getDocument().getFilePath() ); + if ( editor->getDocument().save( path ) ) { + editor->getDocument().setDeleteOnClose( false ); + FileSystem::fileRemove( oldPath ); + if ( mCurrentLayout == oldPath ) + mCurrentLayout = path; + UITab* tab = mSplitter->isDocumentOpen( path ); + if ( tab ) + tab->setTooltipText( editor->getDocument().getFilePath() ); + tryUpdateWatch( path ); + updateEditorState(); + } else { + UIMessageBox* msg = + UIMessageBox::New( UIMessageBox::OK, i18n( "coudlnt_write_the_file", + "Couldn't write the file." ) ); + msg->setTitle( "Error" ); + msg->show(); + } + } else { + UIMessageBox* msg = UIMessageBox::New( + UIMessageBox::OK, + i18n( "empty_file_name", "You must set a name to the file." ) ); + msg->setTitle( "Error" ); + msg->show(); + } + } + } ); + if ( focusOnClose ) { + dialog->addEventListener( Event::OnWindowClose, [&, editor]( const Event* ) { + if ( editor && !SceneManager::instance()->isShootingDown() ) + editor->setFocus(); + } ); + } + dialog->center(); + dialog->show(); + return dialog; +} + +void App::createNewLayout() { + std::string file; + std::string tmpPath( Sys::getTempPath() + "untitled_%d.xml" ); + int i = 0; + do { + file = String::format( tmpPath.c_str(), ++i ); + } while ( FileSystem::fileExists( file ) ); + FileSystem::fileWrite( file, "\n" ); + std::pair d = loadLayout( file ); + if ( !d.first ) + return; + d.second->getDocument().setDeleteOnClose( true ); +} + void App::fileMenuClick( const Event* event ) { if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) return; @@ -958,7 +1057,9 @@ void App::fileMenuClick( const Event* event ) { SceneManager::instance()->setCurrentUISceneNode( mAppUISceneNode ); - if ( "open-project" == id ) { + if ( "new-layout" == id ) { + createNewLayout(); + } else if ( "open-project" == id ) { showFileDialog( "Open project...", [&]( const Event* event ) { projectOpen( event ); }, "*.xml" ); } else if ( "open-layout" == id ) { @@ -993,6 +1094,13 @@ void App::fileMenuClick( const Event* event ) { mUISceneNode->setDrawDebugData( !mUISceneNode->getDrawDebugData() ); } else if ( "inspect-widgets" == id ) { createWidgetTreeView(); + } else if ( "save-doc" == id ) { + saveDoc(); + } else if ( "save-as-doc" == id ) { + if ( mSplitter->curEditorExistsAndFocused() ) + saveFileDialog( mSplitter->getCurEditor() ); + } else if ( "save-all" == id ) { + saveAll(); } SceneManager::instance()->setCurrentUISceneNode( mUISceneNode ); @@ -1007,17 +1115,24 @@ void App::createAppMenu() { mUIMenuBar = mAppUISceneNode->find( "menubar" )->asType(); UIPopUpMenu* uiPopMenu = UIPopUpMenu::New(); - uiPopMenu->add( "Open project...", findIcon( "document-open" ) )->setId( "open-project" ); - uiPopMenu->addSeparator(); + uiPopMenu->add( "New layout", findIcon( "file-add" ) )->setId( "new-layout" ); uiPopMenu->add( "Open layout...", findIcon( "document-open" ) )->setId( "open-layout" ); + uiPopMenu->add( "Open project...", findIcon( "document-open" ) )->setId( "open-project" ); uiPopMenu->addSeparator(); uiPopMenu->addSubMenu( "Recent files", NULL, UIPopUpMenu::New() )->setId( "recent-files" ); uiPopMenu->addSubMenu( "Recent projects", NULL, UIPopUpMenu::New() ) ->setId( "recent-projects" ); uiPopMenu->addSeparator(); + uiPopMenu->add( i18n( "save", "Save" ), findIcon( "document-save" ) )->setId( "save-doc" ); + uiPopMenu->add( i18n( "save_as", "Save as..." ), findIcon( "document-save-as" ) ) + ->setId( "save-as-doc" ); + uiPopMenu->add( i18n( "save_all", "Save All" ), findIcon( "document-save-as" ) ) + ->setId( "save-all" ); + uiPopMenu->addSeparator(); uiPopMenu->add( "Close", findIcon( "document-close" ) )->setId( "close" ); uiPopMenu->addSeparator(); uiPopMenu->add( "Quit", findIcon( "quit" ) )->setId( "quit" ); + mUIMenuBar->addMenuButton( "File", uiPopMenu ); uiPopMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { fileMenuClick( event ); } ); @@ -1292,6 +1407,7 @@ void App::init( const Float& pixelDensityConf, const bool& useAppTheme, const st if ( !cssFile.empty() ) { loadStyleSheet( cssFile ); } else if ( mUseDefaultTheme ) { + loadBaseStyleSheet(); setUserDefaultTheme(); } @@ -1309,6 +1425,10 @@ void App::init( const Float& pixelDensityConf, const bool& useAppTheme, const st } #endif + if ( xmlFile.empty() && projectFile.empty() ) { + createNewLayout(); + } + mWindow->runMainLoop( &appLoop ); } } @@ -1366,15 +1486,65 @@ void App::onDocumentLoaded( UICodeEditor* editor, const std::string& path ) { } } -void App::onCodeEditorCreated( UICodeEditor*, TextDocument& doc ) { - doc.setCommand( "save-doc", [&] { - if ( mSplitter->getCurEditor() ) { - mSplitter->getCurEditor()->save(); - updateEditorTabTitle( mSplitter->getCurEditor() ); +void App::saveAllProcess() { + if ( mTmpDocs.empty() ) + return; + + mSplitter->forEachEditorStoppable( [&]( UICodeEditor* editor ) { + if ( editor->getDocument().isDirty() && + std::find( mTmpDocs.begin(), mTmpDocs.end(), &editor->getDocument() ) != + mTmpDocs.end() ) { + if ( editor->getDocument().hasFilepath() ) { + editor->save(); + updateEditorTabTitle( editor ); + if ( mSplitter->getCurEditor() == editor ) + updateEditorTitle( editor ); + mTmpDocs.erase( &editor->getDocument() ); + } else { + UIFileDialog* dialog = saveFileDialog( editor, false ); + dialog->addEventListener( Event::SaveFile, [&, editor]( const Event* ) { + updateEditorTabTitle( editor ); + if ( mSplitter->getCurEditor() == editor ) + updateEditorTitle( editor ); + } ); + dialog->addEventListener( Event::OnWindowClose, [&, editor]( const Event* ) { + mTmpDocs.erase( &editor->getDocument() ); + if ( !SceneManager::instance()->isShootingDown() && !mTmpDocs.empty() ) + saveAllProcess(); + } ); + return true; + } } + return false; } ); } +void App::saveDoc() { + if ( mSplitter->getCurEditor() ) { + mSplitter->getCurEditor()->save(); + updateEditorTabTitle( mSplitter->getCurEditor() ); + } +} + +void App::saveAll() { + mSplitter->forEachEditor( [&]( UICodeEditor* editor ) { + if ( editor->isDirty() ) + mTmpDocs.insert( &editor->getDocument() ); + } ); + saveAllProcess(); +} + +void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { + editor->setAutoCloseXMLTags( true ); + doc.setCommand( "save-doc", [&] { saveDoc(); } ); + doc.setCommand( "save-as-doc", [&] { + if ( mSplitter->curEditorExistsAndFocused() ) + saveFileDialog( mSplitter->getCurEditor() ); + } ); + doc.setCommand( "save-all", [&] { saveAll(); } ); + doc.setCommand( "create-new", [&] { createNewLayout(); } ); +} + void App::onCodeEditorFocusChange( UICodeEditor* editor ) { std::string ext( FileSystem::fileExtension( editor->getDocument().getFilePath() ) ); if ( ext == "xml" && editor->getDocument().getFilePath() != mCurrentLayout ) diff --git a/src/tools/uieditor/uieditor.hpp b/src/tools/uieditor/uieditor.hpp index 117101592..7cf26b170 100644 --- a/src/tools/uieditor/uieditor.hpp +++ b/src/tools/uieditor/uieditor.hpp @@ -55,7 +55,8 @@ class App : public UICodeEditorSplitter::Client { void loadLayoutsFromFolder( std::string folderPath ); void setUserDefaultTheme(); void loadStyleSheet( std::string cssPath, bool updateCurrentStyleSheet = true ); - void loadLayout( std::string file, bool updateCurrentLayout = true ); + std::pair loadLayout( std::string file, + bool updateCurrentLayout = true ); void refreshLayout(); void refreshStyleSheet(); void onRecentProjectClick( const Event* event ); @@ -129,6 +130,24 @@ class App : public UICodeEditorSplitter::Client { void showEditor( bool show ); + void createNewLayout(); + + UIFileDialog* saveFileDialog( UICodeEditor* editor, bool focusOnClose = true ); + + String i18n( const std::string& key, const String& def ); + + void updateEditorState(); + + void saveDoc(); + + void saveAll(); + + void saveAllProcess(); + + void tryUpdateWatch( const std::string& file ); + + void loadBaseStyleSheet(); + protected: EE::Window::Window* mWindow{ nullptr }; UIMessageBox* mMsgBox{ nullptr }; @@ -179,6 +198,7 @@ class App : public UICodeEditorSplitter::Client { UILayout* mBaseLayout{ nullptr }; UILayout* mPreviewLayout{ nullptr }; UIWidget* mSidePanel{ nullptr }; + std::unordered_set mTmpDocs; Drawable* findIcon( const std::string& icon ); };