#include "codeeditor.hpp" #include App* appInstance = NULL; void appLoop() { appInstance->mainLoop(); } bool App::onCloseRequestCallback( EE::Window::Window* ) { if ( NULL != mCurEditor && mCurEditor->isDirty() ) { mMsgBox = UIMessageBox::New( UIMessageBox::OK_CANCEL, "Do you really want to close the code editor?\nAll changes will be lost." ); mMsgBox->addEventListener( Event::MsgBoxConfirmClick, [&]( const Event* ) { mWindow->close(); } ); mMsgBox->addEventListener( Event::OnClose, [&]( const Event* ) { mMsgBox = NULL; } ); mMsgBox->setTitle( "Close Code Editor?" ); mMsgBox->center(); mMsgBox->show(); return false; } else { return true; } } bool App::tryTabClose( UICodeEditor* editor ) { if ( NULL != editor && editor->isDirty() ) { mMsgBox = UIMessageBox::New( UIMessageBox::OK_CANCEL, "Do you really want to close this tab?\nAll changes will be lost." ); mMsgBox->addEventListener( Event::MsgBoxConfirmClick, [&, editor]( const Event* ) { closeEditorTab( editor ); } ); mMsgBox->addEventListener( Event::OnClose, [&]( const Event* ) { mMsgBox = NULL; if ( mCurEditor ) mCurEditor->setFocus(); } ); mMsgBox->setTitle( "Close Tab?" ); mMsgBox->center(); mMsgBox->show(); return false; } else { closeEditorTab( editor ); return true; } } void App::closeEditorTab( UICodeEditor* editor ) { if ( editor ) { UITabWidget* tabWidget = tabWidgetFromEditor( editor ); if ( tabWidget ) { if ( !( editor->getDocument().isEmpty() && !tabWidget->getParent()->isType( UI_TYPE_SPLITTER ) && tabWidget->getTabCount() == 1 ) ) { tabWidget->removeTab( (UITab*)editor->getData() ); } } } } void App::splitEditor( const SplitDirection& direction, UICodeEditor* editor ) { if ( !editor ) return; UIOrientation orientation = direction == SplitDirection::Left || direction == SplitDirection::Right ? UIOrientation::Horizontal : UIOrientation::Vertical; UITabWidget* tabWidget = tabWidgetFromEditor( editor ); if ( !tabWidget ) return; Node* parent = tabWidget->getParent(); UISplitter* parentSplitter = NULL; bool wasFirst = true; if ( parent->isType( UI_TYPE_SPLITTER ) ) { parentSplitter = parent->asType(); wasFirst = parentSplitter->getFirstWidget() == tabWidget; if ( !parentSplitter->isFull() ) { parentSplitter->setOrientation( orientation ); createEditorWithTabWidget( parentSplitter ); if ( direction == SplitDirection::Left || direction == SplitDirection::Top ) parentSplitter->swap(); return; } } UISplitter* splitter = UISplitter::New(); splitter->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); splitter->setOrientation( orientation ); tabWidget->detach(); splitter->setParent( parent ); tabWidget->setParent( splitter ); createEditorWithTabWidget( splitter ); if ( direction == SplitDirection::Left || direction == SplitDirection::Top ) splitter->swap(); if ( parentSplitter ) { if ( wasFirst && parentSplitter->getFirstWidget() != splitter ) { parentSplitter->swap(); } else if ( !wasFirst && parentSplitter->getLastWidget() != splitter ) { parentSplitter->swap(); } } } void App::switchToTab( Int32 index ) { UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); if ( tabWidget ) { tabWidget->setTabSelected( eeclamp( index, 0, tabWidget->getTabCount() - 1 ) ); } } UITabWidget* App::findPreviousSplit( UICodeEditor* editor ) { if ( !editor ) return NULL; UISplitter* splitter = splitterFromEditor( editor ); if ( !splitter ) return NULL; UITabWidget* tabWidget = tabWidgetFromEditor( editor ); if ( tabWidget ) { auto it = std::find( mTabWidgets.rbegin(), mTabWidgets.rend(), tabWidget ); if ( it != mTabWidgets.rend() && ++it != mTabWidgets.rend() ) { return *it; } } return NULL; } void App::switchPreviousSplit( UICodeEditor* editor ) { UITabWidget* tabWidget = findPreviousSplit( editor ); if ( tabWidget && tabWidget->getTabSelected() && tabWidget->getTabSelected()->getOwnedWidget() ) { tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); } else { tabWidget = findNextSplit( editor ); if ( tabWidget && tabWidget->getTabSelected() && tabWidget->getTabSelected()->getOwnedWidget() ) { tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); } } } UITabWidget* App::findNextSplit( UICodeEditor* editor ) { if ( !editor ) return NULL; UISplitter* splitter = splitterFromEditor( editor ); if ( !splitter ) return NULL; UITabWidget* tabWidget = tabWidgetFromEditor( editor ); if ( tabWidget ) { auto it = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget ); if ( it != mTabWidgets.end() && ++it != mTabWidgets.end() ) { return *it; } } return NULL; } void App::switchNextSplit( UICodeEditor* editor ) { UITabWidget* tabWidget = findNextSplit( editor ); if ( tabWidget && tabWidget->getTabSelected() && tabWidget->getTabSelected()->getOwnedWidget() ) { tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); } else { tabWidget = findPreviousSplit( editor ); if ( tabWidget && tabWidget->getTabSelected() && tabWidget->getTabSelected()->getOwnedWidget() ) { tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); } } } void App::applyColorScheme( const SyntaxColorScheme& colorScheme ) { for ( UITabWidget* tabWidget : mTabWidgets ) { for ( size_t i = 0; i < tabWidget->getTabCount(); i++ ) { tabWidget->getTab( i )->getOwnedWidget()->asType()->setColorScheme( colorScheme ); } } updateColorSchemeMenu(); } void App::saveDoc() { if ( mCurEditor->getDocument().hasFilepath() ) { if ( mCurEditor->save() ) updateEditorState(); } else { saveFileDialog(); } } UICodeEditor* App::createCodeEditor() { UICodeEditor* codeEditor = UICodeEditor::NewOpt( false, true ); codeEditor->setFontSize( 11 ); codeEditor->setEnableColorPickerOnSelection( true ); codeEditor->setColorScheme( mColorSchemes[mCurrentColorScheme] ); TextDocument& doc = codeEditor->getDocument(); /* global commands */ doc.setCommand( "move-to-previous-line", [&] { if ( mCurEditor ) mCurEditor->moveToPreviousLine(); } ); doc.setCommand( "move-to-next-line", [&] { if ( mCurEditor ) mCurEditor->moveToNextLine(); } ); doc.setCommand( "select-to-previous-line", [&] { if ( mCurEditor ) mCurEditor->selectToPreviousLine(); } ); doc.setCommand( "select-to-next-line", [&] { if ( mCurEditor ) mCurEditor->selectToNextLine(); } ); doc.setCommand( "move-scroll-up", [&] { if ( mCurEditor ) mCurEditor->moveScrollUp(); } ); doc.setCommand( "move-scroll-down", [&] { if ( mCurEditor ) mCurEditor->moveScrollDown(); } ); doc.setCommand( "indent", [&] { if ( mCurEditor ) mCurEditor->indent(); } ); doc.setCommand( "unindent", [&] { if ( mCurEditor ) mCurEditor->unindent(); } ); doc.setCommand( "copy", [&] { if ( mCurEditor ) mCurEditor->copy(); } ); doc.setCommand( "cut", [&] { if ( mCurEditor ) mCurEditor->cut(); } ); doc.setCommand( "paste", [&] { if ( mCurEditor ) mCurEditor->paste(); } ); doc.setCommand( "font-size-grow", [&] { if ( mCurEditor ) mCurEditor->fontSizeGrow(); } ); doc.setCommand( "font-size-shrink", [&] { if ( mCurEditor ) mCurEditor->fontSizeShrink(); } ); doc.setCommand( "font-size-reset", [&] { if ( mCurEditor ) mCurEditor->fontSizeReset(); } ); doc.setCommand( "lock", [&] { if ( mCurEditor ) mCurEditor->setLocked( true ); } ); doc.setCommand( "unlock", [&] { if ( mCurEditor ) mCurEditor->setLocked( false ); } ); doc.setCommand( "lock-toggle", [&] { if ( mCurEditor ) mCurEditor->setLocked( !mCurEditor->isLocked() ); } ); codeEditor->addUnlockedCommand( "copy" ); codeEditor->addUnlockedCommand( "select-all" ); /* global commands */ doc.setCommand( "switch-to-previous-colorscheme", [&] { auto it = mColorSchemes.find( mCurrentColorScheme ); auto prev = std::prev( it, 1 ); if ( prev != mColorSchemes.end() ) { setColorScheme( prev->first ); } else { setColorScheme( mColorSchemes.rbegin()->first ); } } ); doc.setCommand( "switch-to-next-colorscheme", [&] { auto it = mColorSchemes.find( mCurrentColorScheme ); if ( ++it != mColorSchemes.end() ) mCurrentColorScheme = it->first; else mCurrentColorScheme = mColorSchemes.begin()->first; applyColorScheme( mColorSchemes[mCurrentColorScheme] ); } ); doc.setCommand( "switch-to-previous-split", [&] { switchPreviousSplit( mCurEditor ); } ); doc.setCommand( "switch-to-next-split", [&] { switchNextSplit( mCurEditor ); } ); doc.setCommand( "save-doc", [&] { saveDoc(); } ); doc.setCommand( "save-as-doc", [&] { saveFileDialog(); } ); doc.setCommand( "find", [&] { showFindView(); } ); doc.setCommand( "repeat-find", [&] { findNextText( "", mSearchBarLayout->find( "case_sensitive" )->isChecked() ); } ); doc.setCommand( "close-app", [&] { closeApp(); } ); doc.setCommand( "fullscreen-toggle", [&]() { mWindow->toggleFullscreen(); } ); doc.setCommand( "open-file", [&] { openFileDialog(); } ); doc.setCommand( "console-toggle", [&] { mConsole->toggle(); bool lock = mConsole->isActive(); for ( auto tabW : mTabWidgets ) { for ( size_t i = 0; i < tabW->getTabCount(); i++ ) { tabW->getTab( i )->getOwnedWidget()->asType()->setLocked( lock ); } } } ); doc.setCommand( "close-doc", [&] { tryTabClose( mCurEditor ); } ); doc.setCommand( "create-new", [&] { auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( mCurEditor ) ); d.first->getTabWidget()->setTabSelected( d.first ); } ); doc.setCommand( "next-doc", [&] { UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); if ( tabWidget && tabWidget->getTabCount() > 1 ) { UITab* tab = (UITab*)mCurEditor->getData(); Uint32 tabIndex = tabWidget->getTabIndex( tab ); switchToTab( ( tabIndex + 1 ) % tabWidget->getTabCount() ); } } ); doc.setCommand( "previous-doc", [&] { UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); if ( tabWidget && tabWidget->getTabCount() > 1 ) { UITab* tab = (UITab*)mCurEditor->getData(); Uint32 tabIndex = tabWidget->getTabIndex( tab ); Int32 newTabIndex = (Int32)tabIndex - 1; switchToTab( newTabIndex < 0 ? tabWidget->getTabCount() - newTabIndex : newTabIndex ); } } ); for ( int i = 1; i <= 10; i++ ) doc.setCommand( String::format( "switch-to-tab-%d", i ), [&, i] { switchToTab( i - 1 ); } ); doc.setCommand( "split-right", [&] { splitEditor( SplitDirection::Right, mCurEditor ); } ); doc.setCommand( "split-bottom", [&] { splitEditor( SplitDirection::Bottom, mCurEditor ); } ); doc.setCommand( "split-left", [&] { splitEditor( SplitDirection::Left, mCurEditor ); } ); doc.setCommand( "split-top", [&] { splitEditor( SplitDirection::Top, mCurEditor ); } ); doc.setCommand( "split-swap", [&] { if ( UISplitter* splitter = splitterFromEditor( mCurEditor ) ) splitter->swap(); } ); codeEditor->addEventListener( Event::OnFocus, [&]( const Event* event ) { mCurEditor = event->getNode()->asType(); updateEditorState(); } ); codeEditor->addEventListener( Event::OnTextChanged, [&]( const Event* event ) { updateEditorTitle( event->getNode()->asType() ); } ); codeEditor->addEventListener( Event::OnSelectionChanged, [&]( const Event* event ) { updateEditorTitle( event->getNode()->asType() ); } ); codeEditor->addKeyBindingString( "f2", "open-file", true ); codeEditor->addKeyBindingString( "f3", "repeat-find", false ); codeEditor->addKeyBindingString( "f12", "console-toggle", true ); codeEditor->addKeyBindingString( "alt+return", "fullscreen-toggle", true ); codeEditor->addKeyBindingString( "alt+keypad enter", "fullscreen-toggle", true ); codeEditor->addKeyBindingString( "ctrl+s", "save-doc", false ); codeEditor->addKeyBindingString( "ctrl+f", "find", false ); codeEditor->addKeyBindingString( "ctrl+q", "close-app", true ); codeEditor->addKeyBindingString( "ctrl+o", "open-file", true ); codeEditor->addKeyBindingString( "ctrl+l", "lock-toggle", true ); codeEditor->addKeyBindingString( "ctrl+t", "create-new", true ); codeEditor->addKeyBindingString( "ctrl+w", "close-doc", true ); codeEditor->addKeyBindingString( "ctrl+tab", "next-doc", true ); codeEditor->addKeyBindingString( "ctrl+shift+tab", "previous-doc", true ); codeEditor->addKeyBindingString( "alt+shift+j", "split-left", true ); codeEditor->addKeyBindingString( "alt+shift+l", "split-right", true ); codeEditor->addKeyBindingString( "alt+shift+i", "split-top", true ); codeEditor->addKeyBindingString( "alt+shift+k", "split-bottom", true ); codeEditor->addKeyBindingString( "alt+shift+s", "split-swap", true ); codeEditor->addKeyBindingString( "ctrl+alt+j", "switch-to-previous-split", true ); codeEditor->addKeyBindingString( "ctrl+alt+l", "switch-to-next-split", true ); codeEditor->addKeyBindingString( "ctrl+alt+n", "switch-to-previous-colorscheme", true ); codeEditor->addKeyBindingString( "ctrl+alt+m", "switch-to-next-colorscheme", true ); for ( int i = 1; i <= 10; i++ ) { codeEditor->addKeyBindingString( String::format( "ctrl+%d", i ), String::format( "switch-to-tab-%d", i ), true ); codeEditor->addKeyBindingString( String::format( "alt+%d", i ), String::format( "switch-to-tab-%d", i ), true ); } if ( NULL == mCurEditor ) { mCurEditor = codeEditor; } return codeEditor; } std::string App::titleFromEditor( UICodeEditor* editor ) { std::string title( editor->getDocument().getFilename() ); return editor->getDocument().isDirty() ? title + "*" : title; } void App::updateEditorTitle( UICodeEditor* editor ) { std::string title( titleFromEditor( editor ) ); if ( editor->getData() ) { UITab* tab = (UITab*)editor->getData(); tab->setText( title ); } setAppTitle( title ); } void App::focusSomeEditor( Node* searchFrom ) { UICodeEditor* editor = searchFrom ? searchFrom->findByType( UI_TYPE_CODEEDITOR ) : mUISceneNode->getRoot()->findByType( UI_TYPE_CODEEDITOR ); if ( searchFrom && !editor ) editor = mUISceneNode->getRoot()->findByType( UI_TYPE_CODEEDITOR ); if ( editor && tabWidgetFromEditor( editor ) && !tabWidgetFromEditor( editor )->isClosing() ) { UITabWidget* tabW = tabWidgetFromEditor( editor ); if ( tabW && tabW->getTabCount() > 0 ) { tabW->setTabSelected( tabW->getTabSelected() ); } } else { UITabWidget* tabW = mUISceneNode->getRoot()->findByType( UI_TYPE_TABWIDGET ); if ( tabW && tabW->getTabCount() > 0 ) { tabW->setTabSelected( tabW->getTabSelected() ); } } } void App::closeTabWidgets( UISplitter* splitter ) { Node* node = splitter->getFirstChild(); while ( node ) { if ( node->isType( UI_TYPE_TABWIDGET ) ) { auto it = std::find( mTabWidgets.begin(), mTabWidgets.end(), node->asType() ); if ( it != mTabWidgets.end() ) { mTabWidgets.erase( it ); } } else if ( node->isType( UI_TYPE_SPLITTER ) ) { closeTabWidgets( node->asType() ); } node = node->getNextNode(); } } void App::addRemainingTabWidgets( Node* widget ) { if ( widget->isType( UI_TYPE_TABWIDGET ) ) { if ( std::find( mTabWidgets.begin(), mTabWidgets.end(), widget->asType() ) == mTabWidgets.end() ) { mTabWidgets.push_back( widget->asType() ); } } else if ( widget->isType( UI_TYPE_SPLITTER ) ) { UISplitter* splitter = widget->asType(); addRemainingTabWidgets( splitter->getFirstWidget() ); addRemainingTabWidgets( splitter->getLastWidget() ); } } void App::closeSplitter( UISplitter* splitter ) { splitter->setParent( mUISceneNode->getRoot() ); splitter->setVisible( false ); splitter->setEnabled( false ); splitter->close(); closeTabWidgets( splitter ); } void App::onTabClosed( const TabEvent* tabEvent ) { UICodeEditor* editor = mCurEditor; if ( tabEvent->getTab()->getOwnedWidget() == mCurEditor ) { mCurEditor = NULL; } UITabWidget* tabWidget = tabEvent->getTab()->getTabWidget(); if ( tabWidget->getTabCount() == 0 ) { UISplitter* splitter = splitterFromEditor( editor ); if ( splitter ) { if ( splitter->isFull() ) { tabWidget->close(); auto itWidget = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget ); if ( itWidget != mTabWidgets.end() ) { mTabWidgets.erase( itWidget ); } // Remove splitter if it's redundant Node* parent = splitter->getParent(); if ( parent->isType( UI_TYPE_SPLITTER ) ) { UISplitter* parentSplitter = parent->asType(); Node* remainingNode = tabWidget == splitter->getFirstWidget() ? splitter->getLastWidget() : splitter->getFirstWidget(); bool wasFirst = parentSplitter->getFirstWidget() == splitter; remainingNode->detach(); closeSplitter( splitter ); remainingNode->setParent( parentSplitter ); addRemainingTabWidgets( remainingNode ); if ( wasFirst ) parentSplitter->swap(); focusSomeEditor( parentSplitter ); } else { // Then this is the main splitter Node* remainingNode = tabWidget == splitter->getFirstWidget() ? splitter->getLastWidget() : splitter->getFirstWidget(); closeSplitter( splitter ); eeASSERT( parent->getChildCount() == 0 ); remainingNode->setParent( parent ); addRemainingTabWidgets( remainingNode ); focusSomeEditor( NULL ); } return; } } auto d = createCodeEditorInTabWidget( tabWidget ); d.first->getTabWidget()->setTabSelected( d.first ); } else { tabWidget->setTabSelected( eemin( tabWidget->getTabCount() - 1, tabEvent->getTabIndex() ) ); } } std::pair App::createCodeEditorInTabWidget( UITabWidget* tabWidget ) { if ( NULL == tabWidget ) return std::make_pair( (UITab*)NULL, (UICodeEditor*)NULL ); UICodeEditor* editor = createCodeEditor(); editor->addEventListener( Event::OnDocumentChanged, [&] (const Event* event) { updateEditorTitle( event->getNode()->asType() ); } ); UITab* tab = tabWidget->add( editor->getDocument().getFilename(), editor ); editor->setData( (UintPtr)tab ); return std::make_pair( tab, editor ); } void App::removeUnusedTab( UITabWidget* tabWidget ) { if ( tabWidget && tabWidget->getTabCount() == 2 && tabWidget->getTab( 0 ) ->getOwnedWidget() ->asType() ->getDocument() .isEmpty() ) { tabWidget->removeTab( (Uint32)0 ); } } UITabWidget* App::createEditorWithTabWidget( Node* parent ) { UICodeEditor* prevCurEditor = mCurEditor; UITabWidget* tabWidget = UITabWidget::New(); tabWidget->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); tabWidget->setParent( parent ); tabWidget->setTabsClosable( true ); tabWidget->setHideTabBarOnSingleTab( true ); tabWidget->setAllowRearrangeTabs( true ); tabWidget->setAllowDragAndDropTabs( true ); tabWidget->addEventListener( Event::OnTabSelected, [&]( const Event* event ) { UITabWidget* tabWidget = event->getNode()->asType(); mCurEditor = tabWidget->getTabSelected()->getOwnedWidget()->asType(); updateEditorState(); } ); tabWidget->setTabTryCloseCallback( [&]( UITab* tab ) -> bool { tryTabClose( tab->getOwnedWidget()->asType() ); return false; } ); tabWidget->addEventListener( Event::OnTabClosed, [&]( const Event* event ) { onTabClosed( static_cast( event ) ); } ); auto editorData = createCodeEditorInTabWidget( tabWidget ); // Open same document in the new split if ( prevCurEditor && prevCurEditor != editorData.second && !prevCurEditor->getDocument().isEmpty() ) editorData.second->setDocument( prevCurEditor->getDocumentRef() ); mTabWidgets.push_back( tabWidget ); return tabWidget; } UITabWidget* App::tabWidgetFromEditor( UICodeEditor* editor ) { if ( editor ) return ( (UITab*)editor->getData() )->getTabWidget(); return NULL; } UISplitter* App::splitterFromEditor( UICodeEditor* editor ) { if ( editor && editor->getParent()->getParent()->getParent()->isType( UI_TYPE_SPLITTER ) ) return editor->getParent()->getParent()->getParent()->asType(); return NULL; } void App::setAppTitle( const std::string& title ) { mWindow->setTitle( mWindowTitle + String( title.empty() ? "" : " - " + title ) ); } bool App::loadFileFromPath( const std::string& path, UICodeEditor* codeEditor ) { if ( FileSystem::isDirectory( path ) ) return false; if ( NULL == codeEditor ) codeEditor = mCurEditor; codeEditor->setColorScheme( mColorSchemes[mCurrentColorScheme] ); bool ret = codeEditor->loadFromFile( path ); updateEditorTitle( codeEditor ); if ( codeEditor == mCurEditor ) updateCurrentFiletype(); removeUnusedTab( tabWidgetFromEditor( codeEditor ) ); return ret; } void App::openFileDialog() { UIFileDialog* dialog = UIFileDialog::New(); dialog->setWinFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL ); dialog->setTitle( "Open File" ); dialog->setCloseShortcut( KEY_ESCAPE ); dialog->addEventListener( Event::OpenFile, [&]( const Event* event ) { auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( mCurEditor ) ); UITabWidget* tabWidget = d.first->getTabWidget(); UITab* addedTab = d.first; loadFileFromPath( event->getNode()->asType()->getFullPath(), d.second ); tabWidget->setTabSelected( addedTab ); } ); dialog->addEventListener( Event::OnWindowClose, [&]( const Event* ) { if ( mCurEditor && !SceneManager::instance()->isShootingDown() ) mCurEditor->setFocus(); } ); dialog->center(); dialog->show(); } void App::saveFileDialog() { UIFileDialog* dialog = UIFileDialog::New( UIFileDialog::DefaultFlags | UIFileDialog::SaveDialog ); dialog->setWinFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL ); dialog->setTitle( "Save File As" ); dialog->setCloseShortcut( KEY_ESCAPE ); std::string filename( mCurEditor->getDocument().getFilename() ); if ( FileSystem::fileExtension( mCurEditor->getDocument().getFilename() ).empty() ) filename += mCurEditor->getSyntaxDefinition().getFileExtension(); dialog->setFileName( filename ); dialog->addEventListener( Event::SaveFile, [&]( const Event* event ) { if ( mCurEditor ) { std::string path( event->getNode()->asType()->getFullPath() ); if ( !path.empty() && !FileSystem::isDirectory( path ) && FileSystem::fileCanWrite( FileSystem::fileRemoveFileName( path ) ) ) { if ( mCurEditor->getDocument().save( path ) ) { updateEditorState(); } else { UIMessageBox* msg = UIMessageBox::New( UIMessageBox::OK, "Couldn't write the file." ); msg->setTitle( "Error" ); msg->show(); } } else { UIMessageBox* msg = UIMessageBox::New( UIMessageBox::OK, "You must set a name to the file." ); msg->setTitle( "Error" ); msg->show(); } } } ); dialog->addEventListener( Event::OnWindowClose, [&]( const Event* ) { if ( mCurEditor && !SceneManager::instance()->isShootingDown() ) mCurEditor->setFocus(); } ); dialog->center(); dialog->show(); } void App::findPrevText( String text, const bool& caseSensitive ) { if ( text.empty() ) text = mLastSearch; if ( !mCurEditor || text.empty() ) return; mLastSearch = text; TextDocument& doc = mCurEditor->getDocument(); TextPosition found = doc.findLast( text, doc.getSelection( true ).start(), caseSensitive ); if ( found.isValid() ) { doc.setSelection( {doc.positionOffset( found, text.size() ), found} ); } else { found = doc.findLast( text, doc.endOfDoc() ); if ( found.isValid() ) { doc.setSelection( {doc.positionOffset( found, text.size() ), found} ); } } } void App::findNextText( String text, const bool& caseSensitive ) { if ( text.empty() ) text = mLastSearch; if ( !mCurEditor || text.empty() ) return; mLastSearch = text; TextDocument& doc = mCurEditor->getDocument(); TextPosition found = doc.find( text, doc.getSelection( true ).end(), caseSensitive ); if ( found.isValid() ) { doc.setSelection( {doc.positionOffset( found, text.size() ), found} ); } else { found = doc.find( text, {0, 0} ); if ( found.isValid() ) { doc.setSelection( {doc.positionOffset( found, text.size() ), found} ); } } } void App::replaceSelection( const String& replacement ) { if ( !mCurEditor || !mCurEditor->getDocument().hasSelection() ) return; mCurEditor->getDocument().replaceSelection( replacement ); } void App::replaceAll( String find, const String& replace, const bool& caseSensitive ) { if ( !mCurEditor ) return; if ( find.empty() ) find = mLastSearch; if ( !mCurEditor || find.empty() ) return; mLastSearch = find; TextDocument& doc = mCurEditor->getDocument(); TextPosition found; TextPosition startedPosition = doc.getSelection().start(); doc.setSelection( doc.startOfDoc() ); do { found = doc.find( find, doc.getSelection( true ).end(), caseSensitive ); if ( found.isValid() ) { doc.setSelection( {doc.positionOffset( found, find.size() ), found} ); doc.replaceSelection( replace ); } } while ( found.isValid() ); doc.setSelection( startedPosition ); } void App::findAndReplace( String find, String replace, const bool& caseSensitive ) { if ( find.empty() ) find = mLastSearch; if ( !mCurEditor || find.empty() ) return; mLastSearch = find; TextDocument& doc = mCurEditor->getDocument(); if ( doc.hasSelection() && doc.getSelectedText() == find ) { replaceSelection( replace ); } else { findNextText( find, caseSensitive ); } } void App::runCommand( const std::string& command ) { if ( mCurEditor ) { mCurEditor->getDocument().execute( command ); } } void App::initSearchBar() { auto addClickListener = [&]( UIWidget* widget, std::string cmd ) { widget->addEventListener( Event::MouseClick, [this, cmd]( const Event* event ) { const MouseEvent* mouseEvent = static_cast( event ); if ( mouseEvent->getFlags() & EE_BUTTON_LMASK ) mSearchBarLayout->execute( cmd ); } ); }; auto addReturnListener = [&]( UIWidget* widget, std::string cmd ) { widget->addEventListener( Event::OnPressEnter, [this, cmd]( const Event* ) { mSearchBarLayout->execute( cmd ); } ); }; UITextInput* findInput = mSearchBarLayout->find( "search_find" ); UITextInput* replaceInput = mSearchBarLayout->find( "search_replace" ); UICheckBox* caseSensitiveChk = mSearchBarLayout->find( "case_sensitive" ); findInput->addEventListener( Event::OnTextChanged, [&, findInput, caseSensitiveChk]( const Event* ) { if ( mCurEditor ) { if ( !findInput->getText().empty() ) { mCurEditor->getDocument().setSelection( mCurEditor->getDocument().startOfDoc() ); findNextText( findInput->getText(), caseSensitiveChk->isChecked() ); } else { mCurEditor->getDocument().setSelection( mCurEditor->getDocument().getSelection().start() ); } } } ); mSearchBarLayout->addCommand( "close-searchbar", [&] { mSearchBarLayout->setEnabled( false )->setVisible( false ); mCurEditor->setFocus(); } ); mSearchBarLayout->addCommand( "repeat-find", [this, findInput, caseSensitiveChk] { findNextText( findInput->getText(), caseSensitiveChk->isChecked() ); } ); mSearchBarLayout->addCommand( "replace-all", [this, findInput, replaceInput, caseSensitiveChk] { replaceAll( findInput->getText(), replaceInput->getText(), caseSensitiveChk->isChecked() ); } ); mSearchBarLayout->addCommand( "find-and-replace", [this, findInput, replaceInput, caseSensitiveChk] { findAndReplace( findInput->getText(), replaceInput->getText(), caseSensitiveChk->isChecked() ); } ); mSearchBarLayout->addCommand( "find-prev", [this, findInput, caseSensitiveChk] { findPrevText( findInput->getText(), caseSensitiveChk->isChecked() ); } ); mSearchBarLayout->addCommand( "replace-selection", [this, replaceInput] { replaceSelection( replaceInput->getText() ); } ); mSearchBarLayout->addCommand( "change-case", [caseSensitiveChk] { caseSensitiveChk->setChecked( !caseSensitiveChk->isChecked() ); } ); mSearchBarLayout->getKeyBindings().addKeybindsString( { {"f3", "repeat-find"}, {"ctrl+g", "repeat-find"}, {"escape", "close-searchbar"}, {"ctrl+r", "replace-all"}, {"ctrl+s", "change-case"}, } ); addReturnListener( findInput, "repeat-find" ); addReturnListener( replaceInput, "find-and-replace" ); addClickListener( mSearchBarLayout->find( "find_prev" ), "find-prev" ); addClickListener( mSearchBarLayout->find( "find_next" ), "repeat-find" ); addClickListener( mSearchBarLayout->find( "replace" ), "replace-selection" ); addClickListener( mSearchBarLayout->find( "replace_find" ), "find-and-replace" ); addClickListener( mSearchBarLayout->find( "replace_all" ), "replace-all" ); addClickListener( mSearchBarLayout->find( "searchbar_close" ), "close-searchbar" ); replaceInput->addEventListener( Event::OnTabNavigate, [findInput]( const Event* ) { findInput->setFocus(); } ); } void App::showFindView() { if ( !mCurEditor ) return; mSearchBarLayout->setEnabled( true )->setVisible( true ); UITextInput* findInput = mSearchBarLayout->find( "search_find" ); findInput->setFocus(); String text = mCurEditor->getDocument().getSelectedText(); if ( !text.empty() ) { findInput->setText( text ); findInput->getDocument().selectAll(); } else if ( !findInput->getText().empty() ) { findInput->getDocument().selectAll(); } mCurEditor->getDocument().setActiveClient( mCurEditor ); } void App::closeApp() { if ( NULL == mMsgBox && onCloseRequestCallback( mWindow ) ) { mWindow->close(); } } void App::mainLoop() { Input* input = mWindow->getInput(); input->update(); if ( input->isKeyUp( KEY_F6 ) ) { mUISceneNode->setHighlightFocus( !mUISceneNode->getHighlightFocus() ); mUISceneNode->setHighlightOver( !mUISceneNode->getHighlightOver() ); } if ( input->isKeyUp( KEY_F7 ) ) { mUISceneNode->setDrawBoxes( !mUISceneNode->getDrawBoxes() ); } if ( input->isKeyUp( KEY_F8 ) ) { mUISceneNode->setDrawDebugData( !mUISceneNode->getDrawDebugData() ); } Time elapsed = SceneManager::instance()->getElapsed(); SceneManager::instance()->update(); if ( SceneManager::instance()->getUISceneNode()->invalidated() || mConsole->isActive() || mConsole->isFading() ) { mWindow->clear(); SceneManager::instance()->draw(); mConsole->draw( elapsed ); mWindow->display(); } else { Sys::sleep( Milliseconds( mWindow->hasFocus() ? 1 : 16 ) ); } } void App::onFileDropped( String file ) { Vector2f mousePos( mUISceneNode->getEventDispatcher()->getMousePosf() ); Node* node = mUISceneNode->overFind( mousePos ); UICodeEditor* codeEditor = mCurEditor; if ( !node ) node = codeEditor; if ( node && node->isType( UI_TYPE_CODEEDITOR ) ) { codeEditor = node->asType(); if ( !codeEditor->getDocument().isEmpty() ) { auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( codeEditor ) ); codeEditor = d.second; d.first->getTabWidget()->setTabSelected( d.first ); } } loadFileFromPath( file, codeEditor ); } void App::onTextDropped( String text ) { Vector2f mousePos( mUISceneNode->getEventDispatcher()->getMousePosf() ); Node* node = mUISceneNode->overFind( mousePos ); UICodeEditor* codeEditor = mCurEditor; if ( node && node->isType( UI_TYPE_CODEEDITOR ) ) codeEditor = node->asType(); if ( codeEditor && !text.empty() ) { if ( text[text.size() - 1] != '\n' ) text += '\n'; codeEditor->getDocument().textInput( text ); } } App::~App() { eeSAFE_DELETE( mConsole ); } void App::createSettingsMenu() { mSettingsMenu = UIPopUpMenu::New(); mSettingsMenu->add( "New", mUISceneNode->findIcon( "document-new" ), "Ctrl+T" ); mSettingsMenu->add( "Open...", mUISceneNode->findIcon( "document-open" ), "Ctrl+O" ); mSettingsMenu->addSeparator(); mSettingsMenu->add( "Save", mUISceneNode->findIcon( "document-save" ), "Ctrl+S" ); mSettingsMenu->add( "Save as...", mUISceneNode->findIcon( "document-save-as" ) ); mSettingsMenu->addSeparator(); mSettingsMenu->addSubMenu( "Filetype", NULL, createFiletypeMenu() ); mSettingsMenu->addSubMenu( "Color Scheme", NULL, createColorSchemeMenu() ); mSettingsMenu->addSeparator(); mSettingsMenu->add( "Close", mUISceneNode->findIcon( "document-close" ), "Ctrl+W" ); mSettingsMenu->addSeparator(); mSettingsMenu->add( "Quit", mUISceneNode->findIcon( "quit" ), "Ctrl+Q" ); mSettingsButton = mUISceneNode->find( "settings" ); mSettingsButton->addEventListener( Event::MouseClick, [&]( const Event* ) { Vector2f pos( mSettingsButton->getPixelsPosition() ); mSettingsButton->nodeToWorldTranslation( pos ); UIMenu::fixMenuPos( pos, mSettingsMenu ); mSettingsMenu->setPixelsPosition( pos ); mSettingsMenu->show(); } ); mSettingsMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) ) return; const String& name = event->getNode()->asType()->getText(); if ( name == "New" ) { runCommand( "create-new" ); } else if ( name == "Open..." ) { runCommand( "open-file" ); } else if ( name == "Save" ) { runCommand( "save-doc" ); } else if ( name == "Save as..." ) { runCommand( "save-as-doc" ); } else if ( name == "Close" ) { runCommand( "close-doc" ); } else if ( name == "Quit" ) { runCommand( "close-app" ); } } ); } void App::setColorScheme( const std::string& name ) { if ( name != mCurrentColorScheme ) { mCurrentColorScheme = name; applyColorScheme( mColorSchemes[mCurrentColorScheme] ); } } void App::updateColorSchemeMenu() { for ( size_t i = 0; i < mColorSchemeMenu->getCount(); i++ ) { UIMenuRadioButton* menuItem = mColorSchemeMenu->getItem( i )->asType(); menuItem->setActive( mCurrentColorScheme == menuItem->getText() ); } } UIMenu* App::createColorSchemeMenu() { mColorSchemeMenu = UIPopUpMenu::New(); for ( auto& colorScheme : mColorSchemes ) { mColorSchemeMenu->addRadioButton( colorScheme.first, mCurrentColorScheme == colorScheme.first ); } mColorSchemeMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { UIMenuItem* item = event->getNode()->asType(); const String& name = item->getText(); setColorScheme( name ); } ); return mColorSchemeMenu; } UIMenu* App::createFiletypeMenu() { auto* dM = SyntaxDefinitionManager::instance(); mFiletypeMenu = UIPopUpMenu::New(); auto names = dM->getLanguageNames(); for ( auto& name : names ) { mFiletypeMenu->addRadioButton( name, mCurEditor && mCurEditor->getSyntaxDefinition().getLanguageName() == name ); } mFiletypeMenu->addEventListener( Event::OnItemClicked, [&, dM]( const Event* event ) { UIMenuItem* item = event->getNode()->asType(); const String& name = item->getText(); if ( mCurEditor ) { mCurEditor->setSyntaxDefinition( dM->getStyleByLanguageName( name ) ); updateCurrentFiletype(); } } ); return mFiletypeMenu; } void App::updateCurrentFiletype() { if ( !mCurEditor ) return; std::string curLang( mCurEditor->getSyntaxDefinition().getLanguageName() ); for ( size_t i = 0; i < mFiletypeMenu->getCount(); i++ ) { UIMenuRadioButton* menuItem = mFiletypeMenu->getItem( i )->asType(); std::string itemLang( menuItem->getText() ); menuItem->setActive( curLang == itemLang ); } } void App::updateEditorState() { updateEditorTitle( mCurEditor ); updateCurrentFiletype(); } void App::init( const std::string& file, const Float& pidelDensity ) { DisplayManager* displayManager = Engine::instance()->getDisplayManager(); Display* currentDisplay = displayManager->getDisplayIndex( 0 ); Float pixelDensity = pidelDensity > 0 ? pidelDensity : currentDisplay->getPixelDensity(); displayManager->enableScreenSaver(); displayManager->enableMouseFocusClickThrough(); displayManager->disableBypassCompositor(); std::string resPath( Sys::getProcessPath() ); mWindow = Engine::instance()->createWindow( WindowSettings( 1280, 720, mWindowTitle, WindowStyle::Default, WindowBackend::Default, 32, resPath + "assets/icon/ee.png", pixelDensity ), ContextSettings( true ) ); if ( mWindow->isOpen() ) { mWindow->setCloseRequestCallback( [&]( EE::Window::Window* win ) -> bool { return onCloseRequestCallback( win ); } ); mWindow->getInput()->pushCallback( [&]( InputEvent* event ) { if ( event->Type == InputEvent::FileDropped ) { onFileDropped( event->file.file ); } else if ( event->Type == InputEvent::TextDropped ) { onTextDropped( event->textdrop.text ); } } ); PixelDensity::setPixelDensity( eemax( mWindow->getScale(), pixelDensity ) ); mUISceneNode = UISceneNode::New(); FontTrueType* font = FontTrueType::New( "NotoSans-Regular", resPath + "assets/fonts/NotoSans-Regular.ttf" ); FontTrueType* fontMono = FontTrueType::New( "monospace", resPath + "assets/fonts/DejaVuSansMono.ttf" ); fontMono->setBoldAdvanceSameAsRegular( true ); FontTrueType* iconFont = FontTrueType::New( "icon", resPath + "assets/fonts/remixicon.ttf" ); SceneManager::instance()->add( mUISceneNode ); mTheme = UITheme::load( "uitheme", "uitheme", "", font, resPath + "assets/ui/breeze.css" ); mUISceneNode->setStyleSheet( mTheme->getStyleSheet() ); mUISceneNode ->getUIThemeManager() //->setDefaultEffectsEnabled( true ) ->setDefaultTheme( mTheme ) ->setDefaultFont( font ) ->add( mTheme ); auto colorSchemes = SyntaxColorScheme::loadFromFile( resPath + "assets/colorschemes/colorschemes.conf" ); if ( !colorSchemes.empty() ) { mCurrentColorScheme = colorSchemes[0].getName(); for ( auto& colorScheme : colorSchemes ) mColorSchemes[colorScheme.getName()] = colorScheme; } else { mColorSchemes["default"] = SyntaxColorScheme::getDefault(); mCurrentColorScheme = "default"; } mUISceneNode->getRoot()->addClass( "appbackground" ); const std::string baseUI = R"xml( )xml"; UIWidgetCreator::registerWidget( "searchbar", [] { return UISearchBar::New(); } ); mUISceneNode->loadLayoutFromString( baseUI ); mUISceneNode->bind( "code_container", mBaseLayout ); mUISceneNode->bind( "search_bar", mSearchBarLayout ); mSearchBarLayout->setVisible( false )->setEnabled( false ); UIIconTheme* iconTheme = UIIconTheme::New( "remixicon" ); auto addIcon = [iconTheme, iconFont]( const std::string& name, const Uint32& codePoint, const Uint32& size ) { iconTheme->add( name, iconFont->getGlyphDrawable( codePoint, PixelDensity::dpToPx( size ) ) ); }; addIcon( "go-up", 0xea78, 16 ); addIcon( "ok", 0xeb7a, 16 ); addIcon( "cancel", 0xeb98, 16 ); addIcon( "document-new", 0xecc3, 12 ); addIcon( "document-open", 0xed70, 12 ); addIcon( "document-save", 0xf0b3, 12 ); addIcon( "document-save-as", 0xf0b3, 12 ); addIcon( "document-close", 0xeb99, 12 ); addIcon( "quit", 0xeb97, 12 ); mUISceneNode->getUIIconThemeManager()->setCurrentTheme( iconTheme ); initSearchBar(); createSettingsMenu(); createEditorWithTabWidget( mBaseLayout ); if ( !file.empty() ) { loadFileFromPath( file ); } mConsole = eeNew( Console, ( fontMono, true, true, 1024 * 1000, 0, mWindow ) ); mWindow->runMainLoop( &appLoop ); } } EE_MAIN_FUNC int main( int argc, char* argv[] ) { args::ArgumentParser parser( "ecode" ); args::HelpFlag help( parser, "help", "Display this help menu", {'h', "help"} ); args::Positional file( parser, "file", "The file path" ); args::ValueFlag pixelDenstiyConf( parser, "pixel-density", "Set default application pixel density", {'d', "pixel-density"} ); try { parser.ParseCLI( argc, argv ); } catch ( const args::Help& ) { std::cout << parser; return EXIT_SUCCESS; } catch ( const args::ParseError& e ) { std::cerr << e.what() << std::endl; std::cerr << parser; return EXIT_FAILURE; } catch ( args::ValidationError& e ) { std::cerr << e.what() << std::endl; std::cerr << parser; return EXIT_FAILURE; } appInstance = eeNew( App, () ); appInstance->init( file.Get(), pixelDenstiyConf ? pixelDenstiyConf.Get() : 0.f ); eeSAFE_DELETE( appInstance ); Engine::destroySingleton(); MemoryManager::showResults(); return EXIT_SUCCESS; }