Adds support for custom tab visualization (SpartanJ/ecode#37).

This commit is contained in:
Martín Lucas Golini
2024-08-17 13:02:59 -03:00
parent fe69610cb7
commit 526e86b5d6
7 changed files with 194 additions and 57 deletions

View File

@@ -33,6 +33,8 @@ class UILoader;
class UIPopUpMenu;
class UIMenuItem;
enum class CharacterAlignment : Uint32 { Left = 0, Center = 1, Right = 2 };
using DocumentLineRange = std::pair<Int64, Int64>;
using DocumentViewLineRange = std::pair<VisibleIndex, VisibleIndex>;
@@ -80,30 +82,30 @@ class UICodeEditorPlugin {
}
virtual void drawBeforeLineText( UICodeEditor*, const Int64&, Vector2f, const Float&,
const Float& ) {};
const Float& ){};
virtual void drawAfterLineText( UICodeEditor* /*editor*/, const Int64& /*index*/,
Vector2f /*position*/, const Float& /*fontSize*/,
const Float& /*lineHeight*/ ) {};
const Float& /*lineHeight*/ ){};
virtual void minimapDrawBefore( UICodeEditor* /*editor*/, const DocumentLineRange&,
const DocumentViewLineRange&, const Vector2f& /*linePos*/,
const Vector2f& /*lineSize*/, const Float& /*charWidth*/,
const Float& /*gutterWidth*/,
const DrawTextRangesFn& /* drawTextRanges */ ) {};
const DrawTextRangesFn& /* drawTextRanges */ ){};
virtual void minimapDrawAfter( UICodeEditor* /*editor*/, const DocumentLineRange&,
const DocumentViewLineRange&, const Vector2f& /* linePos */,
const Vector2f& /* lineSize */, const Float& /* charWidth */,
const Float& /* gutterWidth */,
const DrawTextRangesFn& /* drawTextRanges */ ) {};
const DrawTextRangesFn& /* drawTextRanges */ ){};
virtual void drawGutter( UICodeEditor* /*editor*/, const Int64& /*index*/,
const Vector2f& /*screenStart*/, const Float& /*lineHeight*/,
const Float& /*gutterWidth*/, const Float& /*fontSize*/ ) {};
const Float& /*gutterWidth*/, const Float& /*fontSize*/ ){};
virtual void drawTop( UICodeEditor* /*editor*/, const Vector2f& /*screenStart*/,
const Sizef& /*size*/, const Float& /*fontSize*/ ) {};
const Sizef& /*size*/, const Float& /*fontSize*/ ){};
Uint32 addOnReadyCallback( const OnReadyCb& cb ) {
mOnReadyCallbacks[mReadyCbNum++] = cb;
@@ -722,6 +724,14 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void updateMouseCursor( const Vector2f& position );
Uint32 getTabIndentCharacter() const { return mTabIndentCharacter; }
void setTabIndentCharacter( Uint32 chr );
CharacterAlignment getTabIndentAlignment() const { return mTabIndentAlignment; }
void setTabIndentAlignment( CharacterAlignment alignment );
protected:
struct LastXOffset {
TextPosition position{ 0, 0 };
@@ -843,6 +853,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
Drawable* mFoldDrawable{ nullptr };
Drawable* mFoldedDrawable{ nullptr };
String::HashType mTagFoldRange{ 0 };
Uint32 mTabIndentCharacter{ 187 /*'»'*/ };
CharacterAlignment mTabIndentAlignment{ CharacterAlignment::Center };
UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands = true,
const bool& autoRegisterBaseKeybindings = true );

View File

@@ -3899,9 +3899,20 @@ void UICodeEditor::drawWhitespaces( const DocumentLineRange& lineRange, const Ve
Color color( Color( mWhitespaceColor ).blendAlpha( mAlpha ) );
unsigned int fontSize = getCharacterSize();
// We use the GlyphDrawable since can batch the draw calls instead of Text.
GlyphDrawable* adv = mFont->getGlyphDrawable( 187 /*'»'*/, fontSize );
GlyphDrawable* adv = mFont->getGlyphDrawable( mTabIndentCharacter, fontSize );
GlyphDrawable* cpoint = mFont->getGlyphDrawable( 183 /*'·'*/, fontSize );
Float tabCenter = ( tabWidth - adv->getPixelsSize().getWidth() ) * 0.5f;
Float tabAlign = 0;
switch ( mTabIndentAlignment ) {
case CharacterAlignment::Center:
tabAlign = ( tabWidth - adv->getPixelsSize().getWidth() ) * 0.5f;
break;
case CharacterAlignment::Right:
tabAlign = ( tabWidth - adv->getPixelsSize().getWidth() );
break;
case CharacterAlignment::Left:
break;
}
adv->setDrawMode( GlyphDrawable::DrawMode::Text );
cpoint->setDrawMode( GlyphDrawable::DrawMode::Text );
adv->setColor( color );
@@ -3922,7 +3933,7 @@ void UICodeEditor::drawWhitespaces( const DocumentLineRange& lineRange, const Ve
auto offset =
startScroll +
getTextPositionOffset( { index, static_cast<Int64>( i ) } ).asFloat();
offset.x += tabCenter;
offset.x += tabAlign;
adv->draw( offset );
}
}
@@ -3938,7 +3949,7 @@ void UICodeEditor::drawWhitespaces( const DocumentLineRange& lineRange, const Ve
cpoint->draw( position );
position.x += glyphW;
} else if ( '\t' == text[i] ) {
adv->draw( Vector2f( position.x + tabCenter, position.y ) );
adv->draw( Vector2f( position.x + tabAlign, position.y ) );
position.x += tabWidth;
} else {
position.x += glyphW;
@@ -4837,4 +4848,18 @@ void UICodeEditor::updateMouseCursor( const Vector2f& position ) {
}
}
void UICodeEditor::setTabIndentCharacter( Uint32 chr ) {
if ( mTabIndentCharacter != chr ) {
mTabIndentCharacter = chr;
invalidateDraw();
}
}
void UICodeEditor::setTabIndentAlignment( CharacterAlignment alignment ) {
if ( mTabIndentAlignment != alignment ) {
mTabIndentAlignment = alignment;
invalidateDraw();
}
}
}} // namespace EE::UI

