ecode: add centralized settings panel and simplify settings menus

- add searchable user and project settings panels
  - organize settings into navigable categories and subsections
  - apply supported configuration changes in real time
  - add direct document and terminal settings accessors
  - retain only contextual actions in the legacy settings menus
  - replace legacy configuration dialogs with inline controls
  - improve modal behavior, keyboard navigation, and persistence
  - add validation for editable settings
  - update and remove obsolete i18n strings
This commit is contained in:
Martín Lucas Golini
2026-08-24 15:16:17 -03:00
parent 30cf6d9e97
commit 932c00ae4f
12 changed files with 236 additions and 2418 deletions
+6 -17
View File
@@ -2181,9 +2181,6 @@ std::map<std::string, std::string> App::getMigrateKeybindings() {
std::vector<std::string> App::getUnlockedCommands() {
return {
"take-screenshot",
"screenshot-save-path",
"screenshot-filename-pattern",
"screenshot-save-format",
"create-new",
"create-new-terminal",
"create-new-welcome-tab",
@@ -2211,6 +2208,8 @@ std::vector<std::string> App::getUnlockedCommands() {
"menu-toggle",
"open-settings",
"open-project-settings",
"open-document-settings",
"open-terminal-settings",
"switch-side-panel",
"toggle-status-bar",
"download-file-web",
@@ -2226,20 +2225,11 @@ std::vector<std::string> App::getUnlockedCommands() {
"debug-draw-highlight-toggle",
"debug-draw-boxes-toggle",
"debug-draw-debug-data",
"editor-set-line-breaking-column",
"editor-set-line-spacing",
"editor-set-cursor-blinking-time",
"editor-set-indent-tab-character",
"check-for-updates",
"keybindings",
"about-ecode",
"ecode-source",
"ui-scale-factor",
"show-side-panel",
"editor-font-size",
"terminal-font-size",
"ui-font-size",
"ui-panel-font-size",
"sans-serif-font",
"editor-font",
"terminal-font",
@@ -2251,8 +2241,8 @@ std::vector<std::string> App::getUnlockedCommands() {
"show-folder-treeview-tab",
"show-build-tab",
"create-new-window",
"reset-global-language-extensions-priorities",
"reset-project-language-extensions-priorities",
"reset-global-file-associations",
"reset-project-file-associations",
"maximize-tab-widget",
"restore-maximized-tab-widget",
"close-folder",
@@ -2397,7 +2387,6 @@ void App::createDocDirtyAlert( UICodeEditor* editor, bool showEnableAutoReload )
docAlert->close();
editor->setFocus();
mConfig.editor.autoReloadOnDiskChange = true;
mSettings->updateGlobalDocumentSettingsMenu();
} );
docAlert->find( "file_reload" )->onClick( [editor, docAlert]( const MouseEvent* ) {
@@ -3598,10 +3587,10 @@ void App::toggleSettingsMenu() {
mSettings->toggleSettingsMenu();
}
void App::openSettings() {
void App::openSettings( const std::string& category ) {
if ( !mSettingsPanel )
mSettingsPanel = std::make_unique<SettingsPanel>( this );
mSettingsPanel->show( SettingsPanel::Scope::User );
mSettingsPanel->show( SettingsPanel::Scope::User, category );
}
void App::openProjectSettings() {
+3 -19
View File
@@ -216,7 +216,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
void toggleSettingsMenu();
void openSettings();
void openSettings( const std::string& category = {} );
void openProjectSettings();
@@ -249,6 +249,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
t.setCommand( "debug-widget-tree-view", [this] { createWidgetInspector(); } );
t.setCommand( "menu-toggle", [this] { toggleSettingsMenu(); } );
t.setCommand( "open-settings", [this] { openSettings(); } );
t.setCommand( "open-document-settings", [this] { openSettings( "editor.document" ); } );
t.setCommand( "open-terminal-settings", [this] { openSettings( "terminal.*" ); } );
t.setCommand( "open-project-settings", [this] { openProjectSettings(); } );
t.setCommand( "switch-side-panel", [this] { switchSidePanel(); } );
t.setCommand( "download-file-web", [this] { downloadFileWebDialog(); } );
@@ -344,31 +346,13 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
[this] { mUniversalLocator->showWorkspaceSymbol(); } );
t.setCommand( "open-document-symbol-search",
[this] { mUniversalLocator->showDocumentSymbol(); } );
t.setCommand( "editor-set-line-breaking-column",
[this] { mSettingsActions->setLineBreakingColumn(); } );
t.setCommand( "editor-set-line-spacing", [this] { mSettingsActions->setLineSpacing(); } );
t.setCommand( "editor-set-cursor-blinking-time",
[this] { mSettingsActions->setCursorBlinkingTime(); } );
t.setCommand( "editor-set-indent-tab-character",
[this] { mSettingsActions->setIndentTabCharacter(); } );
t.setCommand( "check-for-updates", [this] { mSettingsActions->checkForUpdates(); } );
t.setCommand( "about-ecode", [this] { mSettingsActions->aboutEcode(); } );
t.setCommand( "ecode-source", [this] { mSettingsActions->ecodeSource(); } );
t.setCommand( "ui-scale-factor", [this] { mSettingsActions->setUIScaleFactor(); } );
t.setCommand( "take-screenshot", [this] { takeScreenshot(); } );
t.setCommand( "screenshot-save-path",
[this] { mSettingsActions->setScreenshotSavePath(); } );
t.setCommand( "screenshot-filename-pattern",
[this] { mSettingsActions->setScreenshotFilenamePattern(); } );
t.setCommand( "screenshot-save-format",
[this] { mSettingsActions->setScreenshotSaveFormat(); } );
t.setCommand( "show-side-panel", [this] { switchSidePanel(); } );
t.setCommand( "toggle-status-bar", [this] { switchStatusBar(); } );
t.setCommand( "toggle-menu-bar", [this] { switchMenuBar(); } );
t.setCommand( "editor-font-size", [this] { mSettingsActions->setEditorFontSize(); } );
t.setCommand( "terminal-font-size", [this] { mSettingsActions->setTerminalFontSize(); } );
t.setCommand( "ui-font-size", [this] { mSettingsActions->setUIFontSize(); } );
t.setCommand( "ui-panel-font-size", [this] { mSettingsActions->setUIPanelFontSize(); } );
t.setCommand( "sans-serif-font",
[this] { openFontDialog( mConfig.ui.sansSerifFont, false ); } );
t.setCommand( "editor-font", [this] { openFontDialog( mConfig.ui.monospaceFont, true ); } );
+5 -300
View File
@@ -1,10 +1,7 @@
#include "settingsactions.hpp"
#include "datetimecontroller.hpp"
#include "ecode.hpp"
#include "version.hpp"
using namespace std::string_literals;
namespace ecode {
void SettingsActions::checkForUpdatesResponse( Http::Response&& response, bool fromStartup ) {
@@ -143,204 +140,6 @@ void SettingsActions::ecodeSource() {
Engine::instance()->openURI( "https://github.com/SpartanJ/ecode" );
}
void SettingsActions::setLineBreakingColumn() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "set_line_breaking_column", "Set Line Breaking Column:\nSet 0 to disable it.\n" )
.unescape() );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->getTextInput()->setAllowOnlyNumbers( true, false );
msgBox->getTextInput()->setText( String::toString( mApp->getConfig().doc.lineBreakingColumn ) );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
int val;
if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 0 ) {
mApp->getConfig().doc.lineBreakingColumn = val;
mApp->getSplitter()->forEachEditor(
[val]( UICodeEditor* editor ) { editor->setLineBreakingColumn( val ); } );
msgBox->closeWindow();
}
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setLineSpacing() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "set_line_spacing", "Set Line Spacing:\nSet 0 to disable it.\n" ).unescape() );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->getTextInput()->setText( mApp->getConfig().editor.lineSpacing.toString() );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
mApp->getConfig().editor.lineSpacing =
StyleSheetLength( msgBox->getTextInput()->getText() );
mApp->getSplitter()->forEachEditor( [this]( UICodeEditor* editor ) {
editor->setLineSpacing( mApp->getConfig().editor.lineSpacing );
} );
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setCursorBlinkingTime() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "set_cursor_blinking_time", "Set Cursor Blinking Time:\nSet 0 to disable it.\n" )
.unescape() );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->getTextInput()->setText( mApp->getConfig().editor.cursorBlinkingTime.toString() );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
mApp->getConfig().editor.cursorBlinkingTime =
Time::fromString( msgBox->getTextInput()->getText().toUtf8() );
mApp->getSplitter()->forEachEditor( [this]( UICodeEditor* editor ) {
editor->setCursorBlinkTime( mApp->getConfig().editor.cursorBlinkingTime );
} );
msgBox->closeWindow();
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setIndentTabCharacter() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::COMBOBOX,
i18n( "set_indent_tab_character", "Set the tab indentation guide character displayed." ) );
msgBox->setId( "indent_tab_window" );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
UIComboBox* comboBox = msgBox->getComboBox();
UIListBox* listBox = msgBox->getComboBox()->getDropDownList()->getListBox();
listBox->addClass( "indent_tab_listbox_item" );
listBox->addListBoxItems( { u8"»"s, u8"→"s, u8"⇒"s, u8"↪"s, u8"⇢"s, u8"↣"s } );
msgBox->getComboBox()->setText(
mApp->getConfig().editor.tabIndentCharacter.empty()
? String( u8"»"s )
: String::fromUtf8( mApp->getConfig().editor.tabIndentCharacter ) );
msgBox->showWhenReady();
comboBox->on( Event::OnValueChange, [this, comboBox]( const Event* ) {
if ( comboBox->getText().size() != 1 )
return;
mApp->getSplitter()->forEachEditor( [comboBox]( UICodeEditor* editor ) {
editor->setTabIndentCharacter( comboBox->getText()[0] );
} );
} );
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
auto txt( msgBox->getComboBox()->getText() );
if ( txt.size() == 1 ) {
mApp->getConfig().editor.tabIndentCharacter = txt.toUtf8();
mApp->getSplitter()->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( mApp->getWindowTitle() );
msgBoxAlert->setCloseShortcut( { KEY_ESCAPE, 0 } );
}
} );
msgBox->on( Event::OnDiscard, [this]( const Event* ) {
String txt = String::fromUtf8( mApp->getConfig().editor.tabIndentCharacter );
if ( txt.size() != 1 )
return;
mApp->getSplitter()->forEachEditor(
[&txt]( UICodeEditor* editor ) { editor->setTabIndentCharacter( txt[0] ); } );
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setTabOutChars() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "set_tab_out_characters_message",
"Set the characters the cursor can move past when pressing Tab." ) );
msgBox->setTitle( i18n( "set_tab_out_characters", "Set Tab Out Characters" ) );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->getTextInput()->setText( String::fromUtf8( mApp->getConfig().doc.tabOutChars ) );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
String chars = msgBox->getTextInput()->getText();
mApp->getConfig().doc.tabOutChars = chars.toUtf8();
mApp->getSplitter()->forEachEditor(
[&chars]( UICodeEditor* editor ) { editor->getDocument().setTabOutChars( chars ); } );
msgBox->closeWindow();
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setFoldRefreshFreq() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "set_fold_refresh_frequency",
"Set code folds refresh frequency:\nIt should be bigger than 1 second.\nFolds are "
"only refreshed after any document modification." )
.unescape() );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->getTextInput()->setText( mApp->getConfig().editor.codeFoldingRefreshFreq.toString() );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
mApp->getConfig().editor.codeFoldingRefreshFreq =
Time::fromString( msgBox->getTextInput()->getText().toUtf8() );
if ( mApp->getConfig().editor.codeFoldingRefreshFreq < Seconds( 1 ) )
mApp->getConfig().editor.codeFoldingRefreshFreq = Seconds( 1 );
mApp->getSplitter()->forEachEditor( [this]( UICodeEditor* editor ) {
editor->setFoldsRefreshTime( mApp->getConfig().editor.codeFoldingRefreshFreq );
} );
msgBox->closeWindow();
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setUIScaleFactor() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "set_ui_scale_factor", "Set the UI scale factor (pixel density):\nMinimum value is "
"1, and maximum 6. Requires restart." ) );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->getTextInput()->setText(
String::fromFloat( mApp->getConfig().windowState.pixelDensity ) );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
msgBox->closeWindow();
Float val;
if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 1 &&
val <= 6 ) {
if ( mApp->getConfig().windowState.pixelDensity != val ) {
mApp->getConfig().windowState.pixelDensity = val;
UIMessageBox* msg = UIMessageBox::New(
UIMessageBox::OK,
i18n( "new_ui_scale_factor", "New UI scale factor assigned.\nPlease "
"restart the application." ) );
msg->showWhenReady();
mApp->setFocusEditorOnClose( msg );
} else if ( mApp->getSplitter() && mApp->getSplitter()->getCurWidget() ) {
mApp->getSplitter()->getCurWidget()->setFocus();
}
} else {
UIMessageBox* msg = UIMessageBox::New( UIMessageBox::OK, "Invalid value!" );
msg->showWhenReady();
mApp->setFocusEditorOnClose( msg );
}
} );
}
void SettingsActions::setEditorFontSize() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT, i18n( "set_editor_font_size", "Set the editor font size:" ) );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->getTextInput()->setText( mApp->getConfig().editor.fontSize.toString() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
setEditorFontSize( StyleSheetLength( msgBox->getTextInput()->getText() ) );
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setEditorFontSize( const StyleSheetLength& size ) {
mApp->getConfig().editor.fontSize = size;
const Float fontSize = size.asPixels( 0, Sizef(), mApp->getDisplayDPI() );
@@ -348,19 +147,6 @@ void SettingsActions::setEditorFontSize( const StyleSheetLength& size ) {
[fontSize]( UICodeEditor* editor ) { editor->setFontSize( fontSize ); } );
}
void SettingsActions::setTerminalFontSize() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT, i18n( "set_terminal_font_size", "Set the terminal font size:" ) );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->getTextInput()->setText( mApp->getConfig().term.fontSize.toString() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
setTerminalFontSize( StyleSheetLength( msgBox->getTextInput()->getText() ) );
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setTerminalFontSize( const StyleSheetLength& size ) {
mApp->getConfig().term.fontSize = size;
const Float fontSize = size.asPixels( 0, Sizef(), mApp->getDisplayDPI() );
@@ -370,20 +156,6 @@ void SettingsActions::setTerminalFontSize( const StyleSheetLength& size ) {
} );
}
void SettingsActions::setUIFontSize() {
UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT,
i18n( "set_ui_font_size", "Set the UI font size:" ) );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->getTextInput()->setText( mApp->getConfig().ui.fontSize.toString() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
setUIFontSize( StyleSheetLength( msgBox->getTextInput()->getText() ) );
msgBox->closeWindow();
} );
mApp->setFocusEditorOnClose( msgBox );
}
void SettingsActions::setUIFontSize( const StyleSheetLength& size ) {
mApp->getConfig().ui.fontSize = size;
const Float fontSize = size.asPixels( 0, Sizef(), mApp->getDisplayDPI() );
@@ -400,26 +172,11 @@ void SettingsActions::setUIFontSize( const StyleSheetLength& size ) {
}
}
} );
}
void SettingsActions::setUIPanelFontSize() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT, i18n( "set_side_panel_font_size", "Set side panel font size:" ) );
msgBox->setTitle( mApp->getWindowTitle() );
msgBox->getTextInput()->setText( mApp->getConfig().ui.panelFontSize.toString() );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
setUIPanelFontSize( StyleSheetLength( msgBox->getTextInput()->getText() ) );
msgBox->closeWindow();
} );
mApp->setFocusEditorOnClose( msgBox );
setUIPanelFontSize( mApp->getConfig().ui.panelFontSize );
}
void SettingsActions::setUIPanelFontSize( const StyleSheetLength& size ) {
mApp->getConfig().ui.panelFontSize = size;
// Update the CSS
auto selsFound = mApp->getUISceneNode()->getStyleSheet().findStyleFromSelectorName(
"#project_view > treeview::row > treeview::cell" );
if ( !selsFound.empty() ) {
@@ -429,10 +186,10 @@ void SettingsActions::setUIPanelFontSize( const StyleSheetLength& size ) {
}
UITreeView* treeView = mApp->getUISceneNode()->find<UITreeView>( "project_view" );
if ( !treeView )
return;
treeView->reloadStyle( true, true, true, true );
treeView->updateContentSize();
if ( treeView ) {
treeView->reloadStyle( true, true, true, true );
treeView->updateContentSize();
}
}
void SettingsActions::setScreenshotSavePath() {
@@ -461,58 +218,6 @@ void SettingsActions::setScreenshotSavePath() {
dialog->show();
}
void SettingsActions::setScreenshotFilenamePattern() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "set_screenshot_filename_pattern_message",
"Set the screenshot filename pattern:\nUses strftime format specifiers. The file "
"extension follows the selected screenshot format." ) );
msgBox->setTitle(
i18n( "set_screenshot_filename_pattern", "Set Screenshot Filename Pattern" ) );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->getTextInput()->setText( mApp->getConfig().screenshot.filenamePattern );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
std::string pattern = msgBox->getTextInput()->getText().toUtf8();
std::string filename = DateTimeController::formatCurrentDate( pattern );
if ( !DateTimeController::isValidDateFormat( pattern ) || filename.empty() ||
filename.find_first_of( "<>:\"/\\|?*" ) != std::string::npos ) {
msgBox->getTextInput()->addClass( "error" );
mApp->errorMsgBox( i18n( "invalid_screenshot_filename_pattern",
"The screenshot filename pattern is invalid." ) );
return;
}
mApp->getConfig().screenshot.filenamePattern = std::move( pattern );
mApp->saveConfig();
msgBox->closeWindow();
} );
}
void SettingsActions::setScreenshotSaveFormat() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::DROPDOWNLIST,
i18n( "set_screenshot_save_format_message", "Select the screenshot save format:" ) );
msgBox->setTitle( i18n( "set_screenshot_save_format", "Set Screenshot Save Format" ) );
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->getDropDownList()->getListBox()->addListBoxItems(
{ "PNG", "JPG", "WEBP", "QOI", "BMP", "TGA", "DDS" } );
msgBox->getDropDownList()->getListBox()->setSelected(
String( mApp->getConfig().screenshot.saveFormat ).toUpper() );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
String formatText( msgBox->getDropDownList()->getText() );
std::string format = formatText.toLower().toUtf8();
if ( Image::extensionToSaveType( format ) == Image::SaveType::Unknown ) {
mApp->errorMsgBox(
i18n( "invalid_screenshot_save_format", "Invalid screenshot save format." ) );
return;
}
mApp->getConfig().screenshot.saveFormat = std::move( format );
mApp->saveConfig();
msgBox->closeWindow();
} );
}
String SettingsActions::i18n( const std::string& key, const String& def ) {
return mApp->i18n( key, def );
}
-22
View File
@@ -17,38 +17,16 @@ class SettingsActions {
void ecodeSource();
void setLineBreakingColumn();
void setLineSpacing();
void setCursorBlinkingTime();
void setIndentTabCharacter();
void setTabOutChars();
void setFoldRefreshFreq();
void setUIScaleFactor();
void setUIFontSize();
void setUIFontSize( const StyleSheetLength& size );
void setEditorFontSize();
void setEditorFontSize( const StyleSheetLength& size );
void setTerminalFontSize();
void setTerminalFontSize( const StyleSheetLength& size );
void setUIPanelFontSize();
void setUIPanelFontSize( const StyleSheetLength& size );
void setScreenshotSavePath();
void setScreenshotFilenamePattern();
void setScreenshotSaveFormat();
private:
App* mApp{ nullptr };
File diff suppressed because it is too large Load Diff
-26
View File
@@ -30,24 +30,12 @@ class SettingsMenu {
UIMenu* createWindowMenu();
UIMenu* createRendererMenu();
UIMenu* createViewMenu();
UIPopUpMenu* createToolsMenu();
UIMenu* createHelpMenu();
UIMenu* createThemesMenu();
UIMenu* createLanguagesMenu();
UIMenu* createFontHintMenu();
UIMenu* createFontAntiAliasingMenu();
UIMenu* createFontsMenu();
void updateTerminalMenu();
void updateEditMenu();
@@ -60,8 +48,6 @@ class SettingsMenu {
void updateViewMenu();
void updateGlobalDocumentSettingsMenu();
void showProjectTreeMenu();
void createProjectTreeMenu();
@@ -108,33 +94,21 @@ class SettingsMenu {
UISceneNode* mUISceneNode{ nullptr };
UICodeEditorSplitter* mSplitter{ nullptr };
UIPopUpMenu* mDocMenu{ nullptr };
UIPopUpMenu* mGlobalMenu{ nullptr };
UIPopUpMenu* mTerminalMenu{ nullptr };
UIPopUpMenu* mViewMenu{ nullptr };
UIPopUpMenu* mWindowMenu{ nullptr };
UIPopUpMenu* mRendererMenu{ nullptr };
UIPopUpMenu* mToolsMenu{ nullptr };
UIPopUpMenu* mProjectTreeMenu{ nullptr };
UIPopUpMenu* mProjectDocMenu{ nullptr };
UIPopUpMenu* mProjectMenu{ nullptr };
UIPopUpMenu* mHExtLanguageTypeMenu{ nullptr };
UIPopUpMenu* mEditMenu{ nullptr };
UIPopUpMenu* mDateMenu{ nullptr };
UIPopUpMenu* mHelpMenu{ nullptr };
UIPopUpMenu* mLineWrapMenu{ nullptr };
UIPopUpMenu* mCodeFoldingMenu{ nullptr };
UIPopUpMenu* mTabBarMenu{ nullptr };
UIMenuBar* mMenuBar{ nullptr };
UIPopUpMenu* mFontHintMenu{ nullptr };
UIPopUpMenu* mFontAntiAliasingMenu{ nullptr };
UIPopUpMenu* mFontsMenu{ nullptr };
std::vector<UIPopUpMenu*> mFileTypeMenus;
Float mFileTypeMenusCreatedWithHeight{ 0 };
std::vector<UIPopUpMenu*> mColorSchemeMenus;
Float mColorSchemeMenusCreatedWithHeight{ 0 };
void forEachTerminal( const std::function<void( UITerminal* )> fn );
void updateDateMenu();
UITerminal* getCurrentTerminal() const;
+110 -29
View File
@@ -153,6 +153,13 @@ static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
padding-bottom: 6dp;
border-bottom: 1dp solid var(--tab-line);
}
.settings_panel .settings_subcategory_heading {
font-size: 12dp;
font-weight: bold;
margin: 18dp 4dp 3dp 4dp;
padding-bottom: 6dp;
border-bottom: 1dp solid var(--tab-line);
}
.settings_panel .settings_option {
border-bottom: 1dp solid var(--disabled-border);
padding: 9dp 4dp 11dp 4dp;
@@ -246,6 +253,9 @@ static constexpr const char* SETTINGS_ROW_LAYOUT = R"xml(
static constexpr const char* SETTINGS_CATEGORY_HEADING_LAYOUT = R"xml(
<TextView lw="mp" lh="wc" class="settings_category_heading" visible="false" focusable="false" />
)xml";
static constexpr const char* SETTINGS_SUBCATEGORY_HEADING_LAYOUT = R"xml(
<TextView lw="mp" lh="wc" class="settings_subcategory_heading" visible="false" focusable="false" />
)xml";
static constexpr const char* SETTINGS_BOOL_LAYOUT = R"xml(<CheckBox class="settings_bool" />)xml";
static constexpr const char* SETTINGS_CHOICE_LAYOUT =
R"xml(<DropDownList class="settings_choice" />)xml";
@@ -285,25 +295,58 @@ void SettingsPanel::PanelState::reset() {
categorySearchText.clear();
categoryTitles.clear();
categoryHeadings.clear();
subcategoryHeadings.clear();
bindings.clear();
selectedCategory.clear();
categoryFilter.clear();
}
void SettingsPanel::show( Scope scope ) {
void SettingsPanel::show( Scope scope, const std::string& category ) {
auto& panel = state( scope );
if ( panel.window ) {
panel.window->show();
panel.window->toFront();
selectCategory( panel, category );
panel.categories->setFocus();
return;
}
Clock c;
create( scope );
selectCategory( panel, category );
Log::info( "Settings Panel %s created in %s", scope == Scope::User ? "User" : "Project",
c.getElapsedTime().toString() );
}
void SettingsPanel::selectCategory( PanelState& panel, const std::string& category ) {
if ( category.empty() || !panel.categories )
return;
String title;
if ( String::endsWith( category, ".*" ) ) {
const std::string prefix = category.substr( 0, category.size() - 1 );
for ( const auto& [parent, children] : panel.categoryItems ) {
if ( !children.empty() ) {
auto id = panel.categoryIds.find( parent + '/' + children.front() );
if ( id != panel.categoryIds.end() && String::startsWith( id->second, prefix ) ) {
title = String::fromUtf8( parent );
break;
}
}
}
} else if ( auto found = panel.categoryTitles.find( category );
found != panel.categoryTitles.end() ) {
title = found->second;
}
if ( title.empty() )
return;
panel.selectedCategory = category;
auto index = panel.categories->findRowWithText(
title.toUtf8(), true, UIAbstractView::FindRowWithTextMatchKind::Equals );
if ( index.isValid() )
panel.categories->setSelection( index );
filter( panel );
panel.scroll->getVerticalScrollBar()->setValue( 0, false );
}
void SettingsPanel::create( Scope scope ) {
auto& panel = state( scope );
UIWindow::StyleConfig config{ UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL };
@@ -409,6 +452,16 @@ void SettingsPanel::addCategory( PanelState& panel, const std::string& id, const
panel.selectedCategory = id;
}
void SettingsPanel::addSubcategoryHeading( PanelState& panel, const std::string& category,
const String& name ) {
auto* heading =
mApp->getUISceneNode()
->loadLayoutFromString( SETTINGS_SUBCATEGORY_HEADING_LAYOUT, panel.settings )
->asType<UITextView>();
heading->setText( name );
panel.subcategoryHeadings.push_back( { category, name, heading } );
}
void SettingsPanel::setupCategories( PanelState& panel ) {
auto model = SettingsCategoryModel::create( panel.categoryItems, panel.categoryIds );
panel.categoryModel = model;
@@ -550,22 +603,28 @@ void SettingsPanel::addInteger( PanelState& panel, SettingBinding binding, int m
void SettingsPanel::addText( PanelState& panel, SettingBinding binding,
std::function<std::string()> get,
std::function<bool( const std::string& )> set ) {
std::function<bool( const std::string& )> set,
bool commitOnFocusLoss ) {
auto* row = createRow( panel, binding );
auto* input = mApp->getUISceneNode()
->loadLayoutFromString( SETTINGS_TEXT_LAYOUT,
row->find<UILinearLayout>( "setting_control" ) )
->asType<UITextInput>();
input->setText( String::fromUtf8( get() ) );
panel.connections +=
input->connect( Event::OnTextChanged, [input, set = std::move( set )]( const Event* ) {
std::string text = input->getText().toUtf8();
if ( !set( text ) ) {
input->addClass( "error" );
return;
}
input->removeClass( "error" );
} );
auto commit = [input, set = std::move( set )]( const Event* ) {
std::string text = input->getText().toUtf8();
if ( !set( text ) ) {
input->addClass( "error" );
return;
}
input->removeClass( "error" );
};
if ( commitOnFocusLoss ) {
panel.connections += input->connect( Event::OnPressEnter, commit );
panel.connections += input->connect( Event::OnFocusLoss, std::move( commit ) );
} else {
panel.connections += input->connect( Event::OnTextChanged, std::move( commit ) );
}
panel.bindings.emplace_back( std::move( binding ) );
}
@@ -1069,16 +1128,20 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
? TextFormat::LineEnding::CRLF
: ( selected == 2 ? TextFormat::LineEnding::CR : TextFormat::LineEnding::LF );
} );
const String autoCloseGroup =
mApp->i18n( "auto_close_brackets_and_tags", "Auto-Close Brackets & Tags" );
addSubcategoryHeading( panel, "editor.document", autoCloseGroup );
addBool( panel,
{ "autoCloseXMLTags", "editor.document",
mApp->i18n( "auto_close_xml_tags", "Auto Close XML Tags" ),
mApp->i18n( "auto_close_xml_tags_desc", "Automatically insert closing XML tags." ) },
mApp->i18n( "auto_close_xml_tags_desc", "Automatically insert closing XML tags." ),
autoCloseGroup },
&mApp->getConfig().editor.autoCloseXMLTags, [this]( bool value ) {
mApp->getSplitter()->forEachEditor(
[value]( UICodeEditor* editor ) { editor->setAutoCloseXMLTags( value ); } );
} );
auto addAutoClosePair = [this, &panel]( std::string id, const char* nameKey, const char* name,
std::string pair ) {
auto addAutoClosePair = [this, &panel, &autoCloseGroup]( std::string id, const char* nameKey,
const char* name, std::string pair ) {
auto containsPair = [this, pair] {
auto pairs = String::split( mApp->getConfig().editor.autoCloseBrackets, ',' );
return std::find( pairs.begin(), pairs.end(), pair ) != pairs.end();
@@ -1086,7 +1149,8 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
addBool( panel,
{ std::move( id ), "editor.document", mApp->i18n( nameKey, name ),
mApp->i18n( "auto_close_pair_desc",
"Automatically insert the matching closing character." ) },
"Automatically insert the matching closing character." ),
autoCloseGroup },
std::move( containsPair ), [this, pair = std::move( pair )]( bool enabled ) {
auto pairs = String::split( mApp->getConfig().editor.autoCloseBrackets, ',' );
auto found = std::find( pairs.begin(), pairs.end(), pair );
@@ -1273,13 +1337,15 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
[this] { mApp->runCommand( "fallback-font" ); } );
auto addFontSize = [this, &panel]( SettingBinding binding, std::function<std::string()> get,
std::function<void( const StyleSheetLength& )> set ) {
addText( panel, std::move( binding ), std::move( get ),
[set = std::move( set )]( const std::string& text ) {
if ( !StyleSheetLength::isLength( text ) )
return false;
set( StyleSheetLength::fromString( text ) );
return true;
} );
addText(
panel, std::move( binding ), std::move( get ),
[set = std::move( set )]( const std::string& text ) {
if ( !StyleSheetLength::isLength( text ) )
return false;
set( StyleSheetLength::fromString( text ) );
return true;
},
true );
};
addFontSize(
{ "uiFontSize", "appearance.fonts", mApp->i18n( "ui_font_size", "UI Font Size" ),
@@ -1364,11 +1430,12 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
} );
auto addFontFeature = [this, &panel]( std::string id, const char* nameKey, const char* name,
const char* descriptionKey, const char* description,
Uint32 feature, bool editorFeature ) {
Uint32 feature, bool editorFeature,
const String& group ) {
addBool(
panel,
{ std::move( id ), "appearance.fonts", mApp->i18n( nameKey, name ),
mApp->i18n( descriptionKey, description ) },
mApp->i18n( descriptionKey, description ), group },
[this, feature, editorFeature] {
const Uint32 features = editorFeature ? mApp->getConfig().editor.fontFeatures
: mApp->getConfig().ui.fontFeatures;
@@ -1392,22 +1459,26 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
};
for ( bool editorFeature : { false, true } ) {
const std::string prefix = editorFeature ? "editor" : "ui";
const String group = editorFeature
? mApp->i18n( "editor_font_features", "Editor Font Features" )
: mApp->i18n( "ui_font_features", "UI Font Features" );
addSubcategoryHeading( panel, "appearance.fonts", group );
addFontFeature( prefix + "StandardLigatures", "standard_ligatures",
"Standard Ligatures (liga)", "standard_ligatures_desc",
"Typographic combinations such as fi, fl, and ffi, depending on the font.",
TextHints::StandardLigatures, editorFeature );
TextHints::StandardLigatures, editorFeature, group );
addFontFeature( prefix + "ContextualAlternates", "contextual_alternates",
"Contextual Alternates (calt)", "contextual_alternates_desc",
"Context-dependent alternatives, including many programming ligatures.",
TextHints::ContextualAlternates, editorFeature );
TextHints::ContextualAlternates, editorFeature, group );
addFontFeature( prefix + "ContextualLigatures", "contextual_ligatures",
"Contextual Ligatures (clig)", "contextual_ligatures_desc",
"Ligatures applied in specific contexts to improve readability.",
TextHints::ContextualLigatures, editorFeature );
TextHints::ContextualLigatures, editorFeature, group );
addFontFeature( prefix + "DiscretionaryLigatures", "discretionary_ligatures",
"Discretionary Ligatures (dlig)", "discretionary_ligatures_desc",
"Optional decorative or stylistic ligatures provided by the font.",
TextHints::DiscretionaryLigatures, editorFeature );
TextHints::DiscretionaryLigatures, editorFeature, group );
}
addCategory( panel, "editor.advanced", mApp->i18n( "editor", "Editor" ),
@@ -1968,7 +2039,7 @@ void SettingsPanel::addProjectSettings( PanelState& panel ) {
HExtLanguageType::ObjectiveC, HExtLanguageType::ObjectiveCPP };
auto value = values[std::min( selected, size_t{ 4 } )];
mApp->getProjectConfig().hExtLanguageType = value;
mApp->getSplitter()->forEachEditor( [this, value]( UICodeEditor* editor ) {
mApp->getSplitter()->forEachEditor( [value]( UICodeEditor* editor ) {
auto& document = editor->getDocument();
document.setHExtLanguageType( value );
if ( document.getFileInfo().getExtension() == "h" ) {
@@ -1997,6 +2068,7 @@ void SettingsPanel::filter( PanelState& panel ) {
for ( const auto& binding : panel.bindings ) {
if ( String::icontains( binding.name, query ) ||
String::icontains( binding.description, query ) ||
String::icontains( binding.group, query ) ||
String::icontains( binding.id, queryUtf8 ) )
matchingCategories.insert( binding.category );
}
@@ -2020,9 +2092,18 @@ void SettingsPanel::filter( PanelState& panel ) {
( aggregate && String::startsWith( binding.category, aggregatePrefix ) )
: categoryMatches || String::icontains( binding.name, query ) ||
String::icontains( binding.description, query ) ||
String::icontains( binding.group, query ) ||
String::icontains( binding.id, queryUtf8 );
binding.row->setVisible( matches );
}
for ( auto& subcategory : panel.subcategoryHeadings ) {
const bool hasVisibleSetting = std::any_of(
panel.bindings.begin(), panel.bindings.end(), [&subcategory]( const auto& binding ) {
return binding.category == subcategory.category &&
binding.group == subcategory.name && binding.row && binding.row->isVisible();
} );
subcategory.heading->setVisible( hasVisibleSetting );
}
}
} // namespace ecode
+12 -2
View File
@@ -16,7 +16,7 @@ class SettingsPanel {
explicit SettingsPanel( App* app );
void show( Scope scope );
void show( Scope scope, const std::string& category = {} );
protected:
struct SettingBinding {
@@ -24,8 +24,14 @@ class SettingsPanel {
std::string category;
String name;
String description;
String group;
UIWidget* row{ nullptr };
};
struct SubcategoryHeading {
std::string category;
String name;
UITextView* heading{ nullptr };
};
struct PanelState {
UIWindow* window{ nullptr };
@@ -42,6 +48,7 @@ class SettingsPanel {
UnorderedMap<std::string, String> categorySearchText;
UnorderedMap<std::string, String> categoryTitles;
UnorderedMap<std::string, UITextView*> categoryHeadings;
std::vector<SubcategoryHeading> subcategoryHeadings;
std::vector<SettingBinding> bindings;
std::string selectedCategory;
std::string categoryFilter;
@@ -55,10 +62,13 @@ class SettingsPanel {
PanelState& state( Scope scope );
void create( Scope scope );
void selectCategory( PanelState& state, const std::string& category );
void addUserSettings( PanelState& state );
void addProjectSettings( PanelState& state );
void addCategory( PanelState& state, const std::string& id, const String& parent,
const String& name );
void addSubcategoryHeading( PanelState& state, const std::string& category,
const String& name );
void setupCategories( PanelState& state );
void addBool( PanelState& state, SettingBinding binding, bool* value,
std::function<void( bool )> apply = {} );
@@ -73,7 +83,7 @@ class SettingsPanel {
void addInteger( PanelState& state, SettingBinding binding, int min, int max,
std::function<int()> get, std::function<void( int )> set );
void addText( PanelState& state, SettingBinding binding, std::function<std::string()> get,
std::function<bool( const std::string& )> set );
std::function<bool( const std::string& )> set, bool commitOnFocusLoss = false );
void addFloat( PanelState& state, SettingBinding binding, double min, double max, double step,
std::function<double()> get, std::function<void( double )> set );
void addAction( PanelState& state, SettingBinding binding, const String& buttonText,