From 0da3ffca17b9a0c72eeb997acdd8ad754cc1410c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 7 Feb 2025 00:09:32 -0300 Subject: [PATCH] Add flash cursor command and functionality. --- include/eepp/ui/uicodeeditor.hpp | 13 +++++- src/eepp/ui/tools/uicodeeditorsplitter.cpp | 43 ++++++++++---------- src/eepp/ui/uicodeeditor.cpp | 47 +++++++++++++++++++++- src/eepp/ui/uipushbutton.cpp | 6 +-- src/tools/ecode/appconfig.cpp | 2 + src/tools/ecode/appconfig.hpp | 1 + src/tools/ecode/ecode.cpp | 1 + src/tools/ecode/settingsmenu.cpp | 13 ++++++ 8 files changed, 97 insertions(+), 29 deletions(-) diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 40b59a143..fbc681e84 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -779,6 +779,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Float getPluginsGutterSpace() const; + void setEnableFlashCursor( bool enable ) { mEnableFlashCursor = enable; } + + bool isEnabledFlashCursor() { return mEnableFlashCursor; } + protected: struct LastXOffset { TextPosition position{ 0, 0 }; @@ -826,10 +830,11 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool mFoldsAlwaysVisible{ false }; bool mFoldsVisible{ false }; bool mFoldsIsFirst{ true }; + bool mEnableFlashCursor{ false }; + Uint32 mTabWidth; std::atomic mHighlightWordProcessing{ false }; TextRange mLinkPosition; String mLink; - Uint32 mTabWidth; Vector2f mScroll; Float mMouseWheelScroll; Float mFontSize; @@ -854,6 +859,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Color mMinimapHoverColor; Color mMinimapSelectionColor; Color mMinimapHighlightColor; + Color mPreviewColor; SyntaxColorScheme mColorScheme; UIScrollBar* mVScrollBar; UIScrollBar* mHScrollBar; @@ -872,7 +878,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { TextRanges mHighlightWordCache; Mutex mHighlightWordCacheMutex; TextRange mHighlightTextRange; - Color mPreviewColor; TextRange mPreviewColorRange; std::vector mPlugins; UILoader* mLoader{ nullptr }; @@ -903,6 +908,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { String::HashType mTagFoldRange{ 0 }; Uint32 mTabIndentCharacter{ 187 /*'ยป'*/ }; CharacterAlignment mTabIndentAlignment{ CharacterAlignment::Center }; + Uint32 mModDownCount{ 0 }; + Clock mModDownClock; UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands = true, const bool& autoRegisterBaseKeybindings = true ); @@ -1104,6 +1111,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void refreshTag(); bool isNotMonospace() const; + + void flashCursor(); }; }} // namespace EE::UI diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index bb7f79d95..bcfb5ed29 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -244,24 +244,24 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() { registerSplitterCommands( doc ); /* Splitter commands */ - editor->addEventListener( Event::OnFocus, [this]( const Event* event ) { + editor->on( Event::OnFocus, [this]( const Event* event ) { setCurrentWidget( event->getNode()->asType() ); } ); - editor->addEventListener( Event::OnTextChanged, [this]( const Event* event ) { + editor->on( Event::OnTextChanged, [this]( const Event* event ) { mClient->onDocumentModified( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); - editor->addEventListener( Event::OnSelectionChanged, [this]( const Event* event ) { + editor->on( Event::OnSelectionChanged, [this]( const Event* event ) { mClient->onDocumentSelectionChange( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); - editor->addEventListener( Event::OnCursorPosChange, [this]( const Event* event ) { + editor->on( Event::OnCursorPosChange, [this]( const Event* event ) { mClient->onDocumentCursorPosChange( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); - editor->addEventListener( Event::OnDocumentUndoRedo, [this]( const Event* event ) { + editor->on( Event::OnDocumentUndoRedo, [this]( const Event* event ) { mClient->onDocumentUndoRedo( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); @@ -526,7 +526,7 @@ UICodeEditorSplitter::createCodeEditorInTabWidget( UITabWidget* tabWidget ) { return std::make_pair( (UITab*)nullptr, (UICodeEditor*)nullptr ); UICodeEditor* editor = createCodeEditor(); mAboutToAddEditor = editor; - editor->addEventListener( Event::OnDocumentChanged, [this]( const Event* event ) { + editor->on( Event::OnDocumentChanged, [this]( const Event* event ) { mClient->onDocumentStateChanged( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); @@ -568,10 +568,10 @@ UICodeEditorSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget* return std::make_pair( (UITab*)nullptr, (UIWidget*)nullptr ); UITab* tab = tabWidget->add( tabName, widget ); widget->setData( (UintPtr)tab ); - widget->addEventListener( Event::OnFocus, [this]( const Event* event ) { + widget->on( Event::OnFocus, [this]( const Event* event ) { setCurrentWidget( event->getNode()->asType() ); } ); - widget->addEventListener( Event::OnTitleChange, [this]( const Event* event ) { + widget->on( Event::OnTitleChange, [this]( const Event* event ) { const TextEvent* tevent = static_cast( event ); UIWidget* widget = event->getNode()->asType(); UITabWidget* tabWidget = tabWidgetFromWidget( widget ); @@ -644,7 +644,7 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) { }, mVisualSplitEdgePercent ); } - tabWidget->addEventListener( Event::OnTabSelected, [this]( const Event* event ) { + tabWidget->on( Event::OnTabSelected, [this]( const Event* event ) { UITabWidget* tabWidget = event->getNode()->asType(); if ( tabWidget->getTabSelected()->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) { setCurrentEditor( @@ -661,7 +661,7 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) { } return true; } ); - tabWidget->addEventListener( Event::OnTabClosed, [this]( const Event* event ) { + tabWidget->on( Event::OnTabClosed, [this]( const Event* event ) { onTabClosed( static_cast( event ) ); } ); if ( mOnTabWidgetCreateCb ) @@ -1007,18 +1007,17 @@ bool UICodeEditorSplitter::tryTabClose( UIWidget* widget, "\"%s\" will be lost." ) .toUtf8(), editor->getDocument().getFilename() ) ); - mTryCloseMsgBox->addEventListener( Event::OnConfirm, - [this, editor, focusTabBehavior]( const Event* ) { - closeTab( editor, focusTabBehavior ); - } ); - mTryCloseMsgBox->addEventListener( Event::OnClose, - [this, onMsgBoxCloseCb]( const Event* ) { - mTryCloseMsgBox = nullptr; - if ( mCurEditor ) - mCurEditor->setFocus(); - if ( onMsgBoxCloseCb ) - onMsgBoxCloseCb(); - } ); + mTryCloseMsgBox->on( Event::OnConfirm, + [this, editor, focusTabBehavior]( const Event* ) { + closeTab( editor, focusTabBehavior ); + } ); + mTryCloseMsgBox->on( Event::OnClose, [this, onMsgBoxCloseCb]( const Event* ) { + mTryCloseMsgBox = nullptr; + if ( mCurEditor ) + mCurEditor->setFocus(); + if ( onMsgBoxCloseCb ) + onMsgBoxCloseCb(); + } ); mTryCloseMsgBox->setTitle( widget->getUISceneNode()->i18n( "ask_close_tab", "Close Tab?" ) ); mTryCloseMsgBox->center(); diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 02d488362..3d19ebf97 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -128,9 +131,9 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis mLineNumberPaddingLeft( PixelDensity::dpToPx( 6 ) ), mLineNumberPaddingRight( PixelDensity::dpToPx( 6 ) ), mFoldRegionWidth( PixelDensity::dpToPx( 12 ) ), + mPreviewColor( Color::Transparent ), mKeyBindings( getUISceneNode()->getWindow()->getInput() ), - mFindLongestLineWidthUpdateFrequency( Seconds( 1 ) ), - mPreviewColor( Color::Transparent ) { + mFindLongestLineWidthUpdateFrequency( Seconds( 1 ) ) { mFlags |= UI_TAB_STOP | UI_OWNS_CHILDS_POSITION | UI_SCROLLABLE; setTextSelection( true ); setColorScheme( SyntaxColorScheme::getDefault() ); @@ -1107,6 +1110,28 @@ Uint32 UICodeEditor::onTextEditing( const TextEditingEvent& event ) { return 1; } +void UICodeEditor::flashCursor() { + Vector2f screenStart( getScreenStart() ); + Vector2f start( screenStart.x + getGutterWidth(), screenStart.y + getPluginsTopSpace() ); + Vector2f startScroll( start - mScroll ); + auto offset = getTextPositionOffset( mDoc->getSelection().start(), getLineHeight() ); + Vector2f cursorPos( startScroll.x + offset.x - getFontHeight() * 0.5f, + startScroll.y + offset.y ); + UIWidget* widget = UIWidget::New(); + widget->setBorderColor( Color( mCaretColor ).blendAlpha( 100 ).blendAlpha( mAlpha ) ); + widget->setPixelsPosition( cursorPos.floor() ); + widget->setBorderWidth( PixelDensity::dpToPx( 2 ) ); + widget->setPixelsSize( Sizef( getFontHeight(), getFontHeight() ) ); + widget->setEnabled( false ); + + Float scale = eemax( getUISceneNode()->getPixelsSize().getWidth() / getFontHeight(), + getUISceneNode()->getPixelsSize().getHeight() / getFontHeight() ); + + widget->runAction( Actions::Sequence::New( + Actions::Scale::New( { scale, scale }, { 1, 1 }, Milliseconds( 250 ), Ease::Linear ), + Actions::Close::New() ) ); +} + Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { if ( getUISceneNode()->getWindow()->getIME().isEditing() ) return 0; @@ -1129,6 +1154,23 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { return 1; } } + + if ( isEnabledFlashCursor() && event.getSanitizedMod() == KeyMod::getDefaultModifier() ) { + if ( mModDownCount == 0 ) + mModDownClock.restart(); + + if ( mModDownClock.getElapsedTime() < Milliseconds( 250 ) ) { + mModDownCount++; + if ( mModDownCount == 5 ) { + mModDownCount = 0; + flashCursor(); + } + } else + mModDownCount = 0; + + mModDownClock.restart(); + } + return 0; } @@ -4221,6 +4263,7 @@ void UICodeEditor::registerCommands() { mDoc->setCommand( "copy-file-path-and-position", [this] { copyFilePath( true ); } ); mDoc->setCommand( "find-replace", [this] { showFindReplace(); } ); mDoc->setCommand( "open-context-menu", [this] { createContextMenu(); } ); + mDoc->setCommand( "flash-cursor", [this] { flashCursor(); } ); mUnlockedCmd.insert( { "copy", "select-all", "open-containing-folder", "copy-containing-folder-path", "copy-file-path", "copy-file-path-and-position", "open-context-menu", "find-replace" } ); diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index 5ed5b8765..5ab438f18 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -449,15 +449,15 @@ void UIPushButton::onAlignChange() { mTextBox->setVerticalAlign( getVerticalAlign() ); } -Uint32 UIPushButton::onKeyDown( const KeyEvent& Event ) { - if ( Event.getKeyCode() == KEY_RETURN ) { +Uint32 UIPushButton::onKeyDown( const KeyEvent& event ) { + if ( event.getKeyCode() == KEY_RETURN || event.getKeyCode() == Window::KEY_KP_ENTER ) { NodeMessage Msg( this, NodeMessage::MouseClick, EE_BUTTON_LMASK ); messagePost( &Msg ); onMouseClick( Vector2i( 0, 0 ), EE_BUTTON_LMASK ); pushState( UIState::StatePressed ); } - return UIWidget::onKeyDown( Event ); + return UIWidget::onKeyDown( event ); } Uint32 UIPushButton::onKeyUp( const KeyEvent& Event ) { diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index d47bc7ba8..aae3bf473 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -182,6 +182,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, editor.tabIndentAlignment = characterAlignmentFromString( ini.getValue( "editor", "tab_indent_alignment", characterAlignmentToString( CharacterAlignment::Center ) ) ); + editor.flashCursor = ini.getValueB( "editor", "flash_cursor", true ); searchBarConfig.caseSensitive = ini.getValueB( "search_bar", "case_sensitive", false ); searchBarConfig.luaPattern = ini.getValueB( "search_bar", "lua_pattern", false ); @@ -328,6 +329,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValue( "editor", "tab_indent_character", editor.tabIndentCharacter ); ini.setValue( "editor", "tab_indent_alignment", characterAlignmentToString( editor.tabIndentAlignment ) ); + ini.setValueB( "editor", "flash_cursor", editor.flashCursor ); ini.setValueB( "search_bar", "case_sensitive", searchBarConfig.caseSensitive ); ini.setValueB( "search_bar", "lua_pattern", searchBarConfig.luaPattern ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index 8874badc5..2e5f7a264 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -82,6 +82,7 @@ struct CodeEditorConfig { bool autoReloadOnDiskChange{ false }; bool codeFoldingEnabled{ true }; bool codeFoldingAlwaysVisible{ false }; + bool flashCursor{ false }; LineWrapMode wrapMode{ LineWrapMode::NoWrap }; LineWrapType wrapType{ LineWrapType::Viewport }; bool wrapKeepIndentation{ true }; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 1ba0f0754..a734b4123 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2361,6 +2361,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { editor->setLineWrapType( config.wrapType ); editor->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) ); editor->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) ); + editor->setEnableFlashCursor( config.flashCursor ); doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() ); doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) ); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index b016df3c6..1bd1d89f2 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -658,6 +658,17 @@ UIMenu* SettingsMenu::createDocumentMenu() { "before exiting the program." ) ) ->setId( "session_snapshot" ); + mGlobalMenu + ->addCheckBox( i18n( "allow_flash_cursor", "Allow Flashing Cursor" ), + mApp->getConfig().editor.flashCursor ) + ->setTooltipText( i18n( + "allow_flash_cursor_desc", + "When enabled, pressing the default modifier key 5 times within 1.5 seconds will\n" + "trigger a visual effect that highlights the current cursor position. A large,\n" + "transparent rectangle will briefly animate, shrinking down to the cursor, making it\n" + "easier to locate when it's hard to see." ) ) + ->setId( "allow_flash_cursor" ); + mGlobalMenu->addSeparator(); mGlobalMenu->add( i18n( "line_breaking_column", "Line Breaking Column" ) ) @@ -716,6 +727,8 @@ UIMenu* SettingsMenu::createDocumentMenu() { mApp->getConfig().editor.autoReloadOnDiskChange = item->isActive(); } else if ( "session_snapshot" == id ) { mApp->getConfig().workspace.sessionSnapshot = item->isActive(); + } else if ( "allow_flash_cursor" == id ) { + mApp->getConfig().editor.flashCursor = item->isActive(); } } else if ( "line_breaking_column" == id ) { mApp->getSettingsActions()->setLineBreakingColumn();