View File

@@ -15,6 +15,28 @@ using namespace std::literals;
namespace ecode {
std::string characterAlignmentToString( CharacterAlignment alignment ) {
switch ( alignment ) {
case CharacterAlignment::Left:
return "left";
break;
case CharacterAlignment::Center:
return "center";
break;
case CharacterAlignment::Right:
return "right";
break;
}
}
CharacterAlignment characterAlignmentFromString( const std::string_view& str ) {
if ( str == "center" )
return CharacterAlignment::Center;
if ( str == "right" )
return CharacterAlignment::Right;
return CharacterAlignment::Left;
}
static PanelPosition panelPositionFromString( const std::string& str ) {
if ( String::toLower( str ) == "right" )
return PanelPosition::Right;
@@ -151,6 +173,10 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
ini.getValueB( "editor", "code_folding_always_visible", false );
editor.codeFoldingRefreshFreq =
Time::fromString( ini.getValue( "editor", "code_folding_refresh_frequency", "2s" ) );
editor.tabIndentCharacter = ini.getValue( "editor", "tab_indent_character" );
editor.tabIndentAlignment = characterAlignmentFromString(
ini.getValue( "editor", "tab_indent_alignment",
characterAlignmentToString( CharacterAlignment::Center ) ) );
searchBarConfig.caseSensitive = ini.getValueB( "search_bar", "case_sensitive", false );
searchBarConfig.luaPattern = ini.getValueB( "search_bar", "lua_pattern", false );
@@ -287,6 +313,9 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
ini.setValueB( "editor", "code_folding_always_visible", editor.codeFoldingAlwaysVisible );
ini.setValue( "editor", "code_folding_refresh_frequency",
editor.codeFoldingRefreshFreq.toString() );
ini.setValue( "editor", "tab_indent_character", editor.tabIndentCharacter );
ini.setValue( "editor", "tab_indent_alignment",
characterAlignmentToString( editor.tabIndentAlignment ) );
ini.setValueB( "search_bar", "case_sensitive", searchBarConfig.caseSensitive );
ini.setValueB( "search_bar", "lua_pattern", searchBarConfig.luaPattern );

View File

@@ -6,6 +6,7 @@
#include <eepp/system/inifile.hpp>
#include <eepp/ui/css/stylesheetlength.hpp>
#include <eepp/ui/tools/uicodeeditorsplitter.hpp>
#include <eepp/ui/uicodeeditor.hpp>
#include <eepp/window/window.hpp>
#include <nlohmann/json.hpp>
@@ -85,6 +86,8 @@ struct CodeEditorConfig {
std::string autoCloseBrackets{ "" };
Time cursorBlinkingTime{ Seconds( 0.5f ) };
Time codeFoldingRefreshFreq{ Seconds( 2.f ) };
std::string tabIndentCharacter{ "" };
CharacterAlignment tabIndentAlignment{ CharacterAlignment::Center };
};
struct DocumentConfig {

View File

@@ -1334,6 +1334,34 @@ void App::setCursorBlinkingTime() {
setFocusEditorOnClose( msgBox );
}
void App::setIndentTabCharacter() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "set_indent_tab_character", "Set the tab indentation guide character displayed." ) );
msgBox->setTitle( mWindowTitle );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->getTextInput()->setFont( mFontMono );
msgBox->getTextInput()->setText( String::fromUtf8(
mConfig.editor.tabIndentCharacter.empty() ? u8"»" : mConfig.editor.tabIndentCharacter ) );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
auto txt( msgBox->getTextInput()->getText() );
if ( txt.size() == 1 ) {
mConfig.editor.tabIndentCharacter = txt.toUtf8();
mSplitter->forEachEditor(
[txt]( UICodeEditor* editor ) { editor->setTabIndentCharacter( txt[0] ); } );
msgBox->closeWindow();
} else {
UIMessageBox* msgBoxAlert =
UIMessageBox::New( UIMessageBox::OK, i18n( "it_must_be_a_single_character",
"It must be a single character" ) );
msgBoxAlert->setTitle( mWindowTitle );
msgBoxAlert->setCloseShortcut( { KEY_ESCAPE, 0 } );
}
} );
setFocusEditorOnClose( msgBox );
}
void App::setFoldRefreshFreq() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
@@ -2079,50 +2107,51 @@ std::map<KeyBindings::Shortcut, std::string> App::getDefaultKeybindings() {
std::map<KeyBindings::Shortcut, std::string> App::getLocalKeybindings() {
return {
{ { KEY_RETURN, KEYMOD_LALT | KEYMOD_LCTRL }, "fullscreen-toggle" },
{ { KEY_F3, KEYMOD_NONE }, "repeat-find" },
{ { KEY_F3, KEYMOD_SHIFT }, "find-prev" },
{ { KEY_F12, KEYMOD_NONE }, "console-toggle" },
{ { KEY_F, KeyMod::getDefaultModifier() }, "find-replace" },
{ { KEY_Q, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "close-app" },
{ { KEY_O, KeyMod::getDefaultModifier() }, "open-file" },
{ { KEY_W, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "download-file-web" },
{ { KEY_O, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-folder" },
{ { KEY_F11, KEYMOD_NONE }, "debug-widget-tree-view" },
{ { KEY_K, KeyMod::getDefaultModifier() }, "open-locatebar" },
{ { KEY_P, KeyMod::getDefaultModifier() }, "open-command-palette" },
{ { KEY_F, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-global-search" },
{ { KEY_L, KeyMod::getDefaultModifier() }, "go-to-line" },
{ { KEY_F3, KEYMOD_NONE }, "repeat-find" }, { { KEY_F3, KEYMOD_SHIFT }, "find-prev" },
{ { KEY_F12, KEYMOD_NONE }, "console-toggle" },
{ { KEY_F, KeyMod::getDefaultModifier() }, "find-replace" },
{ { KEY_Q, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "close-app" },
{ { KEY_O, KeyMod::getDefaultModifier() }, "open-file" },
{ { KEY_W, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "download-file-web" },
{ { KEY_O, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-folder" },
{ { KEY_F11, KEYMOD_NONE }, "debug-widget-tree-view" },
{ { KEY_K, KeyMod::getDefaultModifier() }, "open-locatebar" },
{ { KEY_P, KeyMod::getDefaultModifier() }, "open-command-palette" },
{ { KEY_F, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-global-search" },
{ { KEY_L, KeyMod::getDefaultModifier() }, "go-to-line" },
#if EE_PLATFORM == EE_PLATFORM_MACOS
{ { KEY_M, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "menu-toggle" },
{ { KEY_M, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "menu-toggle" },
#else
{ { KEY_M, KeyMod::getDefaultModifier() }, "menu-toggle" },
{ { KEY_M, KeyMod::getDefaultModifier() }, "menu-toggle" },
#endif
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "save-all" },
{ { KEY_F9, KEYMOD_LALT }, "switch-side-panel" },
{ { KEY_J, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-left" },
{ { KEY_L, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-right" },
{ { KEY_I, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-top" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-bottom" },
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-swap" },
{ { KEY_T, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"reopen-closed-tab" },
{ { KEY_1, KEYMOD_LALT }, "toggle-status-locate-bar" },
{ { KEY_2, KEYMOD_LALT }, "toggle-status-global-search-bar" },
{ { KEY_3, KEYMOD_LALT }, "toggle-status-terminal" },
{ { KEY_4, KEYMOD_LALT }, "toggle-status-build-output" },
{ { KEY_5, KEYMOD_LALT }, "toggle-status-app-output" },
{ { KEY_B, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-start" },
{ { KEY_C, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-cancel" },
{ { KEY_F5, KEYMOD_NONE }, "project-build-and-run" },
{ { KEY_O, KEYMOD_LALT | KEYMOD_SHIFT }, "show-open-documents" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-workspace-symbol-search" },
{ { KEY_P, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-document-symbol-search" },
{ { KEY_N, KEYMOD_SHIFT | KEYMOD_LALT }, "create-new-window" },
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "save-all" },
{ { KEY_F9, KEYMOD_LALT }, "switch-side-panel" },
{ { KEY_J, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-left" },
{ { KEY_L, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-right" },
{ { KEY_I, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-top" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-bottom" },
{ { KEY_S, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"terminal-split-swap" },
{ { KEY_T, KeyMod::getDefaultModifier() | KEYMOD_LALT | KEYMOD_SHIFT },
"reopen-closed-tab" },
{ { KEY_1, KEYMOD_LALT }, "toggle-status-locate-bar" },
{ { KEY_2, KEYMOD_LALT }, "toggle-status-global-search-bar" },
{ { KEY_3, KEYMOD_LALT }, "toggle-status-terminal" },
{ { KEY_4, KEYMOD_LALT }, "toggle-status-build-output" },
{ { KEY_5, KEYMOD_LALT }, "toggle-status-app-output" },
{ { KEY_B, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-start" },
{ { KEY_C, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "project-build-cancel" },
{ { KEY_F5, KEYMOD_NONE }, "project-build-and-run" },
{ { KEY_O, KEYMOD_LALT | KEYMOD_SHIFT }, "show-open-documents" },
{ { KEY_K, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
"open-workspace-symbol-search" },
{ { KEY_P, KeyMod::getDefaultModifier() | KEYMOD_SHIFT },
"open-document-symbol-search" },
{ { KEY_N, KEYMOD_SHIFT | KEYMOD_LALT }, "create-new-window" },
};
}
@@ -2131,13 +2160,13 @@ std::map<KeyBindings::Shortcut, std::string> App::getLocalKeybindings() {
std::map<std::string, std::string> App::getMigrateKeybindings() {
return {
{ "fullscreen-toggle", "alt+return" }, { "switch-to-tab-1", "alt+1" },
{ "switch-to-tab-2", "alt+2" }, { "switch-to-tab-3", "alt+3" },
{ "switch-to-tab-4", "alt+4" }, { "switch-to-tab-5", "alt+5" },
{ "switch-to-tab-6", "alt+6" }, { "switch-to-tab-7", "alt+7" },
{ "switch-to-tab-8", "alt+8" }, { "switch-to-tab-9", "alt+9" },
{ "switch-to-last-tab", "alt+0" },
{ "switch-to-tab-2", "alt+2" }, { "switch-to-tab-3", "alt+3" },
{ "switch-to-tab-4", "alt+4" }, { "switch-to-tab-5", "alt+5" },
{ "switch-to-tab-6", "alt+6" }, { "switch-to-tab-7", "alt+7" },
{ "switch-to-tab-8", "alt+8" }, { "switch-to-tab-9", "alt+9" },
{ "switch-to-last-tab", "alt+0" },
#if EE_PLATFORM == EE_PLATFORM_MACOS
{ "menu-toggle", "mod+shift+m" },
{ "menu-toggle", "mod+shift+m" },
#endif
};
}
@@ -2634,6 +2663,13 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
editor->setFoldsAlwaysVisible( config.codeFoldingAlwaysVisible );
editor->setFoldsRefreshTime( config.codeFoldingRefreshFreq );
if ( !config.tabIndentCharacter.empty() ) {
String indentChar( config.tabIndentCharacter );
if ( indentChar.size() == 1 )
editor->setTabIndentCharacter( indentChar[0] );
}
editor->setTabIndentAlignment( config.tabIndentAlignment );
editor->addKeyBinds( getLocalKeybindings() );
editor->addUnlockedCommands( getUnlockedCommands() );
doc.setCommand( "save-doc", [this] { saveDoc(); } );

View File

@@ -334,6 +334,8 @@ class App : public UICodeEditorSplitter::Client {
void setCursorBlinkingTime();
void setIndentTabCharacter();
void setFoldRefreshFreq();
void checkForUpdates( bool fromStartup = false );

View File

@@ -660,6 +660,34 @@ UIMenu* SettingsMenu::createDocumentMenu() {
mGlobalMenu->add( i18n( "cursor_blinking_time", "Cursor Blinking Time" ) )
->setId( "cursor_blinking_time" );
mGlobalMenu->add( i18n( "indent_tab_character", "Indent Tab Character" ) )
->setId( "indent_tab_character" );
UIPopUpMenu* indentTabAlignmentMenuGlobal = UIPopUpMenu::New();
indentTabAlignmentMenuGlobal->addRadioButton( i18n( "left", "Left" ) )
->setActive( mApp->getConfig().editor.tabIndentAlignment == CharacterAlignment::Left )
->setId( "left" );
indentTabAlignmentMenuGlobal->addRadioButton( i18n( "center", "Center" ) )
->setActive( mApp->getConfig().editor.tabIndentAlignment == CharacterAlignment::Center )
->setId( "center" );
indentTabAlignmentMenuGlobal->addRadioButton( i18n( "right", "Right" ) )
->setActive( mApp->getConfig().editor.tabIndentAlignment == CharacterAlignment::Right )
->setId( "right" );
mGlobalMenu
->addSubMenu( i18n( "indent_tab_alignment", "Indent Tab Alignment" ), nullptr,
indentTabAlignmentMenuGlobal )
->setId( "indent_type_alignment" );
indentTabAlignmentMenuGlobal->on( Event::OnItemClicked, [this]( const Event* event ) {
const String& text = event->getNode()->asType<UIMenuRadioButton>()->getId();
mApp->getConfig().editor.tabIndentAlignment =
text == "center"
? CharacterAlignment::Center
: ( text == "right" ? CharacterAlignment::Right : CharacterAlignment::Left );
mSplitter->forEachEditor( [this]( UICodeEditor* editor ) {
editor->setTabIndentAlignment( mApp->getConfig().editor.tabIndentAlignment );
} );
} );
mGlobalMenu->on( Event::OnItemClicked, [this]( const Event* event ) {
if ( event->getNode()->isType( UI_TYPE_MENU_SEPARATOR ) ||
event->getNode()->isType( UI_TYPE_MENUSUBMENU ) )
@@ -687,6 +715,8 @@ UIMenu* SettingsMenu::createDocumentMenu() {
mApp->setLineSpacing();
} else if ( "cursor_blinking_time" == id ) {
mApp->setCursorBlinkingTime();
} else if ( "indent_tab_character" == id ) {
mApp->setIndentTabCharacter();
}
} );