ecode: add centralized settings panel

- add user and project settings windows with searchable tree navigation
  - centralize setting definitions with reusable bool, choice, text, numeric, and action controls
  - apply supported configuration changes reactively
  - add inline validation and editable choices for complex value types
  - disable project document options while inheriting global settings
  - migrate font, theme, language, renderer, screenshot, and terminal settings
  - improve keyboard navigation and modal shortcut isolation
  - make configuration dialogs modal
  - persist settings when the panel closes
  - add responsive dark and light theme styling
  - update translations for new settings labels and descriptions
  - add regression coverage for modal keybinding propagation
This commit is contained in:
Martín Lucas Golini
2026-08-24 13:07:43 -03:00
parent 288aa931fe
commit 30cf6d9e97
11 changed files with 528 additions and 183 deletions

View File

@@ -148,6 +148,7 @@ Diese Operation kann nicht rückgängig gemacht werden!</string>
<string name="configure_ellipsis">Verwalten...</string>
<string name="configure_ignore_files_ellipsis">Ignorierte Dateien verwalten...</string>
<string name="configure_terminal_scrollback">Konsolenverlaufslänge ändern</string>
<string name="terminal_scrollback">Terminal-Verlauf</string>
<string name="configure_terminal_shell">Konsolen-Shell wählen</string>
<string name="configured_debugger">Gewählter Debugger</string>
<string name="configured_formatter">Gewählter Formatter</string>
@@ -1024,10 +1025,10 @@ Verwendet strftime-Formatbezeichner. Die Dateierweiterung richtet sich nach dem
<string name="ui_panel_font_size_desc">Schriftgröße der Seitenbereiche festlegen.</string>
<string name="editor_font_size_desc">Standardschriftgröße des Code-Editors festlegen.</string>
<string name="terminal_font_size_desc">Standardschriftgröße des Terminals festlegen.</string>
<string name="ui_scale_factor_desc">Gesamte Benutzeroberfläche skalieren.</string>
<string name="line_spacing_desc">Zusätzlichen vertikalen Abstand zwischen Editorzeilen festlegen.</string>
<string name="cursor_blinking_time_desc">Blinkintervall des Textcursors festlegen.</string>
<string name="folds_refresh_freq_desc">Häufigkeit der Neuberechnung von Faltungsbereichen festlegen.</string>
<string name="ui_scale_factor_desc">Gesamte Benutzeroberfläche von 1 bis 6 skalieren. Neustart erforderlich.</string>
<string name="line_spacing_desc">Zusätzlichen vertikalen Abstand zwischen Editorzeilen festlegen. Zum Deaktivieren 0 einstellen.</string>
<string name="cursor_blinking_time_desc">Blinkintervall des Textcursors festlegen. Zum Deaktivieren 0 einstellen.</string>
<string name="folds_refresh_freq_desc">Häufigkeit der Neuberechnung von Faltungsbereichen festlegen (mindestens 1 Sekunde).</string>
<string name="screenshot_save_path_desc">Speicherort für Bildschirmfotos auswählen.</string>
<string name="screenshot_filename_pattern_desc">Zeitstempel-Dateinamensmuster für Bildschirmfotos konfigurieren.</string>
<string name="screenshot_save_format_desc">Bildformat für Bildschirmfotos auswählen.</string>

View File

@@ -132,6 +132,7 @@ This operation cannot be reverted!</string>
<string name="configure_ellipsis">Configure...</string>
<string name="configure_ignore_files_ellipsis">Configure Ignore Files...</string>
<string name="configure_terminal_scrollback">Configure Terminal Scrollback</string>
<string name="terminal_scrollback">Terminal Scrollback</string>
<string name="configure_terminal_shell">Configure Terminal Shell</string>
<string name="configured_debugger">Configured debugger</string>
<string name="configured_formatter">Configured formatter</string>
@@ -1008,10 +1009,10 @@ Uses strftime format specifiers. The file extension follows the selected screens
<string name="ui_panel_font_size_desc">Set the font size used by side panels.</string>
<string name="editor_font_size_desc">Set the default code editor font size.</string>
<string name="terminal_font_size_desc">Set the default integrated terminal font size.</string>
<string name="ui_scale_factor_desc">Scale the complete user interface.</string>
<string name="line_spacing_desc">Set additional vertical spacing between editor lines.</string>
<string name="cursor_blinking_time_desc">Set the text cursor blink interval.</string>
<string name="folds_refresh_freq_desc">Set how frequently code folding ranges are recalculated.</string>
<string name="ui_scale_factor_desc">Scale the complete user interface from 1 to 6. Restart required.</string>
<string name="line_spacing_desc">Set additional vertical spacing between editor lines. Set 0 to disable.</string>
<string name="cursor_blinking_time_desc">Set the text cursor blink interval. Set 0 to disable.</string>
<string name="folds_refresh_freq_desc">Set how frequently code folding ranges are recalculated (minimum 1 second).</string>
<string name="screenshot_save_path_desc">Choose where screenshots are saved.</string>
<string name="screenshot_filename_pattern_desc">Configure the timestamp-based screenshot filename pattern.</string>
<string name="screenshot_save_format_desc">Choose the image format for screenshots.</string>

View File

@@ -133,6 +133,7 @@ Cette opération est irréversible !</string>
<string name="configure_ellipsis">Configurer...</string>
<string name="configure_ignore_files_ellipsis">Configurer les fichiers ignorés...</string>
<string name="configure_terminal_scrollback">Configurer le défilement de l'historique du terminal</string>
<string name="terminal_scrollback">Historique du terminal</string>
<string name="configure_terminal_shell">Configurer l'interpréteur de commandes du terminal</string>
<string name="configured_debugger">Débogueur configuré</string>
<string name="configured_formatter">Formatteur configuré</string>
@@ -1003,10 +1004,10 @@ Utilise les spécificateurs de format strftime. Lextension du fichier corresp
<string name="ui_panel_font_size_desc">Set the font size used by side panels.</string>
<string name="editor_font_size_desc">Set the default code editor font size.</string>
<string name="terminal_font_size_desc">Set the default integrated terminal font size.</string>
<string name="ui_scale_factor_desc">Scale the complete user interface.</string>
<string name="line_spacing_desc">Set additional vertical spacing between editor lines.</string>
<string name="cursor_blinking_time_desc">Set the text cursor blink interval.</string>
<string name="folds_refresh_freq_desc">Set how frequently code folding ranges are recalculated.</string>
<string name="ui_scale_factor_desc">Mettre linterface à léchelle de 1 à 6. Redémarrage requis.</string>
<string name="line_spacing_desc">Définir lespace vertical supplémentaire entre les lignes. Utiliser 0 pour désactiver.</string>
<string name="cursor_blinking_time_desc">Définir lintervalle de clignotement du curseur. Utiliser 0 pour désactiver.</string>
<string name="folds_refresh_freq_desc">Définir la fréquence de recalcul des zones de pliage du code (minimum 1 seconde).</string>
<string name="screenshot_save_path_desc">Choose where screenshots are saved.</string>
<string name="screenshot_filename_pattern_desc">Configure the timestamp-based screenshot filename pattern.</string>
<string name="screenshot_save_format_desc">Choose the image format for screenshots.</string>

