Add flash cursor command and functionality.

This commit is contained in:
Martín Lucas Golini
2025-02-07 00:09:32 -03:00
parent 75c63660b1
commit 0da3ffca17
8 changed files with 97 additions and 29 deletions
+11 -2
View File
@@ -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<size_t> 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<UICodeEditorPlugin*> 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
+21 -22
View File
@@ -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<UICodeEditor>() );
} );
editor->addEventListener( Event::OnTextChanged, [this]( const Event* event ) {
editor->on( Event::OnTextChanged, [this]( const Event* event ) {
mClient->onDocumentModified( event->getNode()->asType<UICodeEditor>(),
event->getNode()->asType<UICodeEditor>()->getDocument() );
} );
editor->addEventListener( Event::OnSelectionChanged, [this]( const Event* event ) {
editor->on( Event::OnSelectionChanged, [this]( const Event* event ) {
mClient->onDocumentSelectionChange(
event->getNode()->asType<UICodeEditor>(),
event->getNode()->asType<UICodeEditor>()->getDocument() );
} );
editor->addEventListener( Event::OnCursorPosChange, [this]( const Event* event ) {
editor->on( Event::OnCursorPosChange, [this]( const Event* event ) {
mClient->onDocumentCursorPosChange(
event->getNode()->asType<UICodeEditor>(),
event->getNode()->asType<UICodeEditor>()->getDocument() );
} );
editor->addEventListener( Event::OnDocumentUndoRedo, [this]( const Event* event ) {
editor->on( Event::OnDocumentUndoRedo, [this]( const Event* event ) {
mClient->onDocumentUndoRedo( event->getNode()->asType<UICodeEditor>(),
event->getNode()->asType<UICodeEditor>()->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<UICodeEditor>(),
event->getNode()->asType<UICodeEditor>()->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<UIWidget>() );
} );
widget->addEventListener( Event::OnTitleChange, [this]( const Event* event ) {
widget->on( Event::OnTitleChange, [this]( const Event* event ) {
const TextEvent* tevent = static_cast<const TextEvent*>( event );
UIWidget* widget = event->getNode()->asType<UIWidget>();
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<UITabWidget>();
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<const TabEvent*>( 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();
+45 -2
View File
@@ -4,6 +4,9 @@
#include <eepp/graphics/globalbatchrenderer.hpp>
#include <eepp/graphics/primitives.hpp>
#include <eepp/graphics/renderer/renderer.hpp>
#include <eepp/scene/actions/close.hpp>
#include <eepp/scene/actions/scale.hpp>
#include <eepp/scene/actions/sequence.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/luapattern.hpp>
#include <eepp/system/scopedop.hpp>
@@ -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" } );
+3 -3
View File
@@ -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 ) {
+2
View File
@@ -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<std::string>& 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 );
+1
View File
@@ -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 };
+1
View File
@@ -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 ) );
+13
View File
@@ -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();