View File

@@ -110,6 +110,7 @@
<string name="configure_default_shell">配置默认终端</string>
<string name="configure_ignore_files_ellipsis">设置忽略的文件...</string>
<string name="configure_terminal_scrollback">Configure Terminal Scrollback</string>
<string name="terminal_scrollback">终端回滚行数</string>
<string name="configure_terminal_shell">设置终端程序</string>
<string name="confirm_build_delete">你确定要删除构建设置吗?</string>
<string name="confirm_close_folder">确定要关闭此文件夹吗?
@@ -782,10 +783,10 @@ file in the directory tree.</string>
<string name="ui_panel_font_size_desc">Set the font size used by side panels.</string>
<string name="editor_font_size_desc">Set the default code editor font size.</string>
<string name="terminal_font_size_desc">Set the default integrated terminal font size.</string>
<string name="ui_scale_factor_desc">Scale the complete user interface.</string>
<string name="line_spacing_desc">Set additional vertical spacing between editor lines.</string>
<string name="cursor_blinking_time_desc">Set the text cursor blink interval.</string>
<string name="folds_refresh_freq_desc">Set how frequently code folding ranges are recalculated.</string>
<string name="ui_scale_factor_desc">将整个用户界面缩放为 1 到 6。需要重新启动。</string>
<string name="line_spacing_desc">设置编辑器行之间的额外垂直间距。设置为 0 可禁用。</string>
<string name="cursor_blinking_time_desc">设置文本光标闪烁间隔。设置为 0 可禁用。</string>
<string name="folds_refresh_freq_desc">设置重新计算代码折叠区域的频率(最少 1 秒)。</string>
<string name="screenshot_save_path_desc">Choose where screenshots are saved.</string>
<string name="screenshot_filename_pattern_desc">Configure the timestamp-based screenshot filename pattern.</string>
<string name="screenshot_save_format_desc">Choose the image format for screenshots.</string>

View File

@@ -1771,7 +1771,8 @@ Uint32 UIWindow::onKeyDown( const KeyEvent& event ) {
executeKeyBindingCommand( cmd );
return 1;
}
return UIWidget::onKeyDown( event );
Uint32 handled = UIWidget::onKeyDown( event );
return handled || isModal();
}
KeyBindings& UIWindow::getKeyBindings() {

View File

@@ -2,6 +2,7 @@
#include <eepp/graphics/fontfamily.hpp>
#include <eepp/graphics/fonttruetype.hpp>
#include <eepp/scene/eventdispatcher.hpp>
#include <eepp/scene/scenemanager.hpp>
#include <eepp/system/filesystem.hpp>
#include <eepp/system/sys.hpp>
@@ -10,6 +11,7 @@
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uithememanager.hpp>
#include <eepp/ui/uiwidget.hpp>
#include <eepp/ui/uiwindow.hpp>
#include <eepp/window/cursor.hpp>
#include <eepp/window/engine.hpp>
#include <eepp/window/input.hpp>
@@ -391,3 +393,31 @@ UTEST( UISceneNode, StyleStateUpdateAllowsWidgetCreation ) {
Engine::destroySingleton();
}
UTEST( UIWindow, ModalWindowStopsKeyBindingsFromReachingScene ) {
Engine::instance()->createWindow( WindowSettings( 1024, 768, "Modal Key Binding Test",
WindowStyle::Default, WindowBackend::Default,
32, {}, 1, false, true ),
ContextSettings( false, 0, 0, GLv_default, true, false ) );
UISceneNode* sceneNode = init_test_scene_node();
int executedCommands = 0;
sceneNode->setKeyBindingCommand( "global-command",
[&executedCommands] { executedCommands++; } );
sceneNode->addKeyBinding( { KEY_F, KEYMOD_LMETA | KEYMOD_LSHIFT }, "global-command" );
UIWindow* window = UIWindow::New();
window->setParent( sceneNode->getRoot() );
sceneNode->getEventDispatcher()->setFocusNode( window->getContainer() );
sceneNode->getEventDispatcher()->sendKeyDown( KEY_F, SCANCODE_UNKNOWN, 0,
KEYMOD_LMETA | KEYMOD_LSHIFT );
EXPECT_EQ( executedCommands, 1 );
window->setWindowFlags( window->getWinFlags() | UI_WIN_MODAL );
sceneNode->getEventDispatcher()->sendKeyDown( KEY_F, SCANCODE_UNKNOWN, 0,
KEYMOD_LMETA | KEYMOD_LSHIFT );
EXPECT_EQ( executedCommands, 1 );
Engine::destroySingleton();
}

View File

@@ -410,30 +410,31 @@ void SettingsActions::setUIPanelFontSize() {
msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
msgBox->showWhenReady();
msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
mApp->getConfig().ui.panelFontSize = StyleSheetLength( msgBox->getTextInput()->getText() );
// Update the CSS
auto selsFound = mApp->getUISceneNode()->getStyleSheet().findStyleFromSelectorName(
"#project_view > treeview::row > treeview::cell" );
if ( !selsFound.empty() ) {
for ( auto sel : selsFound )
sel->updatePropertyValue( "font-size",
mApp->getConfig().ui.panelFontSize.toString() );
mApp->getUISceneNode()->getStyleSheet().refreshCacheFromStyles( selsFound );
}
UITreeView* treeView = mApp->getUISceneNode()->find<UITreeView>( "project_view" );
if ( !treeView ) {
msgBox->closeWindow();
return;
}
treeView->reloadStyle( true, true, true, true );
treeView->updateContentSize();
setUIPanelFontSize( StyleSheetLength( msgBox->getTextInput()->getText() ) );
msgBox->closeWindow();
} );
mApp->setFocusEditorOnClose( msgBox );
}
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() ) {
for ( auto sel : selsFound )
sel->updatePropertyValue( "font-size", size.toString() );
mApp->getUISceneNode()->getStyleSheet().refreshCacheFromStyles( selsFound );
}
UITreeView* treeView = mApp->getUISceneNode()->find<UITreeView>( "project_view" );
if ( !treeView )
return;
treeView->reloadStyle( true, true, true, true );
treeView->updateContentSize();
}
void SettingsActions::setScreenshotSavePath() {
std::string initialPath = mApp->getConfig().screenshot.savePath;
if ( !FileSystem::isDirectory( initialPath ) ) {

View File

@@ -41,6 +41,7 @@ class SettingsActions {
void setTerminalFontSize( const StyleSheetLength& size );
void setUIPanelFontSize();
void setUIPanelFontSize( const StyleSheetLength& size );
void setScreenshotSavePath();

View File

@@ -1,7 +1,9 @@
#include "settingspanel.hpp"
#include "datetimecontroller.hpp"
#include "ecode.hpp"
#include "uitreeviewfs.hpp"
#include <deque>
#include <limits>
using namespace EE::UI::Models;
@@ -110,49 +112,82 @@ class SettingsCategoryModel final : public Model {
static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
<style>
<![CDATA[
.settings_panel #settings_sidebar {
background-color: var(--list-back);
padding: 10dp 8dp 8dp 8dp;
}
.settings_panel #settings_filter {
margin-top: 8dp;
margin-bottom: 8dp;
margin-bottom: 10dp;
}
.settings_panel #settings_categories * {
focusable: false;
}
.settings_panel #settings_categories {
background-color: var(--list-back);
}
.settings_panel #settings_categories treeview::row {
border-left: 0dp solid var(--primary);
transition: border-left-width 0.1s;
}
.settings_panel #settings_categories treeview::row:selected {
background-color: var(--tab-hover);
border-left: 2dp solid var(--primary);
}
.settings_panel #settings_categories treeview::row:selected treeview::cell {
color: var(--font);
}
.settings_panel #settings_rows {
padding-left: 16dp;
padding-right: 16dp;
margin-bottom: 8dp;
max-width: 820dp;
padding: 20dp 28dp 28dp 28dp;
layout-gravity: center_horizontal;
}
.settings_panel #settings_page_title {
font-size: 16dp;
padding-bottom: 2dp;
margin-bottom: 6dp;
font-size: 18dp;
margin-bottom: 18dp;
font-weight: bold;
font-style: shadow;
border-bottom: 1dp solid var(--tab-line);
}
.settings_panel .settings_category_heading {
font-size: 15dp;
font-size: 14dp;
font-weight: bold;
margin: 12dp 8dp 8dp 0dp;
font-style: shadow;
margin: 12dp 0dp 6dp 0dp;
padding-bottom: 6dp;
border-bottom: 1dp solid var(--tab-line);
}
.settings_panel .settings_option {
border-bottom: 1dp solid var(--tab-line);
padding: 4dp 8dp 8dp 0dp;
margin-bottom: 12dp;
border-bottom: 1dp solid var(--disabled-border);
padding: 9dp 4dp 11dp 4dp;
margin-bottom: 0dp;
}
.settings_panel .settings_option:disabled {
opacity: 0.5;
}
.settings_panel .settings_option_name {
font-weight: bold;
font-style: shadow;
font-style: normal;
}
.settings_panel .settings_option_description {
margin-top: 4dp;
color: var(--disabled-color);
font-size: 10dp;
margin-top: 3dp;
word-wrap: true;
font-style: normal;
}
@media (prefers-color-scheme: dark) {
.settings_panel .settings_option_name,
.settings_panel .settings_option_description {
font-style: shadow;
}
.settings_panel .settings_option_description {
color: var(--font-hint);
}
}
.settings_panel .settings_boolean_option .settings_option_name,
.settings_panel .settings_boolean_option .settings_option_description {
cursor: pointer;
}
.settings_panel .settings_option_control {
margin-left: 12dp;
layout-width: 210dp;
gravity: right|center_vertical;
margin-left: 20dp;
layout-gravity: center_vertical;
}
.settings_panel .settings_bool,
@@ -167,6 +202,14 @@ static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
layout-width: 190dp;
layout-height: wrap_content;
}
.settings_panel .settings_editable_choice {
layout-width: 210dp;
layout-height: wrap_content;
}
.settings_panel .settings_text {
layout-width: 210dp;
layout-height: wrap_content;
}
.settings_panel .settings_integer {
layout-width: 110dp;
layout-height: wrap_content;
@@ -174,12 +217,14 @@ static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
]]>
</style>
<vbox lw="mp" lh="mp" class="settings_panel">
<TextInput id="settings_filter" lw="mp" lh="wc" hint="@string(search_settings, Search settings...)" />
<Splitter id="settings_splitter" lw="mp" lh="0" lw8="1" orientation="horizontal" splitter-partition="160dp">
<TreeView id="settings_categories" lw="mp" lh="mp" />
<ScrollView id="settings_scroll" lw="0" lw8="1" lh="mp">
<Splitter id="settings_splitter" lw="mp" lh="mp" orientation="horizontal" splitter-partition="220dp">
<vbox id="settings_sidebar" lw="0" lh="0" min-width="160dp">
<TextInput id="settings_filter" lw="mp" lh="wc" hint="@string(search_settings, Search settings...)" />
<TreeView id="settings_categories" lw="mp" lh="mp" />
</vbox>
<ScrollView id="settings_scroll" lw="0" lw8="1" lh="mp" focusable="false">
<vbox id="settings_rows" lw="mp" lh="wc">
<TextView id="settings_page_title" lw="mp" lh="wc" />
<TextView id="settings_page_title" lw="mp" lh="wc" focusable="false" />
</vbox>
</ScrollView>
</Splitter>
@@ -190,24 +235,35 @@ static constexpr const char* SETTINGS_ROW_LAYOUT = R"xml(
<vbox lw="mp" lh="wc" class="settings_option">
<hbox lw="mp" lh="wc" class="settings_option_content">
<vbox lw="0" lw8="1" lh="wc">
<TextView id="setting_name" lw="mp" lh="wc" class="settings_option_name" />
<TextView id="setting_description" lw="mp" lh="wc" class="settings_option_description" />
<TextView id="setting_name" lw="mp" lh="wc" class="settings_option_name" focusable="false" />
<TextView id="setting_description" lw="mp" lh="wc" class="settings_option_description" focusable="false" />
</vbox>
<hbox id="setting_control" lw="wc" lh="wc" class="settings_option_control" />
</hbox>
</vbox>
)xml";
static constexpr const char* SETTINGS_CATEGORY_HEADING_LAYOUT =
R"xml(<TextView lw="mp" lh="wc" class="settings_category_heading" visible="false" />)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_BOOL_LAYOUT = R"xml(<CheckBox class="settings_bool" />)xml";
static constexpr const char* SETTINGS_CHOICE_LAYOUT =
R"xml(<DropDownList class="settings_choice" />)xml";
static constexpr const char* SETTINGS_EDITABLE_CHOICE_LAYOUT =
R"xml(<ComboBox class="settings_editable_choice" popup-to-root="true" />)xml";
static constexpr const char* SETTINGS_INTEGER_LAYOUT =
R"xml(<SpinBox class="settings_integer" />)xml";
static constexpr const char* SETTINGS_TEXT_LAYOUT = R"xml(<TextInput class="settings_text" />)xml";
static constexpr const char* SETTINGS_ACTION_LAYOUT =
R"xml(<PushButton class="settings_action" />)xml";
static void disableTabFocusTree( Node* node ) {
if ( node->isWidget() )
node->asType<UIWidget>()->unsetTabFocusable();
for ( auto* child = node->getFirstChild(); child; child = child->getNextNode() )
disableTabFocusTree( child );
}
SettingsPanel::SettingsPanel( App* app ) : mApp( app ) {}
SettingsPanel::PanelState& SettingsPanel::state( Scope scope ) {
@@ -242,7 +298,10 @@ void SettingsPanel::show( Scope scope ) {
panel.categories->setFocus();
return;
}
Clock c;
create( scope );
Log::info( "Settings Panel %s created in %s", scope == Scope::User ? "User" : "Project",
c.getElapsedTime().toString() );
}
void SettingsPanel::create( Scope scope ) {
@@ -274,11 +333,31 @@ void SettingsPanel::create( Scope scope ) {
panel.scroll = layout->find<UIScrollView>( "settings_scroll" );
panel.scroll->setVerticalScrollMode( ScrollBarMode::Auto );
panel.scroll->setHorizontalScrollMode( ScrollBarMode::AlwaysOff );
disableTabFocusTree( panel.categories->getVerticalScrollBar() );
disableTabFocusTree( panel.categories->getHorizontalScrollBar() );
disableTabFocusTree( panel.scroll->getVerticalScrollBar() );
disableTabFocusTree( panel.scroll->getHorizontalScrollBar() );
panel.window->setKeyBindingCommand( "focusSettingsFilter", [&panel] {
panel.search->setFocus();
panel.search->getDocument().selectAll();
} );
panel.window->setKeyBindingCommand( "focusSettingsCategories", [&panel] {
auto selected = panel.categories->getSelection().first();
if ( selected.isValid() )
panel.categories->setSelection( selected );
panel.categories->setFocus();
} );
panel.window->getKeyBindings().addKeybind( { KEY_F, KeyMod::getDefaultModifier() },
"focusSettingsFilter" );
panel.window->getKeyBindings().addKeybind(
{ KEY_E, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "focusSettingsCategories" );
panel.settings->beginAttributesTransaction();
if ( scope == Scope::User )
addUserSettings( panel );
else
addProjectSettings( panel );
setupCategories( panel );
panel.settings->endAttributesTransaction();
filter( panel );
panel.connections += panel.search->connect(
Event::OnTextChanged, [this, &panel]( const Event* ) { filter( panel ); } );
@@ -426,17 +505,87 @@ void SettingsPanel::addChoice( PanelState& panel, SettingBinding binding,
panel.bindings.emplace_back( std::move( binding ) );
}
void SettingsPanel::addEditableChoice( PanelState& panel, SettingBinding binding,
const std::vector<String>& choices,
std::function<String()> get,
std::function<bool( const String& )> set ) {
auto* row = createRow( panel, binding );
auto* combo = mApp->getUISceneNode()
->loadLayoutFromString( SETTINGS_EDITABLE_CHOICE_LAYOUT,
row->find<UILinearLayout>( "setting_control" ) )
->asType<UIComboBox>();
combo->getListBox()->addListBoxItems( choices );
combo->setText( get() );
panel.connections +=
combo->connect( Event::OnValueChange, [combo, set = std::move( set )]( const Event* ) {
if ( !set( combo->getText() ) ) {
combo->addClass( "error" );
combo->getDropDownList()->addClass( "error" );
return;
}
combo->removeClass( "error" );
combo->getDropDownList()->removeClass( "error" );
} );
panel.bindings.emplace_back( std::move( binding ) );
}
void SettingsPanel::addInteger( PanelState& panel, SettingBinding binding, int min, int max,
int* value, std::function<void( int )> apply ) {
std::function<int()> get, std::function<void( int )> set ) {
auto* row = createRow( panel, binding );
auto* spin = mApp->getUISceneNode()
->loadLayoutFromString( SETTINGS_INTEGER_LAYOUT,
row->find<UILinearLayout>( "setting_control" ) )
->asType<UISpinBox>();
spin->setMinValue( min )->setMaxValue( max );
auto valueBinding = UIDataBind<int>::New( value, spin );
valueBinding->onValueChangeCb = std::move( apply );
panel.bindingGroup += std::move( valueBinding );
spin->unsetTabFocusable();
spin->getButtonPushUp()->asType<UIWidget>()->unsetTabFocusable();
spin->getButtonPushDown()->asType<UIWidget>()->unsetTabFocusable();
spin->setValue( get() );
panel.connections +=
spin->connect( Event::OnValueChange, [spin, set = std::move( set )]( const Event* ) {
set( static_cast<int>( spin->getValue() ) );
} );
panel.bindings.emplace_back( std::move( binding ) );
}
void SettingsPanel::addText( PanelState& panel, SettingBinding binding,
std::function<std::string()> get,
std::function<bool( const std::string& )> set ) {
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" );
} );
panel.bindings.emplace_back( std::move( binding ) );
}
void SettingsPanel::addFloat( PanelState& panel, SettingBinding binding, double min, double max,
double step, std::function<double()> get,
std::function<void( double )> set ) {
auto* row = createRow( panel, binding );
auto* spin = mApp->getUISceneNode()
->loadLayoutFromString( SETTINGS_INTEGER_LAYOUT,
row->find<UILinearLayout>( "setting_control" ) )
->asType<UISpinBox>();
spin->setMinValue( min )->setMaxValue( max )->setClickStep( step );
spin->allowFloatingPoint( true )->setValue( get() );
spin->unsetTabFocusable();
spin->getButtonPushUp()->asType<UIWidget>()->unsetTabFocusable();
spin->getButtonPushDown()->asType<UIWidget>()->unsetTabFocusable();
panel.connections +=
spin->connect( Event::OnValueChange, [spin, set = std::move( set )]( const Event* ) {
set( spin->getValue() );
} );
panel.bindings.emplace_back( std::move( binding ) );
}
@@ -453,6 +602,43 @@ void SettingsPanel::addAction( PanelState& panel, SettingBinding binding, const
panel.bindings.emplace_back( std::move( binding ) );
}
static void setNodeTreeEnabled( Node* node, bool enabled ) {
node->setEnabled( enabled );
for ( Node* child = node->getFirstChild(); child; child = child->getNextNode() )
setNodeTreeEnabled( child, enabled );
}
static bool parseNonNegativeTime( const std::string& text, Time& time ) {
auto parts = String::split( text, " " );
bool hasValue = false;
for ( auto& part : parts ) {
String::trimInPlace( part );
if ( part.empty() )
continue;
size_t suffixLength =
String::endsWith( part, "ms" )
? 2
: ( String::endsWith( part, "s" ) || String::endsWith( part, "m" ) ? 1 : 0 );
std::string number = part.substr( 0, part.size() - suffixLength );
double value;
if ( number.empty() || !String::fromString( value, number ) || value < 0 )
return false;
hasValue = true;
}
if ( !hasValue )
return false;
time = Time::fromString( text );
return true;
}
void SettingsPanel::setCategoryEnabled( PanelState& panel, const std::string& category,
bool enabled, const std::string& excludedSetting ) {
for ( auto& binding : panel.bindings ) {
if ( binding.category == category && binding.id != excludedSetting && binding.row )
setNodeTreeEnabled( binding.row, enabled );
}
}
void SettingsPanel::addUserSettings( PanelState& panel ) {
addCategory( panel, "general.behavior", mApp->i18n( "general", "General" ),
mApp->i18n( "behavior", "Behavior" ) );
@@ -665,7 +851,9 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
mApp->i18n( "line_breaking_column", "Line Breaking Column" ),
mApp->i18n( "line_breaking_column_desc",
"Column used for wrapping and the editor width guide. Set 0 to disable." ) },
0, 1000, &mApp->getConfig().doc.lineBreakingColumn, [this]( int value ) {
0, 1000, [this] { return mApp->getConfig().doc.lineBreakingColumn; },
[this]( int value ) {
mApp->getConfig().doc.lineBreakingColumn = value;
mApp->getSplitter()->forEachEditor(
[value]( UICodeEditor* editor ) { editor->setLineBreakingColumn( value ); } );
} );
@@ -848,14 +1036,18 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
editor->getDocument().setTabOutEnabled( value );
} );
} );
addInteger( panel,
{ "indentWidth", "editor.document", mApp->i18n( "indent_width", "Indent Width" ),
mApp->i18n( "indent_width_desc", "Columns in one indentation level." ) },
1, 16, &mApp->getConfig().doc.indentWidth );
addInteger( panel,
{ "tabWidth", "editor.document", mApp->i18n( "tab_width", "Tab Width" ),
mApp->i18n( "tab_width_desc", "Columns used to display a tab character." ) },
1, 16, &mApp->getConfig().doc.tabWidth );
addInteger(
panel,
{ "indentWidth", "editor.document", mApp->i18n( "indent_width", "Indent Width" ),
mApp->i18n( "indent_width_desc", "Columns in one indentation level." ) },
1, 16, [this] { return mApp->getConfig().doc.indentWidth; },
[this]( int value ) { mApp->getConfig().doc.indentWidth = value; } );
addInteger(
panel,
{ "tabWidth", "editor.document", mApp->i18n( "tab_width", "Tab Width" ),
mApp->i18n( "tab_width_desc", "Columns used to display a tab character." ) },
1, 16, [this] { return mApp->getConfig().doc.tabWidth; },
[this]( int value ) { mApp->getConfig().doc.tabWidth = value; } );
addChoice(
panel,
{ "lineEndings", "editor.document", mApp->i18n( "line_endings", "Line Endings" ),
@@ -1079,38 +1271,56 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
mApp->i18n( "fallback_font_desc", "Choose the font used for missing glyphs." ) },
mApp->i18n( "choose_font", "Choose Font..." ),
[this] { mApp->runCommand( "fallback-font" ); } );
addAction(
panel,
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;
} );
};
addFontSize(
{ "uiFontSize", "appearance.fonts", mApp->i18n( "ui_font_size", "UI Font Size" ),
mApp->i18n( "ui_font_size_desc", "Set the font size used by the application UI." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setUIFontSize(); } );
addAction(
panel,
[this] { return mApp->getConfig().ui.fontSize.toString(); },
[this]( const StyleSheetLength& size ) {
mApp->getSettingsActions()->setUIFontSize( size );
} );
addFontSize(
{ "panelFontSize", "appearance.fonts",
mApp->i18n( "ui_panel_font_size", "Panel Font Size" ),
mApp->i18n( "ui_panel_font_size_desc", "Set the font size used by side panels." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setUIPanelFontSize(); } );
addAction( panel,
{ "editorFontSize", "appearance.fonts",
mApp->i18n( "editor_font_size", "Editor Font Size" ),
mApp->i18n( "editor_font_size_desc", "Set the default code editor font size." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setEditorFontSize(); } );
addAction( panel,
{ "terminalFontSize", "appearance.fonts",
mApp->i18n( "terminal_font_size", "Terminal Font Size" ),
mApp->i18n( "terminal_font_size_desc",
"Set the default integrated terminal font size." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setTerminalFontSize(); } );
addAction( panel,
{ "uiScaleFactor", "appearance.fonts",
mApp->i18n( "ui_scale_factor", "UI Scale Factor" ),
mApp->i18n( "ui_scale_factor_desc", "Scale the complete user interface." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setUIScaleFactor(); } );
[this] { return mApp->getConfig().ui.panelFontSize.toString(); },
[this]( const StyleSheetLength& size ) {
mApp->getSettingsActions()->setUIPanelFontSize( size );
} );
addFontSize(
{ "editorFontSize", "appearance.fonts",
mApp->i18n( "editor_font_size", "Editor Font Size" ),
mApp->i18n( "editor_font_size_desc", "Set the default code editor font size." ) },
[this] { return mApp->getConfig().editor.fontSize.toString(); },
[this]( const StyleSheetLength& size ) {
mApp->getSettingsActions()->setEditorFontSize( size );
} );
addFontSize(
{ "terminalFontSize", "appearance.fonts",
mApp->i18n( "terminal_font_size", "Terminal Font Size" ),
mApp->i18n( "terminal_font_size_desc",
"Set the default integrated terminal font size." ) },
[this] { return mApp->getConfig().term.fontSize.toString(); },
[this]( const StyleSheetLength& size ) {
mApp->getSettingsActions()->setTerminalFontSize( size );
} );
addFloat(
panel,
{ "uiScaleFactor", "appearance.fonts", mApp->i18n( "ui_scale_factor", "UI Scale Factor" ),
mApp->i18n( "ui_scale_factor_desc",
"Scale the complete user interface from 1 to 6. Restart required." ) },
1, 6, 0.1,
[this] { return std::max<Float>( 1, mApp->getConfig().windowState.pixelDensity ); },
[this]( double value ) { mApp->getConfig().windowState.pixelDensity = value; } );
addBool( panel,
{ "editorFontInInputFields", "appearance.fonts",
mApp->i18n( "editor_font_in_input_fields", "Editor Font in Input Fields" ),
@@ -1202,25 +1412,59 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
addCategory( panel, "editor.advanced", mApp->i18n( "editor", "Editor" ),
mApp->i18n( "advanced", "Advanced" ) );
addAction( panel,
{ "lineSpacing", "editor.advanced", mApp->i18n( "line_spacing", "Line Spacing" ),
mApp->i18n( "line_spacing_desc",
"Set additional vertical spacing between editor lines." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setLineSpacing(); } );
addAction( panel,
{ "cursorBlinkingTime", "editor.advanced",
mApp->i18n( "cursor_blinking_time", "Cursor Blinking Time" ),
mApp->i18n( "cursor_blinking_time_desc", "Set the text cursor blink interval." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setCursorBlinkingTime(); } );
addAction( panel,
{ "indentTabCharacter", "editor.advanced",
mApp->i18n( "indent_tab_character", "Indent Tab Character" ),
mApp->i18n( "indent_tab_character_desc",
"Choose the character inserted when indenting with Tab." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setIndentTabCharacter(); } );
addText(
panel,
{ "lineSpacing", "editor.advanced", mApp->i18n( "line_spacing", "Line Spacing" ),
mApp->i18n( "line_spacing_desc",
"Set additional vertical spacing between editor lines. Set 0 to disable." ) },
[this] { return mApp->getConfig().editor.lineSpacing.toString(); },
[this]( const std::string& text ) {
if ( !StyleSheetLength::isLength( text ) )
return false;
mApp->getConfig().editor.lineSpacing = StyleSheetLength::fromString( text );
mApp->getSplitter()->forEachEditor( [this]( UICodeEditor* editor ) {
editor->setLineSpacing( mApp->getConfig().editor.lineSpacing );
} );
return true;
} );
addText(
panel,
{ "cursorBlinkingTime", "editor.advanced",
mApp->i18n( "cursor_blinking_time", "Cursor Blinking Time" ),
mApp->i18n( "cursor_blinking_time_desc",
"Set the text cursor blink interval. Set 0 to disable." ) },
[this] { return mApp->getConfig().editor.cursorBlinkingTime.toString(); },
[this]( const std::string& text ) {
Time value;
if ( !parseNonNegativeTime( text, value ) )
return false;
mApp->getConfig().editor.cursorBlinkingTime = value;
mApp->getSplitter()->forEachEditor(
[value]( UICodeEditor* editor ) { editor->setCursorBlinkTime( value ); } );
return true;
} );
addEditableChoice(
panel,
{ "indentTabCharacter", "editor.advanced",
mApp->i18n( "indent_tab_character", "Indent Tab Character" ),
mApp->i18n( "indent_tab_character_desc",
"Choose the character inserted when indenting with Tab." ) },
{ String( u8"»" ), String( u8"" ), String( u8"" ), String( u8"" ), String( u8"" ),
String( u8"" ) },
[this] {
return mApp->getConfig().editor.tabIndentCharacter.empty()
? String( u8"»" )
: String::fromUtf8( mApp->getConfig().editor.tabIndentCharacter );
},
[this]( const String& value ) {
if ( value.size() != 1 )
return false;
mApp->getConfig().editor.tabIndentCharacter = value.toUtf8();
mApp->getSplitter()->forEachEditor( [character = value[0]]( UICodeEditor* editor ) {
editor->setTabIndentCharacter( character );
} );
return true;
} );
addChoice(
panel,
{ "indentTabAlignment", "editor.advanced",
@@ -1244,20 +1488,38 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
mApp->getSplitter()->forEachEditor(
[value]( UICodeEditor* editor ) { editor->setTabIndentAlignment( value ); } );
} );
addAction( panel,
{ "foldRefreshFrequency", "editor.advanced",
mApp->i18n( "folds_refresh_freq", "Folds Refresh Frequency" ),
mApp->i18n( "folds_refresh_freq_desc",
"Set how frequently code folding ranges are recalculated." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setFoldRefreshFreq(); } );
addAction( panel,
{ "tabOutCharacters", "editor.advanced",
mApp->i18n( "set_tab_out_characters", "Set Tab Out Characters" ),
mApp->i18n( "set_tab_out_characters_message",
"Set the characters the cursor can move past when pressing Tab." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setTabOutChars(); } );
addText(
panel,
{ "foldRefreshFrequency", "editor.advanced",
mApp->i18n( "folds_refresh_freq", "Folds Refresh Frequency" ),
mApp->i18n(
"folds_refresh_freq_desc",
"Set how frequently code folding ranges are recalculated (minimum 1 second)." ) },
[this] { return mApp->getConfig().editor.codeFoldingRefreshFreq.toString(); },
[this]( const std::string& text ) {
Time value;
if ( !parseNonNegativeTime( text, value ) || value < Seconds( 1 ) )
return false;
mApp->getConfig().editor.codeFoldingRefreshFreq = value;
mApp->getSplitter()->forEachEditor(
[value]( UICodeEditor* editor ) { editor->setFoldsRefreshTime( value ); } );
return true;
} );
addText(
panel,
{ "tabOutCharacters", "editor.advanced",
mApp->i18n( "set_tab_out_characters", "Set Tab Out Characters" ),
mApp->i18n( "set_tab_out_characters_message",
"Set the characters the cursor can move past when pressing Tab." ) },
[this] { return mApp->getConfig().doc.tabOutChars; },
[this]( const std::string& text ) {
mApp->getConfig().doc.tabOutChars = text;
String characters = String::fromUtf8( text );
mApp->getSplitter()->forEachEditor( [&characters]( UICodeEditor* editor ) {
editor->getDocument().setTabOutChars( characters );
} );
return true;
} );
addCategory( panel, "window.screenshots", mApp->i18n( "window", "Window" ),
mApp->i18n( "screenshots", "Screenshots" ) );
@@ -1267,20 +1529,40 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
mApp->i18n( "screenshot_save_path_desc", "Choose where screenshots are saved." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setScreenshotSavePath(); } );
addAction( panel,
{ "screenshotFilenamePattern", "window.screenshots",
mApp->i18n( "set_screenshot_filename_pattern", "Screenshot Filename Pattern" ),
mApp->i18n( "screenshot_filename_pattern_desc",
"Configure the timestamp-based screenshot filename pattern." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setScreenshotFilenamePattern(); } );
addAction(
addText(
panel,
{ "screenshotFilenamePattern", "window.screenshots",
mApp->i18n( "set_screenshot_filename_pattern", "Screenshot Filename Pattern" ),
mApp->i18n( "screenshot_filename_pattern_desc",
"Set the timestamp-based screenshot filename pattern." ) },
[this] { return mApp->getConfig().screenshot.filenamePattern; },
[this]( const std::string& pattern ) {
std::string filename = DateTimeController::formatCurrentDate( pattern );
if ( !DateTimeController::isValidDateFormat( pattern ) || filename.empty() ||
filename.find_first_of( "<>:\"/\\|?*" ) != std::string::npos )
return false;
mApp->getConfig().screenshot.filenamePattern = pattern;
return true;
} );
static const std::vector<String> screenshotFormats{ "PNG", "JPG", "WEBP", "QOI",
"BMP", "TGA", "DDS" };
auto screenshotFormat = String( mApp->getConfig().screenshot.saveFormat ).toUpper().toUtf8();
auto selectedScreenshotFormat = std::find( screenshotFormats.begin(), screenshotFormats.end(),
String::fromUtf8( screenshotFormat ) );
addChoice(
panel,
{ "screenshotSaveFormat", "window.screenshots",
mApp->i18n( "set_screenshot_save_format", "Screenshot Save Format" ),
mApp->i18n( "screenshot_save_format_desc", "Choose the image format for screenshots." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->getSettingsActions()->setScreenshotSaveFormat(); } );
screenshotFormats,
[selected = static_cast<size_t>(
selectedScreenshotFormat == screenshotFormats.end()
? 0
: selectedScreenshotFormat - screenshotFormats.begin() )] { return selected; },
[this]( size_t selected ) {
String format = screenshotFormats[std::min( selected, screenshotFormats.size() - 1 )];
mApp->getConfig().screenshot.saveFormat = format.toLower().toUtf8();
} );
addCategory( panel, "window.renderer", mApp->i18n( "window", "Window" ),
mApp->i18n( "renderer", "Renderer" ) );
@@ -1296,17 +1578,19 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
"Vsync configuration changed.\nRestart ecode to see the changes." )
.unescape() );
} );
addInteger( panel,
{ "frameRateLimit", "window.renderer",
mApp->i18n( "frame_rate_limit", "Frame Rate Limit" ),
mApp->i18n( "frame_rate_limit_desc",
"Limit rendered frames per second. Set 0 to disable." ) },
0, 1000, &mApp->getConfig().context.FrameRateLimit, [this]( int value ) {
mApp->saveConfig();
mApp->getWindow()->setFrameRateLimit( value );
mApp->getNotificationCenter()->addNotification(
mApp->i18n( "frame_rate_limit_applied", "Frame Rate Limit Applied" ) );
} );
addInteger(
panel,
{ "frameRateLimit", "window.renderer", mApp->i18n( "frame_rate_limit", "Frame Rate Limit" ),
mApp->i18n( "frame_rate_limit_desc",
"Limit rendered frames per second. Set 0 to disable." ) },
0, 1000, [this] { return mApp->getConfig().context.FrameRateLimit; },
[this]( int value ) {
mApp->getConfig().context.FrameRateLimit = value;
mApp->saveConfig();
mApp->getWindow()->setFrameRateLimit( value );
mApp->getNotificationCenter()->addNotification(
mApp->i18n( "frame_rate_limit_applied", "Frame Rate Limit Applied" ) );
} );
std::vector<GraphicsLibraryVersion> rendererVersions =
Renderer::getAvailableGraphicsLibraryVersions();
std::vector<String> rendererVersionNames;
@@ -1427,13 +1711,18 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
"Set the shell executable and command-line arguments." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->runCommand( "configure-terminal-shell" ); } );
addAction( panel,
{ "terminalScrollback", "terminal.behavior",
mApp->i18n( "configure_terminal_scrollback", "Configure Terminal Scrollback" ),
mApp->i18n( "configure_terminal_scrollback_desc",
"Set the number of terminal history lines retained." ) },
mApp->i18n( "configure", "Configure..." ),
[this] { mApp->runCommand( "configure-terminal-scrollback" ); } );
addInteger(
panel,
{ "terminalScrollback", "terminal.behavior",
mApp->i18n( "terminal_scrollback", "Terminal Scrollback" ),
mApp->i18n( "configure_terminal_scrollback_desc",
"Set the number of terminal history lines retained." ) },
0, std::numeric_limits<int>::max(),
[this] {
return static_cast<int>( std::min<Uint64>( mApp->getConfig().term.scrollback,
std::numeric_limits<int>::max() ) );
},
[this]( int value ) { mApp->getConfig().term.scrollback = static_cast<Uint64>( value ); } );
addAction( panel,
{ "terminalWorkingDirectory", "terminal.behavior",
mApp->i18n( "configure_terminal_working_dir",
@@ -1558,7 +1847,10 @@ void SettingsPanel::addProjectSettings( PanelState& panel ) {
mApp->i18n( "use_global_settings", "Use Global Settings" ),
mApp->i18n( "use_global_settings_desc",
"Inherit document defaults from the user settings." ) },
&mApp->getProjectConfig().useGlobalSettings );
&mApp->getProjectConfig().useGlobalSettings, [this, &panel]( bool useGlobalSettings ) {
setCategoryEnabled( panel, "editor.document", !useGlobalSettings,
"useGlobalSettings" );
} );
auto addProjectBool = [this, &panel]( std::string id, const char* nameKey, const char* name,
const char* descriptionKey, const char* description,
bool DocumentConfig::* member ) {
@@ -1602,22 +1894,27 @@ void SettingsPanel::addProjectSettings( PanelState& panel ) {
addProjectBool( "writeUnicodeBOM", "write_unicode_bom", "Write Unicode BOM",
"write_unicode_bom_desc", "Write a Unicode byte-order mark when saving.",
&DocumentConfig::writeUnicodeBOM );
addInteger( panel,
{ "indentWidth", "editor.document", mApp->i18n( "indent_width", "Indent Width" ),
mApp->i18n( "indent_width_desc",
"Number of columns inserted for one indentation level." ) },
1, 16, &mApp->getProjectConfig().doc.indentWidth );
addInteger(
panel,
{ "indentWidth", "editor.document", mApp->i18n( "indent_width", "Indent Width" ),
mApp->i18n( "indent_width_desc",
"Number of columns inserted for one indentation level." ) },
1, 16, [this] { return mApp->getProjectConfig().doc.indentWidth; },
[this]( int value ) { mApp->getProjectConfig().doc.indentWidth = value; } );
addInteger(
panel,
{ "tabWidth", "editor.document", mApp->i18n( "tab_width", "Tab Width" ),
mApp->i18n( "tab_width_desc", "Number of columns used to display a tab character." ) },
1, 16, &mApp->getProjectConfig().doc.tabWidth );
addInteger( panel,
{ "lineBreakingColumn", "editor.document",
mApp->i18n( "line_breaking_column", "Line Breaking Column" ),
mApp->i18n( "line_breaking_column_desc",
"Column used for wrapping and the editor width guide." ) },
0, 1000, &mApp->getProjectConfig().doc.lineBreakingColumn );
1, 16, [this] { return mApp->getProjectConfig().doc.tabWidth; },
[this]( int value ) { mApp->getProjectConfig().doc.tabWidth = value; } );
addInteger(
panel,
{ "lineBreakingColumn", "editor.document",
mApp->i18n( "line_breaking_column", "Line Breaking Column" ),
mApp->i18n( "line_breaking_column_desc",
"Column used for wrapping and the editor width guide." ) },
0, 1000, [this] { return mApp->getProjectConfig().doc.lineBreakingColumn; },
[this]( int value ) { mApp->getProjectConfig().doc.lineBreakingColumn = value; } );
addChoice(
panel,
{ "lineEndings", "editor.document", mApp->i18n( "line_endings", "Line Endings" ),
@@ -1639,6 +1936,8 @@ void SettingsPanel::addProjectSettings( PanelState& panel ) {
? TextFormat::LineEnding::CRLF
: ( selected == 2 ? TextFormat::LineEnding::CR : TextFormat::LineEnding::LF );
} );
setCategoryEnabled( panel, "editor.document", !mApp->getProjectConfig().useGlobalSettings,
"useGlobalSettings" );
addCategory( panel, "languages.file_associations", mApp->i18n( "languages", "Languages" ),
mApp->i18n( "file_associations", "File Associations" ) );

View File

@@ -67,12 +67,21 @@ class SettingsPanel {
void addChoice( PanelState& state, SettingBinding binding, const std::vector<String>& choices,
std::function<size_t()> get, std::function<void( size_t )> set,
std::vector<String> choiceDescriptions = {} );
void addInteger( PanelState& state, SettingBinding binding, int min, int max, int* value,
std::function<void( int )> apply = {} );
void addEditableChoice( PanelState& state, SettingBinding binding,
const std::vector<String>& choices, std::function<String()> get,
std::function<bool( const String& )> set );
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 );
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,
std::function<void()> action );
UIWidget* createRow( PanelState& state, SettingBinding& binding );
UICheckBox* createBoolControl( PanelState& state, SettingBinding& binding );
void setCategoryEnabled( PanelState& state, const std::string& category, bool enabled,
const std::string& excludedSetting = {} );
void filter( PanelState& state );
};

View File

@@ -170,7 +170,7 @@ void TerminalManager::setUseFrameBuffer( bool useFrameBuffer ) {
void TerminalManager::configureTerminalShell() {
static const auto layout( R"xml(
<window layout_width="300dp" layout_height="150dp" window-flags="default|shadow" window-title='@string(shell_configuration, "Shell Configuration")'>
<window layout_width="300dp" layout_height="150dp" window-flags="default|shadow|modal" window-title='@string(shell_configuration, "Shell Configuration")'>
<vbox lw="mp" lh="mp" padding="4dp">
<vbox lw="mp" lh="0" lw8="1">
<TextView text='@string(configure_default_shell, "Configure default shell")' font-size="14dp" margin-bottom="8dp" />
@@ -265,7 +265,7 @@ void TerminalManager::configureTerminalScrollback() {
void TerminalManager::configureTerminalWorkingDir() {
static const auto layout( R"xml(
<window layout_width="450dp" layout_height="154dp" window-flags="default|shadow"
<window layout_width="450dp" layout_height="154dp" window-flags="default|shadow|modal"
window-title='@string(terminal_working_dir_configuration, "Terminal Default Working Directory Configuration")'>
<vbox lw="mp" lh="wrap_content" padding="4dp">
<TextView text='@string(configure_terminal_default_working_dir, "Configure Terminal default working directory:")' font-size="14dp" margin-bottom="8dp" />