diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml
index d11d2d18e..cca58cc75 100644
--- a/bin/assets/i18n/de.xml
+++ b/bin/assets/i18n/de.xml
@@ -969,4 +969,168 @@ Verwendet strftime-Formatbezeichner. Die Dateierweiterung richtet sich nach dem
Wenn Tab vor einem konfigurierten Zeichen gedrückt wird, springt der Cursor darüber. Gilt nur für einen einzelnen Cursor ohne Auswahl.
Tab-Ausgangszeichen festlegen
Legen Sie die Zeichen fest, über die der Cursor beim Drücken von Tab springen kann.
+ Einstellungen öffnen...
+ Projekteinstellungen öffnen...
+ Projekteinstellungen
+ Einstellungen durchsuchen...
+ Zeilennummern neben dem Editor anzeigen.
+ Sichtbare Markierungen für Leerraumzeichen anzeigen.
+ Die Zeile mit dem primären Cursor hervorheben.
+ Nachgestellte Leerzeichen beim Speichern entfernen.
+ Sicherstellen, dass gespeicherte Dateien mit einem Zeilenumbruch enden.
+ Suchergebnisse
+ Steuert, wie lange Zeilen im Editor umgebrochen werden.
+ Spalte für Zeilenumbruch und Breitenführung. 0 deaktiviert sie.
+ Anzahl der Spalten pro Einrückungsebene.
+ Anzahl der Spalten zur Darstellung eines Tabulators.
+ Allgemein
+ Verhalten
+ Den Willkommensbildschirm anzeigen, wenn kein Dokument oder Ordner geöffnet ist.
+ Erscheinungsbild
+ Navigation
+ Formatierung
+ Arbeitsbereich
+ Dokumentvorgaben
+ Zeilen am Anzeigebereich oder an der Umbruchspalte umbrechen.
+ Einrückung in umgebrochenen Fortsetzungszeilen beibehalten.
+ Syntaxabhängige Codefaltung aktivieren.
+ Faltsteuerelemente immer am Editorrand anzeigen.
+ Position neu geöffneter Tabs auswählen.
+ Detect indentation settings when opening each document.
+ Indent Using Spaces
+ Insert spaces instead of tab characters for indentation.
+ Write a Unicode byte-order mark when saving.
+ Schließende XML-Tags automatisch einfügen.
+ Unveränderte Dokumente bei Dateiänderungen neu laden.
+ Standard-Zeilenende für neue Dateien.
+ Dokumentvorgaben aus den Benutzereinstellungen übernehmen.
+ Letzte Sitzung wiederherstellen
+ Vorherigen Arbeitsbereich und Dokumente beim Start erneut öffnen.
+ Beim Start nach Aktualisierungen suchen
+ Nach dem Start auf eine neuere ecode-Version prüfen.
+ Bereich für neue Terminalsitzungen auswählen.
+ Terminal-Bildlaufleiste über oder neben dem Inhalt platzieren.
+ Sichtbarkeit der Terminal-Bildlaufleiste steuern.
+ Erweitert
+ Konfigurieren...
+ Schriftarten & Skalierung
+ Bildschirmfotos
+ Grafiksystem
+ Dokumentkodierung und Zeilenendeninformationen anzeigen.
+ Extern angeforderte Dokumente im Haupteditorbereich öffnen.
+ Reihenfolge der Tabnavigation per Tastatur auswählen.
+ Editorschriftart für Texteingabefelder verwenden.
+ Schriftgröße der Benutzeroberfläche festlegen.
+ Schriftgröße der Seitenbereiche festlegen.
+ Standardschriftgröße des Code-Editors festlegen.
+ Standardschriftgröße des Terminals festlegen.
+ Gesamte Benutzeroberfläche skalieren.
+ Zusätzlichen vertikalen Abstand zwischen Editorzeilen festlegen.
+ Blinkintervall des Textcursors festlegen.
+ Häufigkeit der Neuberechnung von Faltungsbereichen festlegen.
+ Speicherort für Bildschirmfotos auswählen.
+ Zeitstempel-Dateinamensmuster für Bildschirmfotos konfigurieren.
+ Bildformat für Bildschirmfotos auswählen.
+ Bilder pro Sekunde begrenzen. 0 deaktiviert die Begrenzung.
+ Multisampling festlegen. Neustart erforderlich.
+ Shell-Programm und Befehlszeilenargumente festlegen.
+ Anzahl gespeicherter Terminalverlaufszeilen festlegen.
+ Arbeitsverzeichnis für neue Terminals auswählen.
+ Form und Animation des Terminalcursors auswählen.
+ Thema & Sprache
+ Choose whether the interface follows the system, light, or dark appearance.
+ Stylesheet der Benutzeroberfläche auswählen.
+ Sprache der Benutzeroberfläche auswählen. Neustart erforderlich.
+ Schriftart auswählen...
+ Proportionale Schriftart der Benutzeroberfläche auswählen.
+ Nichtproportionale Schriftart für Code-Editoren auswählen.
+ Nichtproportionale Schriftart für Terminals auswählen.
+ Schriftart für fehlende Zeichen auswählen.
+ Farben für Syntaxhervorhebung und Editoroberflächen auswählen.
+ Farben der integrierten Terminals auswählen.
+ Grafik-API-Version auswählen. Neustart erforderlich.
+ Disable multisample anti-aliasing.
+ Use 2x multisample anti-aliasing.
+ Use 4x multisample anti-aliasing.
+ Use 8x multisample anti-aliasing.
+ Use 16x multisample anti-aliasing.
+ Keine automatische Einrückung.
+ Einrückungsebene der vorherigen Zeile beibehalten.
+ Einrückung beibehalten und zwischen geschlossenen Klammern einrücken.
+ Immer das helle Oberflächenfarbschema verwenden.
+ Immer das dunkle Oberflächenfarbschema verwenden.
+ Terminal-Bildlaufleiste sichtbar lassen.
+ Terminal-Bildlaufleiste ausgeblendet lassen.
+ Immer ausgeblendet
+ Immer sichtbar
+ Automatisch
+ Das passende schließende Zeichen automatisch einfügen.
+ Automatisch erkennen
+ Automatische Einrückung
+ Keine
+ Beibehalten
+ Intelligent
+ Steuert die automatische Einrückung nach dem Drücken der Eingabetaste.
+ Blinkender Balken
+ Blinkender Block
+ Blinkender Unterstrich
+ Chronologisch
+ Terminal-Tab beim Beenden schließen
+ Schließt einen Terminal-Tab, wenn sein Hauptprozess beendet wird.
+ Standardarbeitsverzeichnis des Terminals konfigurieren
+ Cursorstil
+ Tab-Umschalter anzeigen
+ Zeigt in der Mitte jedes Tab-Widgets einen Tab-Umschalter an.
+ Editor
+ Editor-Schriftart in Eingabefeldern
+ Exklusivmodus standardmäßig aktivieren
+ Deaktiviert globale Tastenkürzel in neu erstellten Terminals.
+ Dateizuordnungen
+ Voll
+ Graustufen
+ Tableiste ausblenden
+ Blendet die Tableiste immer aus.
+ Richtet Text innerhalb der sichtbaren Breite von Einrückungstabs aus.
+ Wählt das beim Einrücken mit Tab eingefügte Zeichen.
+ Der lineare Sprungmodus wechselt Tabs in ihrer angezeigten Reihenfolge.
+ Sprachen
+ Linear
+ Position neuer Tabs
+ Nach aktivem Tab
+ Erste Position
+ Letzte Position
+ Links vom aktiven Tab
+ Dokumente im Hauptbereich öffnen
+ Im Statusleistenbereich öffnen
+ Projekt in neuem Fenster öffnen
+ Öffnet Ordner in einem neuen Fenster, wenn bereits ein anderes Projekt geladen ist.
+ Außerhalb
+ Überlagernd
+ Schnellvorschau für Bilder
+ Zeigt Bilder in einer Vorschau, ohne einen dauerhaften Editor-Tab zu öffnen.
+ Editorauswahl bei Fokus wiederherstellen
+ Stellt die letzte Cursorposition und Auswahl jedes Editorbereichs beim Fokussieren wieder her.
+ Bildlaufleistenmodus
+ Zeigt eine überlagernde Bildlaufleiste bei Mausbewegung oder eine äußere Bildlaufleiste bei scrollbarem Inhalt.
+ Bildlaufleistentyp
+ Die Bildlaufleiste hat eigenen Platz und verdeckt nie den Inhalt.
+ Die Bildlaufleiste wird über dem Inhalt angezeigt.
+ Speicherpfad für Bildschirmfotos
+ Leicht
+ Statischer Balken
+ Statischer Block
+ Statischer Unterstrich
+ Subpixel
+ Tableiste
+ Tab-Sprungmodus
+ Wählt die Standardsprache für mehrdeutige .h-Headerdateien.
+ Schriftkantenglättung
+ Legt fest, wie Glyphenkanten geglättet werden.
+ Schrifthinting
+ Steuert die Ausrichtung der Glyphen am Pixelraster.
+ Native Dateidialoge aktivieren
+ Verwendet Dateidialoge des Betriebssystems, sofern verfügbar.
+ Synchronisiert die Darstellung mit der Bildwiederholrate. Neustart erforderlich.
+ Vor dem Schließen des Tabs warnen
+ Fragt vor dem Schließen eines Terminals mit laufendem Programm nach.
diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml
index d9582ab61..f8d327faf 100644
--- a/bin/assets/i18n/en.xml
+++ b/bin/assets/i18n/en.xml
@@ -953,4 +953,168 @@ Uses strftime format specifiers. The file extension follows the selected screens
Pressing Tab before a configured character moves the cursor past it. Only applies to a single cursor without a selection.
Set Tab Out Characters
Set the characters the cursor can move past when pressing Tab.
+ Open Settings...
+ Open Project Settings...
+ Project Settings
+ Search settings...
+ Display line numbers beside the editor.
+ Display visible markers for whitespace characters.
+ Highlight the line containing the primary cursor.
+ Remove trailing whitespace when saving files.
+ Ensure saved files end with a newline.
+ Search Results
+ Controls how long lines wrap in the editor.
+ Column used for wrapping and the editor width guide. Set 0 to disable.
+ Number of columns inserted for one indentation level.
+ Number of columns used to display a tab character.
+ General
+ Behavior
+ Show the welcome screen when no document or folder is open.
+ Appearance
+ Navigation
+ Formatting
+ Workspace
+ Document Defaults
+ Choose whether lines wrap at the viewport or line breaking column.
+ Preserve indentation on wrapped continuation lines.
+ Enable syntax-aware code folding.
+ Always display fold controls in the editor gutter.
+ Choose where newly opened tabs are inserted.
+ Detect indentation settings when opening each document.
+ Indent Using Spaces
+ Insert spaces instead of tab characters for indentation.
+ Write a Unicode byte-order mark when saving.
+ Automatically insert closing XML tags.
+ Reload unmodified documents when their files change on disk.
+ Default line-ending sequence for new files.
+ Inherit document defaults from the user settings.
+ Restore Last Session
+ Reopen the previous workspace and documents at startup.
+ Check for Updates at Startup
+ Check whether a newer ecode release is available after startup.
+ Choose where newly created terminal sessions are opened.
+ Place the terminal scrollbar over or outside its content.
+ Control when the terminal scrollbar is visible.
+ Advanced
+ Configure...
+ Fonts & Scale
+ Screenshots
+ Renderer
+ Display document encoding and line-ending information.
+ Open externally requested documents in the main editor split.
+ Choose how keyboard navigation cycles through tabs.
+ Use the editor font for text input controls.
+ Set the font size used by the application UI.
+ Set the font size used by side panels.
+ Set the default code editor font size.
+ Set the default integrated terminal font size.
+ Scale the complete user interface.
+ Set additional vertical spacing between editor lines.
+ Set the text cursor blink interval.
+ Set how frequently code folding ranges are recalculated.
+ Choose where screenshots are saved.
+ Configure the timestamp-based screenshot filename pattern.
+ Choose the image format for screenshots.
+ Limit rendered frames per second. Set 0 to disable.
+ Set renderer multisampling. Restart required.
+ Set the shell executable and command-line arguments.
+ Set the number of terminal history lines retained.
+ Choose the working directory used by new terminals.
+ Choose the terminal cursor shape and animation.
+ Theme & Language
+ Choose whether the interface follows the system, light, or dark appearance.
+ Choose the stylesheet used by the application interface.
+ Choose the interface language. Restart required.
+ Choose Font...
+ Choose the proportional font used by the interface.
+ Choose the monospace font used by code editors.
+ Choose the monospace font used by terminals.
+ Choose the font used for missing glyphs.
+ Choose the colors used for syntax highlighting and editor surfaces.
+ Choose the colors used by integrated terminals.
+ Select the graphics API version used by ecode. Restart required.
+ Disable multisample anti-aliasing.
+ Use 2x multisample anti-aliasing.
+ Use 4x multisample anti-aliasing.
+ Use 8x multisample anti-aliasing.
+ Use 16x multisample anti-aliasing.
+ No automatic indentation.
+ Preserve the indentation level of the previous line.
+ Preserve indentation and indent between auto-closed brackets.
+ Always use the light interface color scheme.
+ Always use the dark interface color scheme.
+ Keep the terminal scrollbar visible.
+ Keep the terminal scrollbar hidden.
+ Always Hidden
+ Always Visible
+ Auto
+ Automatically insert the matching closing character.
+ Auto-Detect
+ Auto-Indent
+ None
+ Preserve
+ Smart
+ Control indentation added after pressing Enter.
+ Blinking Bar
+ Blinking Block
+ Blinking Underline
+ Chronological
+ Close Terminal Tab on Exit
+ Close a terminal tab when its main process exits.
+ Configure Terminal Default Working Directory
+ Cursor Style
+ Display Tab Switcher
+ Display a tab switcher in the center of each tab widget.
+ Editor
+ Editor Font in Input Fields
+ Enable Exclusive Mode by Default
+ Disable global keybindings in newly created terminals.
+ File Associations
+ Full
+ Grayscale
+ Hide Tab Bar
+ Always hide the tab bar.
+ Align text within the visual width of indentation tabs.
+ Choose the character inserted when indenting with Tab.
+ Linear Jump Mode switches tabs in their displayed order.
+ Languages
+ Linear
+ New Tab Position
+ After Active Tab
+ First
+ Last
+ Left of Active Tab
+ Open Documents in Main Split
+ Open in Status Bar Panel
+ Open Project in New Window
+ Open folders in a new window when another project is already loaded.
+ Outside
+ Overlay
+ Quick Preview Images
+ Preview images without opening a permanent editor tab.
+ Restore Editor Selection on Focus
+ Restore each editor split's last cursor and selection when focused.
+ Scrollbar Mode
+ Show an overlay scrollbar on mouse movement, or an outside scrollbar when content is scrollable.
+ Scrollbar Type
+ The scrollbar has its own space and never covers content.
+ The scrollbar appears over content.
+ Screenshot Save Path
+ Slight
+ Steady Bar
+ Steady Block
+ Steady Underline
+ Subpixel
+ Tab Bar
+ Tab Jump Mode
+ Choose the default language for ambiguous .h header files.
+ Font Anti-Aliasing
+ Choose how glyph edges are smoothed.
+ Font Hinting
+ Control glyph alignment to the pixel grid.
+ Enable Native File Dialogs
+ Use operating-system file dialogs when available.
+ Synchronize rendering with the display refresh rate. Restart required.
+ Warn Before Closing Tab
+ Ask before closing a terminal while a program is running.
diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml
index a6815adf3..14473157d 100644
--- a/bin/assets/i18n/fr.xml
+++ b/bin/assets/i18n/fr.xml
@@ -948,4 +948,169 @@ Utilise les spécificateurs de format strftime. L’extension du fichier corresp
Appuyer sur Tab avant un caractère configuré déplace le curseur après celui-ci. S'applique uniquement à un curseur sans sélection.
Définir les caractères de sortie
Définissez les caractères que le curseur peut franchir en appuyant sur Tab.
+ Ouvrir les paramètres...
+ Ouvrir les paramètres du projet...
+ Paramètres du projet
+ Rechercher dans les paramètres...
+ Afficher les numéros de ligne à côté de l'éditeur.
+ Afficher des marqueurs visibles pour les espaces.
+ Mettre en évidence la ligne contenant le curseur principal.
+ Supprimer les espaces de fin lors de l'enregistrement.
+ S'assurer que les fichiers enregistrés se terminent par un saut de ligne.
+ Résultats de recherche
+ Contrôle le retour à la ligne des lignes longues dans l'éditeur.
+ Colonne utilisée pour le retour à la ligne et le guide de largeur. 0 pour désactiver.
+ Nombre de colonnes insérées pour un niveau d'indentation.
+ Nombre de colonnes utilisées pour afficher une tabulation.
+ Général
+ Comportement
+ Afficher l'écran de bienvenue lorsqu'aucun document ou dossier n'est ouvert.
+ Appearance
+ Navigation
+ Formatting
+ Workspace
+ Document Defaults
+ Choose whether lines wrap at the viewport or line breaking column.
+ Preserve indentation on wrapped continuation lines.
+ Enable syntax-aware code folding.
+ Always display fold controls in the editor gutter.
+ Choose where newly opened tabs are inserted.
+ Detect indentation settings when opening each document.
+ Indent Using Spaces
+ Insert spaces instead of tab characters for indentation.
+ Write a Unicode byte-order mark when saving.
+ Automatically insert closing XML tags.
+ Reload unmodified documents when their files change on disk.
+ Default line-ending sequence for new files.
+ Inherit document defaults from the user settings.
+ Restore Last Session
+ Reopen the previous workspace and documents at startup.
+ Check for Updates at Startup
+ Check whether a newer ecode release is available after startup.
+ Choose where newly created terminal sessions are opened.
+ Place the terminal scrollbar over or outside its content.
+ Control when the terminal scrollbar is visible.
+ Advanced
+ Configure...
+ Fonts & Scale
+ Screenshots
+ Renderer
+ Display document encoding and line-ending information.
+ Open externally requested documents in the main editor split.
+ Choose how keyboard navigation cycles through tabs.
+ Use the editor font for text input controls.
+ Set the font size used by the application UI.
+ Set the font size used by side panels.
+ Set the default code editor font size.
+ Set the default integrated terminal font size.
+ Scale the complete user interface.
+ Set additional vertical spacing between editor lines.
+ Set the text cursor blink interval.
+ Set how frequently code folding ranges are recalculated.
+ Choose where screenshots are saved.
+ Configure the timestamp-based screenshot filename pattern.
+ Choose the image format for screenshots.
+ Limit rendered frames per second. Set 0 to disable.
+ Set renderer multisampling. Restart required.
+ Set the shell executable and command-line arguments.
+ Set the number of terminal history lines retained.
+ Choose the working directory used by new terminals.
+ Choose the terminal cursor shape and animation.
+ Theme & Language
+ Choose whether the interface follows the system, light, or dark appearance.
+ Choose the stylesheet used by the application interface.
+ Choose the interface language. Restart required.
+ Choose Font...
+ Choose the proportional font used by the interface.
+ Choose the monospace font used by code editors.
+ Choose the monospace font used by terminals.
+ Choose the font used for missing glyphs.
+ Choose the colors used for syntax highlighting and editor surfaces.
+ Choose the colors used by integrated terminals.
+ Select the graphics API version used by ecode. Restart required.
+ Disable multisample anti-aliasing.
+ Use 2x multisample anti-aliasing.
+ Use 4x multisample anti-aliasing.
+ Use 8x multisample anti-aliasing.
+ Use 16x multisample anti-aliasing.
+ No automatic indentation.
+ Preserve the indentation level of the previous line.
+ Preserve indentation and indent between auto-closed brackets.
+ Always use the light interface color scheme.
+ Always use the dark interface color scheme.
+ Keep the terminal scrollbar visible.
+ Toujours masquer la barre de défilement du terminal.
+ Toujours masquée
+ Toujours visible
+ Automatique
+ Insérer automatiquement le caractère de fermeture correspondant.
+ Détection automatique
+ Indentation automatique
+ Aucune
+ Conserver
+ Intelligente
+ Contrôle l'indentation ajoutée après avoir appuyé sur Entrée.
+ Barre clignotante
+ Bloc clignotant
+ Soulignement clignotant
+ Chronologique
+ Fermer l'onglet du terminal à la sortie
+ Ferme un onglet de terminal lorsque son processus principal se termine.
+ Configurer le répertoire de travail par défaut du terminal
+ Style du curseur
+ Afficher le sélecteur d'onglets
+ Affiche un sélecteur d'onglets au centre de chaque groupe d'onglets.
+ Éditeur
+ Police de l'éditeur dans les champs de saisie
+ Activer le mode exclusif par défaut
+ Désactive les raccourcis globaux dans les nouveaux terminaux.
+ Associations de fichiers
+ Complet
+ Niveaux de gris
+ Masquer la barre d'onglets
+ Masque la barre d'onglets lorsqu'elle ne contient qu'un seul élément.
+ Toujours masquer la barre d'onglets.
+ Aligne le texte dans la largeur visuelle des tabulations d'indentation.
+ Choisit le caractère inséré lors de l'indentation avec Tab.
+ Le mode linéaire parcourt les onglets dans leur ordre d'affichage.
+ Langages
+ Linéaire
+ Position des nouveaux onglets
+ Après l'onglet actif
+ En premier
+ En dernier
+ À gauche de l'onglet actif
+ Ouvrir les documents dans la zone principale
+ Ouvrir dans le panneau de la barre d'état
+ Ouvrir le projet dans une nouvelle fenêtre
+ Ouvre les dossiers dans une nouvelle fenêtre lorsqu'un autre projet est déjà chargé.
+ À l'extérieur
+ Superposée
+ Aperçu rapide des images
+ Prévisualise les images sans ouvrir d'onglet permanent.
+ Restaurer la sélection de l'éditeur au focus
+ Restaure la dernière position du curseur et la sélection de chaque zone d'édition lors du focus.
+ Mode de la barre de défilement
+ Affiche une barre superposée au mouvement de la souris, ou une barre extérieure lorsque le contenu défile.
+ Type de barre de défilement
+ La barre de défilement occupe son propre espace et ne recouvre jamais le contenu.
+ La barre de défilement apparaît par-dessus le contenu.
+ Chemin d'enregistrement des captures d'écran
+ Léger
+ Barre fixe
+ Bloc fixe
+ Soulignement fixe
+ Sous-pixel
+ Barre d'onglets
+ Mode de parcours des onglets
+ Choisit le langage par défaut des fichiers d'en-tête .h ambigus.
+ Anticrénelage des polices
+ Choisit la méthode de lissage des contours des glyphes.
+ Optimisation des polices
+ Contrôle l'alignement des glyphes sur la grille de pixels.
+ Activer les boîtes de dialogue de fichiers natives
+ Utilise les boîtes de dialogue du système lorsqu'elles sont disponibles.
+ Synchronise le rendu avec la fréquence de l'écran. Redémarrage requis.
+ Avertir avant de fermer l'onglet
+ Demande confirmation avant de fermer un terminal exécutant un programme.
diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml
index 9c84db85d..3bfb41d7b 100644
--- a/bin/assets/i18n/zh.xml
+++ b/bin/assets/i18n/zh.xml
@@ -727,4 +727,193 @@ file in the directory tree.
在已配置的字符前按 Tab 可将光标移至该字符之后。仅适用于无选区的单个光标。
设置 Tab 跳出字符
设置按 Tab 时光标可以跳过的字符。
+ 打开设置...
+ 打开项目设置...
+ 项目设置
+ 搜索设置...
+ 在编辑器旁显示行号。
+ 显示空白字符的可见标记。
+ 突出显示主光标所在行。
+ 保存文件时删除行尾空白。
+ 确保保存的文件以换行符结尾。
+ 搜索结果
+ 控制编辑器中长行的换行方式。
+ 用于换行和编辑器宽度参考线的列。设置为 0 可禁用。
+ 每个缩进级别插入的列数。
+ 显示制表符时使用的列数。
+ 常规
+ 行为
+ 未打开文档或文件夹时显示欢迎屏幕。
+ Appearance
+ Navigation
+ Formatting
+ Workspace
+ Document Defaults
+ Choose whether lines wrap at the viewport or line breaking column.
+ Preserve indentation on wrapped continuation lines.
+ Enable syntax-aware code folding.
+ Always display fold controls in the editor gutter.
+ Choose where newly opened tabs are inserted.
+ Detect indentation settings when opening each document.
+ Indent Using Spaces
+ Insert spaces instead of tab characters for indentation.
+ Write a Unicode byte-order mark when saving.
+ Automatically insert closing XML tags.
+ Reload unmodified documents when their files change on disk.
+ Default line-ending sequence for new files.
+ Inherit document defaults from the user settings.
+ Restore Last Session
+ Reopen the previous workspace and documents at startup.
+ Check for Updates at Startup
+ Check whether a newer ecode release is available after startup.
+ Choose where newly created terminal sessions are opened.
+ Place the terminal scrollbar over or outside its content.
+ Control when the terminal scrollbar is visible.
+ Advanced
+ Configure...
+ Fonts & Scale
+ Screenshots
+ Renderer
+ Display document encoding and line-ending information.
+ Open externally requested documents in the main editor split.
+ Choose how keyboard navigation cycles through tabs.
+ Use the editor font for text input controls.
+ Set the font size used by the application UI.
+ Set the font size used by side panels.
+ Set the default code editor font size.
+ Set the default integrated terminal font size.
+ Scale the complete user interface.
+ Set additional vertical spacing between editor lines.
+ Set the text cursor blink interval.
+ Set how frequently code folding ranges are recalculated.
+ Choose where screenshots are saved.
+ Configure the timestamp-based screenshot filename pattern.
+ Choose the image format for screenshots.
+ Limit rendered frames per second. Set 0 to disable.
+ Set renderer multisampling. Restart required.
+ Set the shell executable and command-line arguments.
+ Set the number of terminal history lines retained.
+ Choose the working directory used by new terminals.
+ Choose the terminal cursor shape and animation.
+ Theme & Language
+ Choose whether the interface follows the system, light, or dark appearance.
+ Choose the stylesheet used by the application interface.
+ Choose the interface language. Restart required.
+ Choose Font...
+ Choose the proportional font used by the interface.
+ Choose the monospace font used by code editors.
+ Choose the monospace font used by terminals.
+ Choose the font used for missing glyphs.
+ Choose the colors used for syntax highlighting and editor surfaces.
+ Choose the colors used by integrated terminals.
+ Select the graphics API version used by ecode. Restart required.
+ Disable multisample anti-aliasing.
+ Use 2x multisample anti-aliasing.
+ Use 4x multisample anti-aliasing.
+ Use 8x multisample anti-aliasing.
+ Use 16x multisample anti-aliasing.
+ No automatic indentation.
+ Preserve the indentation level of the previous line.
+ Preserve indentation and indent between auto-closed brackets.
+ Always use the light interface color scheme.
+ Always use the dark interface color scheme.
+ Keep the terminal scrollbar visible.
+ 始终隐藏终端滚动条。
+ 始终隐藏
+ 始终显示
+ 自动
+ 自动插入匹配的闭合字符。
+ 自动检测
+ 自动缩进
+ 无
+ 保持
+ 智能
+ 控制按下 Enter 后添加的自动缩进。
+ 磁盘文件更改时自动重新加载
+ 闪烁竖线
+ 闪烁方块
+ 闪烁下划线
+ 居中
+ 按访问顺序
+ 进程退出时关闭终端标签页
+ 终端主进程退出时关闭其标签页。
+ 代码折叠
+ 始终显示折叠控件
+ 配置终端默认工作目录
+ 光标样式
+ 显示标签页切换器
+ 在每个标签页组件中央显示标签页切换器。
+ 编辑器
+ 输入框使用编辑器字体
+ 启用
+ 默认启用独占模式
+ 在新建终端中禁用全局快捷键。
+ 文件关联
+ 折叠区域刷新频率
+ 完全
+ 灰度
+ 隐藏标签栏
+ 始终隐藏标签栏。
+ 缩进制表符对齐
+ 在缩进制表符的可视宽度内对齐文本。
+ 缩进制表字符
+ 选择按 Tab 缩进时插入的字符。
+ 线性模式按标签页显示顺序切换。
+ 保持缩进
+ 语言
+ 左对齐
+ 线性
+ 自动换行
+ 新标签页位置
+ 活动标签页之后
+ 最前
+ 最后
+ 活动标签页左侧
+ 不换行
+ 在主编辑区打开文档
+ 在新窗口中打开文件
+ 在状态栏面板中打开
+ 在新窗口中打开项目
+ 已有项目加载时,在新窗口中打开文件夹。
+ 外置
+ 叠加
+ 快速预览图像
+ 无需打开永久编辑器标签页即可预览图像。
+ 聚焦时恢复编辑器选区
+ 编辑区重新获得焦点时,恢复其上次的光标和选区。
+ 右对齐
+ 滚动条模式
+ 鼠标移动时显示叠加滚动条;内容可滚动时显示外置滚动条。
+ 滚动条类型
+ 滚动条占用独立空间,不会覆盖内容。
+ 滚动条叠加显示在内容上方。
+ 会话快照与定期备份
+ 保留会话之间未保存的文档更改。
+ 截图保存路径
+ 设置
+ 轻微
+ 固定竖线
+ 固定方块
+ 固定下划线
+ 次像素
+ 系统
+ 标签栏
+ 标签页切换模式
+ 将 .h 文件视为
+ 选择歧义 .h 头文件的默认语言。
+ 单击即可在项目树中打开文件。
+ 字体抗锯齿
+ 选择字形边缘的平滑方式。
+ 字体微调
+ 控制字形与像素网格的对齐。
+ 启用系统原生文件对话框
+ 可用时使用操作系统原生文件对话框。
+ 视口
+ 使渲染与显示器刷新率同步。需要重启。
+ 关闭标签页前警告
+ 终端中有程序运行时,关闭前请求确认。
+ 启用欢迎界面
+ 按字符换行
+ 换行依据
+ 按单词换行
diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp
index 07cd4e5ca..fe8c2ae59 100644
--- a/src/tools/ecode/ecode.cpp
+++ b/src/tools/ecode/ecode.cpp
@@ -9,6 +9,7 @@
#include "plugins/debugger/statusdebuggercontroller.hpp"
#include "settingsactions.hpp"
#include "settingsmenu.hpp"
+#include "settingspanel.hpp"
#include "uibuildsettings.hpp"
#include "uidownloadwindow.hpp"
#include "uirightpanel.hpp"
@@ -2123,6 +2124,8 @@ std::map App::getLocalKeybindings() {
{ { KEY_F11, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "debug-widget-tree-view" },
{ { KEY_K, KeyMod::getDefaultModifier() }, "open-locatebar" },
{ { KEY_P, KeyMod::getDefaultModifier() }, "open-command-palette" },
+ { { KEY_COMMA, KeyMod::getDefaultModifier() }, "open-settings" },
+ { { KEY_COMMA, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-project-settings" },
{ { KEY_F, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "open-global-search" },
{ { KEY_L, KeyMod::getDefaultModifier() }, "go-to-line" },
#if EE_PLATFORM == EE_PLATFORM_MACOS
@@ -2206,6 +2209,8 @@ std::vector App::getUnlockedCommands() {
"toggle-status-terminal",
"toggle-status-app-output",
"menu-toggle",
+ "open-settings",
+ "open-project-settings",
"switch-side-panel",
"toggle-status-bar",
"download-file-web",
@@ -3593,6 +3598,20 @@ void App::toggleSettingsMenu() {
mSettings->toggleSettingsMenu();
}
+void App::openSettings() {
+ if ( !mSettingsPanel )
+ mSettingsPanel = std::make_unique( this );
+ mSettingsPanel->show( SettingsPanel::Scope::User );
+}
+
+void App::openProjectSettings() {
+ if ( !projectIsOpen() )
+ return;
+ if ( !mSettingsPanel )
+ mSettingsPanel = std::make_unique( this );
+ mSettingsPanel->show( SettingsPanel::Scope::Project );
+}
+
void App::showFolderTreeViewTab() {
UITab* tab = mSidePanel->find( "treeview_tab" );
if ( tab )
diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp
index 007d40bcb..3486aeb14 100644
--- a/src/tools/ecode/ecode.hpp
+++ b/src/tools/ecode/ecode.hpp
@@ -34,6 +34,7 @@ class FormatterPlugin;
class DateTimeController;
class FontPickerController;
class SettingsMenu;
+class SettingsPanel;
class UITreeViewFS;
class StatusDebuggerController;
class UIRightPanel;
@@ -215,6 +216,10 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
void toggleSettingsMenu();
+ void openSettings();
+
+ void openProjectSettings();
+
UIStatusBar* getStatusBar() const { return mStatusBar; }
void showFolderTreeViewTab();
@@ -243,6 +248,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
t.setCommand( "debug-draw-debug-data", [this] { debugDrawData(); } );
t.setCommand( "debug-widget-tree-view", [this] { createWidgetInspector(); } );
t.setCommand( "menu-toggle", [this] { toggleSettingsMenu(); } );
+ t.setCommand( "open-settings", [this] { openSettings(); } );
+ t.setCommand( "open-project-settings", [this] { openProjectSettings(); } );
t.setCommand( "switch-side-panel", [this] { switchSidePanel(); } );
t.setCommand( "download-file-web", [this] { downloadFileWebDialog(); } );
t.setCommand( "move-panel-left", [this] { panelPosition( PanelPosition::Left ); } );
@@ -364,8 +371,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
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 ); } );
+ t.setCommand( "editor-font", [this] { openFontDialog( mConfig.ui.monospaceFont, true ); } );
t.setCommand( "terminal-font",
[this] { openFontDialog( mConfig.ui.terminalFont, true, true ); } );
t.setCommand( "fallback-font", [this] {
@@ -777,6 +783,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
std::unique_ptr mTerminalManager;
std::unique_ptr mPluginManager;
std::unique_ptr mSettings;
+ std::unique_ptr mSettingsPanel;
std::unique_ptr mDateTimeController;
std::unique_ptr mFontPickerController;
std::string mFileToOpen;
diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp
index 8102b6a5a..32dfb75b8 100644
--- a/src/tools/ecode/settingsmenu.cpp
+++ b/src/tools/ecode/settingsmenu.cpp
@@ -73,6 +73,16 @@ void SettingsMenu::createSettingsMenu( App* app, UIMenuBar* menuBar ) {
getKeybind( "save-all" ) )
->setId( "save-all" );
mSettingsMenu->addSeparator()->setId( "settings-submenus-sep" );
+ mSettingsMenu
+ ->add( i18n( "open_settings_ellipsis", "Open Settings..." ), findIcon( "settings" ),
+ getKeybind( "open-settings" ) )
+ ->setId( "open-settings" );
+ mSettingsMenu
+ ->add( i18n( "open_project_settings_ellipsis", "Open Project Settings..." ),
+ findIcon( "folder-settings" ), getKeybind( "open-project-settings" ) )
+ ->setId( "open-project-settings" )
+ ->setEnabled( mApp->projectIsOpen() );
+ mSettingsMenu->addSeparator();
mProjectMenu = UIPopUpMenu::New();
mDocMenu = UIPopUpMenu::New();
@@ -2353,7 +2363,7 @@ UIMenu* SettingsMenu::createViewMenu() {
} else if ( item->getId() == "enable-color-preview" ) {
mApp->getConfig().editor.colorPreview = item->asType()->isActive();
mSplitter->forEachEditor( [this]( UICodeEditor* editor ) {
- editor->setEnableColorPickerOnSelection( mApp->getConfig().editor.colorPreview );
+ editor->setColorPreview( mApp->getConfig().editor.colorPreview );
} );
} else if ( item->getId() == "enable-color-picker" ) {
mApp->getConfig().editor.colorPickerSelection =
@@ -2587,6 +2597,7 @@ void SettingsMenu::toggleSettingsMenu() {
}
void SettingsMenu::updateProjectSettingsMenu() {
+ mSettingsMenu->getItemId( "open-project-settings" )->setEnabled( mApp->projectIsOpen() );
mSettingsMenu->getItemId( "project_settings" )
->setEnabled( !mApp->getCurrentProject().empty() );
diff --git a/src/tools/ecode/settingspanel.cpp b/src/tools/ecode/settingspanel.cpp
new file mode 100644
index 000000000..3627f7825
--- /dev/null
+++ b/src/tools/ecode/settingspanel.cpp
@@ -0,0 +1,1729 @@
+#include "settingspanel.hpp"
+#include "ecode.hpp"
+#include "uitreeviewfs.hpp"
+#include
+
+using namespace EE::UI::Models;
+
+namespace ecode {
+
+class SettingsCategoryModel final : public Model {
+ public:
+ struct Node {
+ std::string id;
+ std::string text;
+ Node* parent{ nullptr };
+ std::vector children;
+ std::vector visibleChildren;
+ };
+
+ static std::shared_ptr
+ create( const std::vector>>& categories,
+ const UnorderedMap& ids ) {
+ return std::make_shared( categories, ids );
+ }
+
+ SettingsCategoryModel(
+ const std::vector>>& categories,
+ const UnorderedMap& ids ) {
+ mNodes.emplace_back();
+ mRoot = &mNodes.back();
+ for ( const auto& [parent, children] : categories ) {
+ std::string parentId;
+ if ( !children.empty() ) {
+ auto id = ids.find( parent + '/' + children.front() );
+ if ( id != ids.end() ) {
+ auto separator = id->second.find( '.' );
+ parentId = id->second.substr( 0, separator ) + ".*";
+ }
+ }
+ mNodes.push_back( { std::move( parentId ), parent, mRoot } );
+ auto* parentNode = &mNodes.back();
+ mRoot->children.push_back( parentNode );
+ for ( const auto& child : children ) {
+ auto id = ids.find( parent + '/' + child );
+ mNodes.push_back(
+ { id == ids.end() ? std::string{} : id->second, child, parentNode } );
+ parentNode->children.push_back( &mNodes.back() );
+ }
+ }
+ filter( {}, {} );
+ }
+
+ size_t rowCount( const ModelIndex& parent = {} ) const {
+ auto* node = parent.isValid() ? static_cast( parent.internalData() ) : mRoot;
+ return node->visibleChildren.size();
+ }
+
+ size_t columnCount( const ModelIndex& = {} ) const { return 1; }
+
+ ModelIndex index( int row, int column, const ModelIndex& parent = {} ) const {
+ auto* node = parent.isValid() ? static_cast( parent.internalData() ) : mRoot;
+ if ( row < 0 || column != 0 || static_cast( row ) >= node->visibleChildren.size() )
+ return {};
+ return createIndex( row, column, node->visibleChildren[row] );
+ }
+
+ ModelIndex parentIndex( const ModelIndex& index ) const {
+ if ( !index.isValid() )
+ return {};
+ auto* node = static_cast( index.internalData() );
+ if ( !node->parent || node->parent == mRoot )
+ return {};
+ auto* parent = node->parent;
+ auto found =
+ std::find( mRoot->visibleChildren.begin(), mRoot->visibleChildren.end(), parent );
+ return found == mRoot->visibleChildren.end()
+ ? ModelIndex{}
+ : createIndex( std::distance( mRoot->visibleChildren.begin(), found ), 0,
+ parent );
+ }
+
+ Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const {
+ if ( !index.isValid() || role != ModelRole::Display )
+ return {};
+ return Variant( static_cast( index.internalData() )->text );
+ }
+
+ void filter( const std::string_view query,
+ const UnorderedSet& matchingCategories ) {
+ mRoot->visibleChildren.clear();
+ for ( auto* parent : mRoot->children ) {
+ parent->visibleChildren.clear();
+ const bool parentMatches = query.empty() || String::icontains( parent->text, query );
+ for ( auto* child : parent->children ) {
+ if ( parentMatches || String::icontains( child->text, query ) ||
+ matchingCategories.contains( child->id ) )
+ parent->visibleChildren.push_back( child );
+ }
+ if ( !parent->visibleChildren.empty() )
+ mRoot->visibleChildren.push_back( parent );
+ }
+ invalidate( Model::UpdateFlag::InvalidateAllIndexes );
+ }
+
+ private:
+ std::deque mNodes;
+ Node* mRoot{ nullptr };
+};
+
+static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
+
+
+
+
+
+
+
+
+
+
+
+
+)xml";
+
+static constexpr const char* SETTINGS_ROW_LAYOUT = R"xml(
+
+
+
+
+
+
+
+
+
+)xml";
+
+static constexpr const char* SETTINGS_CATEGORY_HEADING_LAYOUT =
+ R"xml()xml";
+static constexpr const char* SETTINGS_BOOL_LAYOUT = R"xml()xml";
+static constexpr const char* SETTINGS_CHOICE_LAYOUT =
+ R"xml()xml";
+static constexpr const char* SETTINGS_INTEGER_LAYOUT =
+ R"xml()xml";
+static constexpr const char* SETTINGS_ACTION_LAYOUT =
+ R"xml()xml";
+
+SettingsPanel::SettingsPanel( App* app ) : mApp( app ) {}
+
+SettingsPanel::PanelState& SettingsPanel::state( Scope scope ) {
+ return scope == Scope::User ? mUser : mProject;
+}
+
+void SettingsPanel::PanelState::reset() {
+ bindingGroup.clear();
+ connections.clear();
+ window = nullptr;
+ search = nullptr;
+ categories = nullptr;
+ scroll = nullptr;
+ settings = nullptr;
+ pageTitle = nullptr;
+ categoryModel.reset();
+ categoryItems.clear();
+ categoryIds.clear();
+ categorySearchText.clear();
+ categoryTitles.clear();
+ categoryHeadings.clear();
+ bindings.clear();
+ selectedCategory.clear();
+ categoryFilter.clear();
+}
+
+void SettingsPanel::show( Scope scope ) {
+ auto& panel = state( scope );
+ if ( panel.window ) {
+ panel.window->show();
+ panel.window->toFront();
+ panel.categories->setFocus();
+ return;
+ }
+ create( scope );
+}
+
+void SettingsPanel::create( Scope scope ) {
+ auto& panel = state( scope );
+ UIWindow::StyleConfig config{ UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL };
+ panel.window = UIWindow::NewOpt( UIWindow::SIMPLE_LAYOUT, config );
+ panel.window->setId( scope == Scope::User ? "settings_panel" : "project_settings_panel" );
+ panel.window->setTitle( scope == Scope::User
+ ? mApp->i18n( "settings", "Settings" )
+ : mApp->i18n( "project_settings", "Project Settings" ) );
+ const auto sceneSize = mApp->getUISceneNode()->getPixelsSize();
+ panel.window->setPixelsSize( { eeclamp( sceneSize.getWidth() * 0.82f, 720.f, 1200.f ),
+ eeclamp( sceneSize.getHeight() * 0.82f, 520.f, 850.f ) } );
+ panel.window->setMinWindowSize( 640, 440 );
+ panel.window->setKeyBindingCommand( "closeWindow", [window = panel.window, this] {
+ if ( !SceneManager::instance()->isShuttingDown() ) {
+ window->closeWindow();
+ if ( mApp->getSplitter() && mApp->getSplitter()->getCurWidget() )
+ mApp->getSplitter()->getCurWidget()->setFocus();
+ }
+ } );
+ panel.window->getKeyBindings().addKeybind( { KEY_ESCAPE }, "closeWindow" );
+ auto* layout = mApp->getUISceneNode()->loadLayoutFromString( SETTINGS_PANEL_LAYOUT,
+ panel.window->getContainer() );
+ panel.search = layout->find( "settings_filter" );
+ panel.categories = layout->find( "settings_categories" );
+ panel.settings = layout->find( "settings_rows" );
+ panel.pageTitle = layout->find( "settings_page_title" );
+ panel.scroll = layout->find( "settings_scroll" );
+ panel.scroll->setVerticalScrollMode( ScrollBarMode::Auto );
+ panel.scroll->setHorizontalScrollMode( ScrollBarMode::AlwaysOff );
+ if ( scope == Scope::User )
+ addUserSettings( panel );
+ else
+ addProjectSettings( panel );
+ setupCategories( panel );
+ filter( panel );
+ panel.connections += panel.search->connect(
+ Event::OnTextChanged, [this, &panel]( const Event* ) { filter( panel ); } );
+ panel.connections += panel.window->connect( Event::OnWindowReady, [&panel]( const Event* ) {
+ auto title = panel.categoryTitles.find( panel.selectedCategory );
+ if ( title != panel.categoryTitles.end() ) {
+ auto index = panel.categories->findRowWithText(
+ title->second.toUtf8(), true, UIAbstractView::FindRowWithTextMatchKind::Equals );
+ if ( index.isValid() )
+ panel.categories->setSelection( index );
+ }
+ panel.categories->setFocus();
+ } );
+ panel.connections +=
+ panel.window->connect( Event::OnWindowClose, [this, &panel, scope]( const Event* ) {
+ if ( scope == Scope::User )
+ mApp->saveConfig();
+ else
+ mApp->saveProject();
+ panel.reset();
+ } );
+ panel.window->center();
+ panel.window->showWhenReady();
+ panel.categories->setFocus();
+}
+
+void SettingsPanel::addCategory( PanelState& panel, const std::string& id, const String& parent,
+ const String& name ) {
+ const auto parentText = parent.toUtf8();
+ const auto nameText = name.toUtf8();
+ auto parentItems =
+ std::find_if( panel.categoryItems.begin(), panel.categoryItems.end(),
+ [&parentText]( const auto& item ) { return item.first == parentText; } );
+ if ( parentItems == panel.categoryItems.end() ) {
+ panel.categoryItems.emplace_back( parentText, std::vector{ nameText } );
+ } else {
+ parentItems->second.emplace_back( nameText );
+ }
+ panel.categoryIds[parentText + '/' + nameText] = id;
+ panel.categorySearchText[id] = parent + " " + name;
+ panel.categoryTitles[id] = name;
+ auto* heading = mApp->getUISceneNode()
+ ->loadLayoutFromString( SETTINGS_CATEGORY_HEADING_LAYOUT, panel.settings )
+ ->asType();
+ heading->setText( name );
+ heading->setId( "settings_category_" + id );
+ panel.categoryHeadings[id] = heading;
+ if ( panel.selectedCategory.empty() )
+ panel.selectedCategory = id;
+}
+
+void SettingsPanel::setupCategories( PanelState& panel ) {
+ auto model = SettingsCategoryModel::create( panel.categoryItems, panel.categoryIds );
+ panel.categoryModel = model;
+ panel.categories->setHeadersVisible( false );
+ panel.categories->setAutoExpandOnSingleColumn( true );
+ panel.categories->setFocusOnSelection( true );
+ panel.categories->setModel( model );
+ panel.categories->expandAll();
+ panel.connections +=
+ panel.categories->connect( Event::OnSelectionChanged, [this, &panel]( const Event* ) {
+ auto index = panel.categories->getSelection().first();
+ if ( !index.isValid() )
+ return;
+ auto* node = static_cast( index.internalData() );
+ if ( !node || node->id.empty() )
+ return;
+ panel.selectedCategory = node->id;
+ panel.pageTitle->setText( node->text );
+ filter( panel );
+ panel.scroll->getVerticalScrollBar()->setValue( 0, false );
+ } );
+}
+
+UIWidget* SettingsPanel::createRow( PanelState& panel, SettingBinding& binding ) {
+ auto* row = mApp->getUISceneNode()->loadLayoutFromString( SETTINGS_ROW_LAYOUT, panel.settings );
+ row->setId( "setting_" + binding.id );
+ row->find( "setting_name" )->setText( binding.name );
+ auto* description = row->find( "setting_description" );
+ description->setText( binding.description );
+ binding.row = row;
+ return row;
+}
+
+void SettingsPanel::addBool( PanelState& panel, SettingBinding binding, bool* value,
+ std::function apply ) {
+ auto* check = createBoolControl( panel, binding );
+ auto valueBinding =
+ UIDataBind::New( value, check, UIValueConverter::converterBool() );
+ valueBinding->onValueChangeCb = std::move( apply );
+ panel.bindingGroup += std::move( valueBinding );
+ panel.bindings.emplace_back( std::move( binding ) );
+}
+
+void SettingsPanel::addBool( PanelState& panel, SettingBinding binding, std::function get,
+ std::function set ) {
+ auto* check = createBoolControl( panel, binding );
+ check->setChecked( get() );
+ panel.connections +=
+ check->connect( Event::OnValueChange, [check, set = std::move( set )]( const Event* ) {
+ set( check->isChecked() );
+ } );
+ panel.bindings.emplace_back( std::move( binding ) );
+}
+
+UICheckBox* SettingsPanel::createBoolControl( PanelState& panel, SettingBinding& binding ) {
+ auto* row = createRow( panel, binding );
+ row->addClass( "settings_boolean_option" );
+ auto* check = mApp->getUISceneNode()
+ ->loadLayoutFromString( SETTINGS_BOOL_LAYOUT,
+ row->find( "setting_control" ) )
+ ->asType();
+ auto toggle = [check]( const Event* ) { check->setChecked( !check->isChecked() ); };
+ panel.connections +=
+ row->find( "setting_name" )->connect( Event::MouseClick, toggle );
+ panel.connections += row->find( "setting_description" )
+ ->connect( Event::MouseClick, std::move( toggle ) );
+ return check;
+}
+
+void SettingsPanel::addChoice( PanelState& panel, SettingBinding binding,
+ const std::vector& choices, std::function get,
+ std::function set,
+ std::vector choiceDescriptions ) {
+ auto* row = createRow( panel, binding );
+ auto* dropDown = mApp->getUISceneNode()
+ ->loadLayoutFromString( SETTINGS_CHOICE_LAYOUT,
+ row->find( "setting_control" ) )
+ ->asType();
+ dropDown->getListBox()->addListBoxItems( choices );
+ const size_t selected = get();
+ dropDown->getListBox()->setSelected( selected );
+ if ( selected < choiceDescriptions.size() )
+ dropDown->setTooltipText( choiceDescriptions[selected] );
+ panel.connections += dropDown->connect(
+ Event::OnValueChange, [dropDown, set = std::move( set ),
+ descriptions = std::move( choiceDescriptions )]( const Event* ) {
+ if ( !dropDown->getListBox()->hasSelection() )
+ return;
+ const size_t selected = dropDown->getListBox()->getItemSelectedIndex();
+ if ( selected < descriptions.size() )
+ dropDown->setTooltipText( descriptions[selected] );
+ set( selected );
+ } );
+ panel.bindings.emplace_back( std::move( binding ) );
+}
+
+void SettingsPanel::addInteger( PanelState& panel, SettingBinding binding, int min, int max,
+ int* value, std::function apply ) {
+ auto* row = createRow( panel, binding );
+ auto* spin = mApp->getUISceneNode()
+ ->loadLayoutFromString( SETTINGS_INTEGER_LAYOUT,
+ row->find( "setting_control" ) )
+ ->asType();
+ spin->setMinValue( min )->setMaxValue( max );
+ auto valueBinding = UIDataBind::New( value, spin );
+ valueBinding->onValueChangeCb = std::move( apply );
+ panel.bindingGroup += std::move( valueBinding );
+ panel.bindings.emplace_back( std::move( binding ) );
+}
+
+void SettingsPanel::addAction( PanelState& panel, SettingBinding binding, const String& buttonText,
+ std::function action ) {
+ auto* row = createRow( panel, binding );
+ auto* button = mApp->getUISceneNode()
+ ->loadLayoutFromString( SETTINGS_ACTION_LAYOUT,
+ row->find( "setting_control" ) )
+ ->asType();
+ button->setText( buttonText );
+ panel.connections += button->connect(
+ Event::MouseClick, [action = std::move( action )]( const Event* ) { action(); } );
+ panel.bindings.emplace_back( std::move( binding ) );
+}
+
+void SettingsPanel::addUserSettings( PanelState& panel ) {
+ addCategory( panel, "general.behavior", mApp->i18n( "general", "General" ),
+ mApp->i18n( "behavior", "Behavior" ) );
+ addBool( panel,
+ { "welcomeScreen", "general.behavior",
+ mApp->i18n( "welcome_screen_enable", "Enable Welcome Screen" ),
+ mApp->i18n( "welcome_screen_enable_desc",
+ "Show the welcome screen when no document or folder is open." ) },
+ &mApp->getConfig().ui.welcomeScreen );
+ addBool(
+ panel,
+ { "openFilesInNewWindow", "general.behavior",
+ mApp->i18n( "open_files_in_new_window_enable", "Open Files in New Window" ),
+ mApp->i18n(
+ "open_files_in_new_window_desc",
+ "Open files received from the file explorer or command line in a new window." ) },
+ &mApp->getConfig().ui.openFilesInNewWindow );
+ addBool(
+ panel,
+ { "openProjectInNewWindow", "general.behavior",
+ mApp->i18n( "open_project_in_new_window", "Open Project in New Window" ),
+ mApp->i18n( "open_project_in_new_window_tooltip",
+ "Open folders in a new window when another project is already loaded." ) },
+ &mApp->getConfig().ui.openProjectInNewWindow );
+ addBool( panel,
+ { "nativeFileDialogs", "general.behavior",
+ mApp->i18n( "use_native_file_dialogs", "Enable Native File Dialogs" ),
+ mApp->i18n( "use_native_file_dialogs_tooltip",
+ "Use operating-system file dialogs when available." ) },
+ &mApp->getConfig().ui.nativeFileDialogs );
+ addBool( panel,
+ { "imagesQuickPreview", "general.behavior",
+ mApp->i18n( "quick_preview_images", "Quick Preview Images" ),
+ mApp->i18n( "quick_preview_images_tooltip",
+ "Preview images without opening a permanent editor tab." ) },
+ &mApp->getConfig().ui.imagesQuickPreview );
+
+ addCategory( panel, "editor.appearance", mApp->i18n( "editor", "Editor" ),
+ mApp->i18n( "appearance", "Appearance" ) );
+ std::vector editorSchemeNames;
+ std::vector editorSchemeIds;
+ for ( const auto& [name, scheme] : mApp->getSplitter()->getColorSchemes() ) {
+ editorSchemeNames.emplace_back( name );
+ editorSchemeIds.emplace_back( name );
+ }
+ auto selectedEditorScheme =
+ std::find( editorSchemeNames.begin(), editorSchemeNames.end(),
+ String( mApp->getSplitter()->getCurrentColorSchemeName() ) );
+ const size_t selectedEditorSchemeIndex =
+ selectedEditorScheme == editorSchemeNames.end()
+ ? 0
+ : static_cast( selectedEditorScheme - editorSchemeNames.begin() );
+ if ( !editorSchemeNames.empty() )
+ addChoice(
+ panel,
+ { "editorColorScheme", "editor.appearance",
+ mApp->i18n( "syntax_color_scheme", "Syntax Color Scheme" ),
+ mApp->i18n( "syntax_color_scheme_desc",
+ "Choose the colors used for syntax highlighting and editor surfaces." ) },
+ editorSchemeNames, [selectedEditorSchemeIndex] { return selectedEditorSchemeIndex; },
+ [this, editorSchemeIds = std::move( editorSchemeIds )]( size_t selected ) {
+ mApp->getSplitter()->setColorScheme(
+ editorSchemeIds[std::min( selected, editorSchemeIds.size() - 1 )] );
+ } );
+ auto addEditorBool = [this, &panel]( std::string id, const char* nameKey, const char* name,
+ const char* descriptionKey, const char* description,
+ bool CodeEditorConfig::* member, auto apply ) {
+ addBool( panel,
+ { std::move( id ), "editor.appearance", mApp->i18n( nameKey, name ),
+ mApp->i18n( descriptionKey, description ) },
+ &( mApp->getConfig().editor.*member ), [this, apply]( bool value ) {
+ mApp->getSplitter()->forEachEditor(
+ [value, apply]( UICodeEditor* editor ) { apply( editor, value ); } );
+ } );
+ };
+ addEditorBool( "showLineNumbers", "show_line_numbers", "Show Line Numbers",
+ "show_line_numbers_desc", "Display line numbers beside the editor.",
+ &CodeEditorConfig::showLineNumbers,
+ []( UICodeEditor* editor, bool value ) { editor->setShowLineNumber( value ); } );
+ addEditorBool(
+ "showWhiteSpaces", "show_white_spaces", "Show White Spaces", "show_white_spaces_desc",
+ "Display visible markers for whitespace characters.", &CodeEditorConfig::showWhiteSpaces,
+ []( UICodeEditor* editor, bool value ) { editor->setShowWhitespaces( value ); } );
+ addEditorBool(
+ "highlightCurrentLine", "highlight_current_line", "Highlight Current Line",
+ "highlight_current_line_desc", "Highlight the line containing the primary cursor.",
+ &CodeEditorConfig::highlightCurrentLine,
+ []( UICodeEditor* editor, bool value ) { editor->setHighlightCurrentLine( value ); } );
+ addBool( panel,
+ { "showDocumentInfo", "editor.appearance",
+ mApp->i18n( "show_doc_info", "Show Document Info" ),
+ mApp->i18n( "show_doc_info_desc",
+ "Display document encoding and line-ending information." ) },
+ &mApp->getConfig().editor.showDocInfo, [this]( bool value ) {
+ if ( mApp->getDocInfo() )
+ mApp->getDocInfo()->setVisible( value );
+ } );
+ addEditorBool(
+ "showLineEndings", "show_line_endings", "Show Line Endings", "show_line_endings_desc",
+ "Display markers for line-ending characters.", &CodeEditorConfig::showLineEndings,
+ []( UICodeEditor* editor, bool value ) { editor->setShowLineEndings( value ); } );
+ addEditorBool(
+ "showIndentationGuides", "show_indentation_guides", "Show Indentation Guides",
+ "show_indentation_guides_desc", "Display vertical guides for indentation levels.",
+ &CodeEditorConfig::showIndentationGuides,
+ []( UICodeEditor* editor, bool value ) { editor->setShowIndentationGuides( value ); } );
+ addEditorBool( "minimap", "show_minimap", "Show Minimap", "show_minimap_desc",
+ "Display a compact overview of the document.", &CodeEditorConfig::minimap,
+ []( UICodeEditor* editor, bool value ) { editor->showMinimap( value ); } );
+ addEditorBool(
+ "relativeLinePositions", "show_lines_relative_position", "Show Lines Relative Position",
+ "show_lines_relative_position_desc", "Show line numbers relative to the primary cursor.",
+ &CodeEditorConfig::linesRelativePosition,
+ []( UICodeEditor* editor, bool value ) { editor->showLinesRelativePosition( value ); } );
+ addEditorBool(
+ "highlightMatchingBracket", "highlight_matching_brackets", "Highlight Matching Bracket",
+ "highlight_matching_brackets_desc",
+ "Highlight the bracket paired with the one beside the cursor.",
+ &CodeEditorConfig::highlightMatchingBracket,
+ []( UICodeEditor* editor, bool value ) { editor->setHighlightMatchingBracket( value ); } );
+ addEditorBool(
+ "highlightSelectionMatch", "highlight_selection_match", "Highlight Selection Match",
+ "highlight_selection_match_desc", "Highlight text matching the current selection.",
+ &CodeEditorConfig::highlightSelectionMatch,
+ []( UICodeEditor* editor, bool value ) { editor->setHighlightSelectionMatch( value ); } );
+ addEditorBool( "verticalScrollbar", "enable_vertical_scrollbar", "Enable Vertical Scrollbar",
+ "enable_vertical_scrollbar_desc", "Display the editor's vertical scrollbar.",
+ &CodeEditorConfig::verticalScrollbar, []( UICodeEditor* editor, bool value ) {
+ editor->setVerticalScrollBarEnabled( value );
+ } );
+ addEditorBool( "horizontalScrollbar", "enable_horizontal_scrollbar",
+ "Enable Horizontal Scrollbar", "enable_horizontal_scrollbar_desc",
+ "Display the editor's horizontal scrollbar.",
+ &CodeEditorConfig::horizontalScrollbar, []( UICodeEditor* editor, bool value ) {
+ editor->setHorizontalScrollBarEnabled( value );
+ } );
+ addEditorBool( "inlineColorBoxes", "enable_inline_color_boxes", "Enable Color Boxes",
+ "enable_inline_color_boxes_tooltip",
+ "Display inline color boxes beside recognized color values.",
+ &CodeEditorConfig::inlineColorBoxes, []( UICodeEditor* editor, bool value ) {
+ editor->setEnableInlineColorBoxes( value );
+ } );
+ addEditorBool( "colorPreview", "enable_color_preview", "Enable Color Preview",
+ "enable_color_preview_tooltip", "Preview selected color values in the editor.",
+ &CodeEditorConfig::colorPreview,
+ []( UICodeEditor* editor, bool value ) { editor->setColorPreview( value ); } );
+ addEditorBool( "colorPickerSelection", "enable_color_picker", "Enable Color Picker",
+ "enable_color_picker_tooltip",
+ "Open the color picker for recognized color selections.",
+ &CodeEditorConfig::colorPickerSelection, []( UICodeEditor* editor, bool value ) {
+ editor->setEnableColorPickerOnSelection( value );
+ } );
+
+ addCategory( panel, "editor.navigation", mApp->i18n( "editor", "Editor" ),
+ mApp->i18n( "navigation", "Navigation" ) );
+ addBool( panel,
+ { "singleClickNavigation", "editor.navigation",
+ mApp->i18n( "treeview_single_click_nav", "Single Click Navigation" ),
+ mApp->i18n( "treeview_single_click_nav_tooltip",
+ "Open project-tree files with a single click." ) },
+ &mApp->getConfig().editor.singleClickNavigation, [this]( bool value ) {
+ if ( mApp->getProjectTreeView() )
+ mApp->getProjectTreeView()->setSingleClickNavigation( value );
+ } );
+ addBool( panel,
+ { "syncProjectTree", "editor.navigation",
+ mApp->i18n( "sync_project_tree", "Synchronize Project Tree with Editor" ),
+ mApp->i18n( "sync_project_tree_tooltip",
+ "Select the active document in the project tree." ) },
+ &mApp->getConfig().editor.syncProjectTreeWithEditor );
+ addBool(
+ panel,
+ { "restoreSelectionOnFocus", "editor.navigation",
+ mApp->i18n( "restore_editor_selection_on_focus", "Restore Editor Selection on Focus" ),
+ mApp->i18n( "restore_editor_selection_on_focus_tooltip",
+ "Restore each editor split's last cursor and selection when focused." ) },
+ &mApp->getConfig().editor.restoreEditorSelectionOnFocus,
+ [this]( bool value ) { mApp->getSplitter()->setRestoreEditorSelectionOnFocus( value ); } );
+
+ addCategory( panel, "editor.formatting", mApp->i18n( "editor", "Editor" ),
+ mApp->i18n( "formatting", "Formatting" ) );
+ addChoice(
+ panel,
+ { "lineWrapMode", "editor.formatting", mApp->i18n( "line_wrap", "Line Wrap" ),
+ mApp->i18n( "line_wrap_desc", "Controls how long lines wrap in the editor." ) },
+ { mApp->i18n( "no_wrap", "No Wrap" ), mApp->i18n( "wrap_word", "Word Wrap" ),
+ mApp->i18n( "wrap_letter", "Letter Wrap" ) },
+ [this] {
+ switch ( mApp->getConfig().editor.wrapMode ) {
+ case LineWrapMode::NoWrap:
+ return size_t{ 0 };
+ case LineWrapMode::Letter:
+ return size_t{ 2 };
+ case LineWrapMode::Word:
+ default:
+ return size_t{ 1 };
+ }
+ },
+ [this]( size_t selected ) {
+ auto mode = selected == 0 ? LineWrapMode::NoWrap
+ : selected == 2 ? LineWrapMode::Letter
+ : LineWrapMode::Word;
+ mApp->getConfig().editor.wrapMode = mode;
+ mApp->getSplitter()->forEachEditor(
+ [mode]( UICodeEditor* editor ) { editor->setLineWrapMode( mode ); } );
+ } );
+ addInteger(
+ panel,
+ { "lineBreakingColumn", "editor.formatting",
+ 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 ) {
+ mApp->getSplitter()->forEachEditor(
+ [value]( UICodeEditor* editor ) { editor->setLineBreakingColumn( value ); } );
+ } );
+ addChoice(
+ panel,
+ { "lineWrapType", "editor.formatting", mApp->i18n( "wrap_type", "Wrap Against" ),
+ mApp->i18n( "wrap_type_desc",
+ "Choose whether lines wrap at the viewport or line breaking column." ) },
+ { mApp->i18n( "viewport", "Viewport" ),
+ mApp->i18n( "line_breaking_column", "Line Breaking Column" ) },
+ [this] { return mApp->getConfig().editor.wrapType == LineWrapType::Viewport ? 0 : 1; },
+ [this]( size_t selected ) {
+ auto value = selected == 0 ? LineWrapType::Viewport : LineWrapType::LineBreakingColumn;
+ mApp->getConfig().editor.wrapType = value;
+ mApp->getSplitter()->forEachEditor(
+ [value]( UICodeEditor* editor ) { editor->setLineWrapType( value ); } );
+ } );
+ addBool( panel,
+ { "wrapKeepIndentation", "editor.formatting",
+ mApp->i18n( "keep_indentation", "Keep Indentation" ),
+ mApp->i18n( "keep_indentation_desc",
+ "Preserve indentation on wrapped continuation lines." ) },
+ &mApp->getConfig().editor.wrapKeepIndentation, [this]( bool value ) {
+ mApp->getSplitter()->forEachEditor( [value]( UICodeEditor* editor ) {
+ editor->setLineWrapKeepIndentation( value );
+ } );
+ } );
+
+ addCategory( panel, "editor.folding", mApp->i18n( "editor", "Editor" ),
+ mApp->i18n( "code_folding", "Code Folding" ) );
+ addBool( panel,
+ { "codeFoldingEnabled", "editor.folding", mApp->i18n( "enabled", "Enabled" ),
+ mApp->i18n( "code_folding_enabled_desc", "Enable syntax-aware code folding." ) },
+ &mApp->getConfig().editor.codeFoldingEnabled, [this]( bool value ) {
+ mApp->getSplitter()->forEachDoc( [value]( TextDocument& doc ) {
+ doc.getFoldRangeService().setEnabled( value );
+ } );
+ } );
+ addBool( panel,
+ { "codeFoldingAlwaysVisible", "editor.folding",
+ mApp->i18n( "code_folding_always_display_folds", "Folds Always Visible" ),
+ mApp->i18n( "code_folding_always_visible_desc",
+ "Always display fold controls in the editor gutter." ) },
+ &mApp->getConfig().editor.codeFoldingAlwaysVisible, [this]( bool value ) {
+ mApp->getSplitter()->forEachEditor(
+ [value]( UICodeEditor* editor ) { editor->setFoldsAlwaysVisible( value ); } );
+ } );
+
+ addCategory( panel, "editor.tabs", mApp->i18n( "editor", "Editor" ),
+ mApp->i18n( "tab_bar", "Tab Bar" ) );
+ addBool( panel,
+ { "hideTabBar", "editor.tabs", mApp->i18n( "hide_tabbar", "Hide Tab Bar" ),
+ mApp->i18n( "hide_tabbar_tooltip", "Always hide the tab bar." ) },
+ &mApp->getConfig().editor.hideTabBar,
+ [this]( bool value ) { mApp->getSplitter()->setHideTabBar( value ); } );
+ addBool( panel,
+ { "hideTabBarOnSingleTab", "editor.tabs",
+ mApp->i18n( "hide_tabbar_on_single_tab", "Hide Tab Bar on Single Tab" ),
+ mApp->i18n( "hide_tabbar_on_single_tab_tooltip",
+ "Hide the tab bar when a tab widget contains only one item." ) },
+ &mApp->getConfig().editor.hideTabBarOnSingleTab,
+ [this]( bool value ) { mApp->getSplitter()->setHideTabBarOnSingleTab( value ); } );
+ addBool( panel,
+ { "tabSwitcher", "editor.tabs",
+ mApp->i18n( "display_tab_switcher", "Display Tab Switcher" ),
+ mApp->i18n( "display_tab_switcher_tooltip",
+ "Display a tab switcher in the center of each tab widget." ) },
+ &mApp->getConfig().editor.tabSwitcher );
+ addBool( panel,
+ { "openDocumentsInMainSplit", "editor.tabs",
+ mApp->i18n( "open_documents_in_main_split", "Open Documents in Main Split" ),
+ mApp->i18n( "open_documents_in_main_split_desc",
+ "Open externally requested documents in the main editor split." ) },
+ &mApp->getConfig().editor.openDocumentsInMainSplit );
+ addChoice(
+ panel,
+ { "tabJumpMode", "editor.tabs", mApp->i18n( "tab_jump_mode", "Tab Jump Mode" ),
+ mApp->i18n( "tab_jump_mode_desc",
+ "Choose how keyboard navigation cycles through tabs." ) },
+ { mApp->i18n( "linear", "Linear" ), mApp->i18n( "chronological", "Chronological" ) },
+ [this] {
+ return mApp->getConfig().editor.tabJumpMode == UITabWidget::TabJumpMode::Linear ? 0 : 1;
+ },
+ [this]( size_t selected ) {
+ mApp->getConfig().editor.tabJumpMode = selected == 0
+ ? UITabWidget::TabJumpMode::Linear
+ : UITabWidget::TabJumpMode::Chronological;
+ },
+ { mApp->i18n( "jump_mode_linear_tooltip",
+ "Linear Jump Mode will switch tabs in the order they are displayed" ),
+ mApp->i18n(
+ "jump_mode_chronological_tooltip",
+ "Chronological Jump Mode will switch tabs in the last focused / visited order." ) } );
+ addChoice(
+ panel,
+ { "newTabPosition", "editor.tabs", mApp->i18n( "new_tab_position", "New Tab Position" ),
+ mApp->i18n( "new_tab_position_desc", "Choose where newly opened tabs are inserted." ) },
+ { mApp->i18n( "new_tab_position_after_active", "After Active Tab" ),
+ mApp->i18n( "new_tab_position_last", "Last" ),
+ mApp->i18n( "new_tab_position_first", "First" ),
+ mApp->i18n( "new_tab_position_left_of_active", "Left of Active Tab" ) },
+ [this] {
+ switch ( mApp->getConfig().editor.newTabPosition ) {
+ case NewTabPosition::AfterActive:
+ return size_t{ 0 };
+ case NewTabPosition::First:
+ return size_t{ 2 };
+ case NewTabPosition::LeftOfActive:
+ return size_t{ 3 };
+ default:
+ return size_t{ 1 };
+ }
+ },
+ [this]( size_t selected ) {
+ static constexpr NewTabPosition::Position positions[] = {
+ NewTabPosition::AfterActive, NewTabPosition::Last, NewTabPosition::First,
+ NewTabPosition::LeftOfActive };
+ mApp->getConfig().editor.newTabPosition = positions[std::min( selected, size_t{ 3 } )];
+ } );
+
+ addCategory( panel, "editor.document", mApp->i18n( "editor", "Editor" ),
+ mApp->i18n( "document_defaults", "Document Defaults" ) );
+ auto addDocumentBool = [this, &panel]( std::string id, const char* nameKey, const char* name,
+ const char* descriptionKey, const char* description,
+ bool DocumentConfig::* member,
+ std::function apply = {} ) {
+ addBool( panel,
+ { std::move( id ), "editor.document", mApp->i18n( nameKey, name ),
+ mApp->i18n( descriptionKey, description ) },
+ &( mApp->getConfig().doc.*member ), std::move( apply ) );
+ };
+ addDocumentBool( "autoDetectIndentType", "auto_detect_indent_type_and_width",
+ "Auto Detect Indent Type & Width", "auto_detect_indent_type_desc",
+ "Detect indentation settings when opening each document.",
+ &DocumentConfig::autoDetectIndentType );
+ addChoice(
+ panel,
+ { "autoIndent", "editor.document", mApp->i18n( "auto_indent", "Auto-Indent" ),
+ mApp->i18n( "auto_indent_tooltip", "Control indentation added after pressing Enter." ) },
+ { mApp->i18n( "auto_indent_none", "None" ),
+ mApp->i18n( "auto_indent_preserve", "Preserve" ),
+ mApp->i18n( "auto_indent_smart", "Smart" ) },
+ [this] { return static_cast( mApp->getConfig().doc.autoIndent ); },
+ [this]( size_t selected ) {
+ auto value =
+ static_cast( std::min( selected, size_t{ 2 } ) );
+ mApp->getConfig().doc.autoIndent = value;
+ mApp->getSplitter()->forEachEditor(
+ [value]( UICodeEditor* editor ) { editor->getDocument().setAutoIndent( value ); } );
+ },
+ { mApp->i18n( "auto_indent_none_desc", "No automatic indentation." ),
+ mApp->i18n( "auto_indent_preserve_desc",
+ "Preserve the indentation level of the previous line." ),
+ mApp->i18n( "auto_indent_smart_desc",
+ "Preserve indentation and indent between auto-closed brackets." ) } );
+ addDocumentBool( "indentSpaces", "indent_spaces", "Indent Using Spaces", "indent_spaces_desc",
+ "Insert spaces instead of tab characters for indentation.",
+ &DocumentConfig::indentSpaces );
+ addDocumentBool( "trimTrailingWhitespaces", "trim_trailing_whitespaces",
+ "Trim Trailing Whitespaces", "trim_trailing_whitespaces_desc",
+ "Remove trailing whitespace when saving files.",
+ &DocumentConfig::trimTrailingWhitespaces );
+ addDocumentBool( "forceNewLineAtEndOfFile", "force_new_line_at_end_of_file",
+ "Force New Line at End of File", "force_new_line_at_end_of_file_desc",
+ "Ensure saved files end with a newline.",
+ &DocumentConfig::forceNewLineAtEndOfFile );
+ addDocumentBool( "writeUnicodeBOM", "write_unicode_bom", "Write Unicode BOM",
+ "write_unicode_bom_desc", "Write a Unicode byte-order mark when saving.",
+ &DocumentConfig::writeUnicodeBOM );
+ addDocumentBool( "tabStops", "tab_stops", "Tab Stops", "tab_stops_desc",
+ "Align tabs to consistent positions on the tab grid.",
+ &DocumentConfig::tabStops, [this]( bool value ) {
+ mApp->getSplitter()->forEachEditor(
+ [value]( UICodeEditor* editor ) { editor->setTabStops( value ); } );
+ } );
+ addDocumentBool( "tabOut", "tab_out", "Tab Out", "tab_out_desc",
+ "Press Tab before a closing character to move past it.",
+ &DocumentConfig::tabOutEnabled, [this]( bool value ) {
+ mApp->getSplitter()->forEachEditor( [value]( UICodeEditor* editor ) {
+ 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 );
+ addChoice(
+ panel,
+ { "lineEndings", "editor.document", mApp->i18n( "line_endings", "Line Endings" ),
+ mApp->i18n( "line_endings_desc", "Default line-ending sequence for new files." ) },
+ { "Windows (CR/LF)", "Unix (LF)", "Macintosh (CR)" },
+ [this] {
+ switch ( mApp->getConfig().doc.lineEndings ) {
+ case TextFormat::LineEnding::CRLF:
+ return size_t{ 0 };
+ case TextFormat::LineEnding::CR:
+ return size_t{ 2 };
+ default:
+ return size_t{ 1 };
+ }
+ },
+ [this]( size_t selected ) {
+ mApp->getConfig().doc.lineEndings =
+ selected == 0
+ ? TextFormat::LineEnding::CRLF
+ : ( selected == 2 ? TextFormat::LineEnding::CR : TextFormat::LineEnding::LF );
+ } );
+ 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->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 containsPair = [this, pair] {
+ auto pairs = String::split( mApp->getConfig().editor.autoCloseBrackets, ',' );
+ return std::find( pairs.begin(), pairs.end(), pair ) != pairs.end();
+ };
+ addBool( panel,
+ { std::move( id ), "editor.document", mApp->i18n( nameKey, name ),
+ mApp->i18n( "auto_close_pair_desc",
+ "Automatically insert the matching closing character." ) },
+ 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 );
+ if ( enabled && found == pairs.end() )
+ pairs.emplace_back( pair );
+ else if ( !enabled && found != pairs.end() )
+ pairs.erase( found );
+ mApp->getConfig().editor.autoCloseBrackets = String::join( pairs, ',' );
+ auto closePairs =
+ mApp->makeAutoClosePairs( mApp->getConfig().editor.autoCloseBrackets );
+ mApp->getSplitter()->forEachEditor( [closePairs]( UICodeEditor* editor ) {
+ editor->getDocument().setAutoCloseBrackets( !closePairs.empty() );
+ editor->getDocument().setAutoCloseBracketsPairs( closePairs );
+ } );
+ } );
+ };
+ addAutoClosePair( "autoCloseBrackets", "brackets", "Brackets ()", "()" );
+ addAutoClosePair( "autoCloseCurlyBrackets", "curly_brackets", "Curly Brackets {}", "{}" );
+ addAutoClosePair( "autoCloseSquareBrackets", "square_brakcets", "Square Brackets []", "[]" );
+ addAutoClosePair( "autoCloseSingleQuotes", "single_quotes", "Single Quotes ''", "''" );
+ addAutoClosePair( "autoCloseDoubleQuotes", "double_quotes", "Double Quotes \"\"", "\"\"" );
+ addAutoClosePair( "autoCloseBackQuotes", "back_quotes", "Back Quotes ``", "``" );
+ addBool( panel,
+ { "autoReloadOnDiskChange", "editor.document",
+ mApp->i18n( "autoreload_on_disk_change", "Auto-Reload on Disk Change" ),
+ mApp->i18n( "autoreload_on_disk_change_desc",
+ "Reload unmodified documents when their files change on disk." ) },
+ &mApp->getConfig().editor.autoReloadOnDiskChange );
+
+ addCategory( panel, "general.workspace", mApp->i18n( "general", "General" ),
+ mApp->i18n( "workspace", "Workspace" ) );
+ addBool( panel,
+ { "sessionSnapshot", "general.workspace",
+ mApp->i18n( "session_snapshot", "Session Snapshot & Periodic Backup" ),
+ mApp->i18n( "session_snapshot_desc",
+ "Preserve unsaved document changes between sessions." ) },
+ &mApp->getConfig().workspace.sessionSnapshot );
+ addBool( panel,
+ { "restoreLastSession", "general.workspace",
+ mApp->i18n( "restore_last_session", "Restore Last Session" ),
+ mApp->i18n( "restore_last_session_desc",
+ "Reopen the previous workspace and documents at startup." ) },
+ &mApp->getConfig().workspace.restoreLastSession );
+ addBool( panel,
+ { "checkForUpdatesAtStartup", "general.workspace",
+ mApp->i18n( "check_updates_at_startup", "Check for Updates at Startup" ),
+ mApp->i18n( "check_updates_at_startup_desc",
+ "Check whether a newer ecode release is available after startup." ) },
+ &mApp->getConfig().workspace.checkForUpdatesAtStartup );
+
+ addCategory( panel, "appearance.theme", mApp->i18n( "appearance", "Appearance" ),
+ mApp->i18n( "theme_and_language", "Theme & Language" ) );
+ addChoice(
+ panel,
+ { "uiColorScheme", "appearance.theme",
+ mApp->i18n( "ui_prefes_color_scheme", "UI Prefers Color Scheme" ),
+ mApp->i18n(
+ "ui_prefers_color_scheme_desc",
+ "Choose whether the interface follows the system, light, or dark appearance." ) },
+ { mApp->i18n( "system", "System" ), mApp->i18n( "light", "Light" ),
+ mApp->i18n( "dark", "Dark" ) },
+ [this] {
+ switch ( mApp->getUIColorScheme() ) {
+ case ColorSchemeExtPreference::System:
+ return size_t{ 0 };
+ case ColorSchemeExtPreference::Light:
+ return size_t{ 1 };
+ default:
+ return size_t{ 2 };
+ }
+ },
+ [this]( size_t selected ) {
+ static constexpr ColorSchemeExtPreference values[] = { ColorSchemeExtPreference::System,
+ ColorSchemeExtPreference::Light,
+ ColorSchemeExtPreference::Dark };
+ mApp->setUIColorSchemeFromUserInteraction( values[std::min( selected, size_t{ 2 } )] );
+ },
+ { mApp->i18n(
+ "prefers_color_scheme_system_tooltip",
+ "System options will try to pick the system-wide currently preferred color scheme." ),
+ mApp->i18n( "prefers_color_scheme_light_tooltip",
+ "Always use the light interface color scheme." ),
+ mApp->i18n( "prefers_color_scheme_dark_tooltip",
+ "Always use the dark interface color scheme." ) } );
+ std::vector themeNames{ mApp->i18n( "default_theme", "Default Theme" ),
+ mApp->i18n( "syntax_color_scheme", "Syntax Color Scheme" ) };
+ std::vector themeIds{ "default_theme", "syntax_color_scheme" };
+ for ( const auto& file :
+ FileSystem::filesInfoGetInPath( mApp->getThemesPath(), true, true, true ) ) {
+ if ( file.getExtension() != "css" )
+ continue;
+ auto name = FileSystem::fileRemoveExtension( file.getFileName() );
+ themeNames.emplace_back( name );
+ themeIds.emplace_back( std::move( name ) );
+ }
+ auto currentTheme =
+ mApp->getConfig().ui.theme.empty() ? "default_theme" : mApp->getConfig().ui.theme;
+ auto selectedTheme = std::find( themeIds.begin(), themeIds.end(), currentTheme );
+ const size_t selectedThemeIndex = selectedTheme == themeIds.end()
+ ? 0
+ : static_cast( selectedTheme - themeIds.begin() );
+ addChoice(
+ panel,
+ { "uiTheme", "appearance.theme", mApp->i18n( "ui_thene", "UI Theme" ),
+ mApp->i18n( "ui_theme_desc",
+ "Choose the stylesheet used by the application interface." ) },
+ themeNames, [selectedThemeIndex] { return selectedThemeIndex; },
+ [this, themeIds = std::move( themeIds )]( size_t selected ) {
+ const auto& id = themeIds[std::min( selected, themeIds.size() - 1 )];
+ mApp->getConfig().ui.theme = id == "default_theme" ? "" : id;
+ mApp->setTheme(
+ id == "default_theme"
+ ? mApp->getDefaultThemePath()
+ : ( id == "syntax_color_scheme" ? id : mApp->getThemesPath() + id + ".css" ) );
+ } );
+ std::map languages;
+ for ( const auto& file :
+ FileSystem::filesInfoGetInPath( mApp->geti18nPath(), false, true, false, true ) ) {
+ if ( file.getExtension() != "xml" )
+ continue;
+ auto id = FileSystem::fileRemoveExtension( file.getFileName() );
+ std::string data;
+ FileSystem::fileGet( file.getFilepath(), data );
+ LuaPattern pattern( "title=\"(.-)\"" );
+ PatternMatcher::Range matches[2];
+ if ( pattern.matches( data, matches ) )
+ languages[data.substr( matches[1].start, matches[1].end - matches[1].start )] = id;
+ else
+ languages[id] = id;
+ }
+ std::vector languageNames;
+ std::vector languageIds;
+ for ( const auto& [name, id] : languages ) {
+ languageNames.emplace_back( name );
+ languageIds.emplace_back( id );
+ }
+ auto currentLanguage =
+ mApp->getConfig().ui.language.empty() ? "en" : mApp->getConfig().ui.language;
+ auto selectedLanguage = std::find( languageIds.begin(), languageIds.end(), currentLanguage );
+ const size_t selectedLanguageIndex =
+ selectedLanguage == languageIds.end()
+ ? 0
+ : static_cast( selectedLanguage - languageIds.begin() );
+ if ( !languageIds.empty() )
+ addChoice(
+ panel,
+ { "uiLanguage", "appearance.theme", mApp->i18n( "ui_language", "UI Language" ),
+ mApp->i18n( "ui_language_desc",
+ "Choose the interface language. Restart required." ) },
+ languageNames, [selectedLanguageIndex] { return selectedLanguageIndex; },
+ [this, languageIds = std::move( languageIds )]( size_t selected ) {
+ mApp->getConfig().ui.language =
+ languageIds[std::min( selected, languageIds.size() - 1 )];
+ } );
+
+ addCategory( panel, "appearance.fonts", mApp->i18n( "appearance", "Appearance" ),
+ mApp->i18n( "fonts_and_scale", "Fonts & Scale" ) );
+ addAction(
+ panel,
+ { "uiFont", "appearance.fonts",
+ mApp->i18n( "ui_font_and_size_ellipsis", "UI Font & Size..." ),
+ mApp->i18n( "ui_font_desc", "Choose the proportional font used by the interface." ) },
+ mApp->i18n( "choose_font", "Choose Font..." ),
+ [this] { mApp->runCommand( "sans-serif-font" ); } );
+ addAction(
+ panel,
+ { "editorFont", "appearance.fonts",
+ mApp->i18n( "editor_font_and_size_ellipsis", "Editor Font & Size..." ),
+ mApp->i18n( "editor_font_desc", "Choose the monospace font used by code editors." ) },
+ mApp->i18n( "choose_font", "Choose Font..." ),
+ [this] { mApp->runCommand( "editor-font" ); } );
+ addAction(
+ panel,
+ { "terminalFont", "appearance.fonts",
+ mApp->i18n( "terminal_font_and_size_ellipsis", "Terminal Font & Size..." ),
+ mApp->i18n( "terminal_font_desc", "Choose the monospace font used by terminals." ) },
+ mApp->i18n( "choose_font", "Choose Font..." ),
+ [this] { mApp->runCommand( "terminal-font" ); } );
+ addAction( panel,
+ { "fallbackFont", "appearance.fonts",
+ mApp->i18n( "fallback_font_ellipsis", "Fallback Font..." ),
+ 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,
+ { "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,
+ { "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(); } );
+ addBool( panel,
+ { "editorFontInInputFields", "appearance.fonts",
+ mApp->i18n( "editor_font_in_input_fields", "Editor Font in Input Fields" ),
+ mApp->i18n( "editor_font_in_input_fields_desc",
+ "Use the editor font for text input controls." ) },
+ &mApp->getConfig().ui.editorFontInInputFields );
+ addChoice(
+ panel,
+ { "fontHinting", "appearance.fonts", mApp->i18n( "ui_font_hint", "Font Hinting" ),
+ mApp->i18n( "ui_font_hint_desc", "Control glyph alignment to the pixel grid." ) },
+ { mApp->i18n( "none", "None" ), mApp->i18n( "slight", "Slight" ),
+ mApp->i18n( "full", "Full" ) },
+ [this] { return static_cast( mApp->getConfig().ui.fontHinting ); },
+ [this]( size_t selected ) {
+ static constexpr FontHinting values[] = { FontHinting::None, FontHinting::Slight,
+ FontHinting::Full };
+ auto value = values[std::min( selected, size_t{ 2 } )];
+ mApp->getConfig().ui.fontHinting = value;
+ defaultResourceScope().getFontService().setHinting( value );
+ mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, []( UIWidget* widget ) {
+ widget->asType()->syncFontRenderingConfig();
+ } );
+ } );
+ addChoice(
+ panel,
+ { "fontAntialiasing", "appearance.fonts",
+ mApp->i18n( "ui_font_antialiasing", "Font Anti-Aliasing" ),
+ mApp->i18n( "ui_font_antialiasing_desc", "Choose how glyph edges are smoothed." ) },
+ { mApp->i18n( "none", "None" ), mApp->i18n( "grayscale", "Grayscale" ),
+ mApp->i18n( "subpixel", "Subpixel" ) },
+ [this] { return static_cast( mApp->getConfig().ui.fontAntialiasing ); },
+ [this]( size_t selected ) {
+ static constexpr FontAntialiasing values[] = {
+ FontAntialiasing::None, FontAntialiasing::Grayscale, FontAntialiasing::Subpixel };
+ auto value = values[std::min( selected, size_t{ 2 } )];
+ mApp->getConfig().ui.fontAntialiasing = value;
+ defaultResourceScope().getFontService().setAntialiasing( value );
+ mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, []( UIWidget* widget ) {
+ widget->asType()->syncFontRenderingConfig();
+ } );
+ } );
+ auto addFontFeature = [this, &panel]( std::string id, const char* nameKey, const char* name,
+ const char* descriptionKey, const char* description,
+ Uint32 feature, bool editorFeature ) {
+ addBool(
+ panel,
+ { std::move( id ), "appearance.fonts", mApp->i18n( nameKey, name ),
+ mApp->i18n( descriptionKey, description ) },
+ [this, feature, editorFeature] {
+ const Uint32 features = editorFeature ? mApp->getConfig().editor.fontFeatures
+ : mApp->getConfig().ui.fontFeatures;
+ return ( features & feature ) != 0;
+ },
+ [this, feature, editorFeature]( bool enabled ) {
+ Uint32& features = editorFeature ? mApp->getConfig().editor.fontFeatures
+ : mApp->getConfig().ui.fontFeatures;
+ if ( enabled )
+ features |= feature;
+ else
+ features &= ~feature;
+ if ( editorFeature ) {
+ mApp->getSplitter()->forEachEditor( [features]( UICodeEditor* editor ) {
+ editor->setLigatureFeatures( features );
+ } );
+ } else {
+ mApp->getUISceneNode()->setDefaultTextHints( features );
+ }
+ } );
+ };
+ for ( bool editorFeature : { false, true } ) {
+ const std::string prefix = editorFeature ? "editor" : "ui";
+ 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 );
+ addFontFeature( prefix + "ContextualAlternates", "contextual_alternates",
+ "Contextual Alternates (calt)", "contextual_alternates_desc",
+ "Context-dependent alternatives, including many programming ligatures.",
+ TextHints::ContextualAlternates, editorFeature );
+ addFontFeature( prefix + "ContextualLigatures", "contextual_ligatures",
+ "Contextual Ligatures (clig)", "contextual_ligatures_desc",
+ "Ligatures applied in specific contexts to improve readability.",
+ TextHints::ContextualLigatures, editorFeature );
+ addFontFeature( prefix + "DiscretionaryLigatures", "discretionary_ligatures",
+ "Discretionary Ligatures (dlig)", "discretionary_ligatures_desc",
+ "Optional decorative or stylistic ligatures provided by the font.",
+ TextHints::DiscretionaryLigatures, editorFeature );
+ }
+
+ 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(); } );
+ addChoice(
+ panel,
+ { "indentTabAlignment", "editor.advanced",
+ mApp->i18n( "indent_tab_alignment", "Indent Tab Alignment" ),
+ mApp->i18n( "indent_tab_alignment_desc",
+ "Align text within the visual width of indentation tabs." ) },
+ { mApp->i18n( "left", "Left" ), mApp->i18n( "center", "Center" ),
+ mApp->i18n( "right", "Right" ) },
+ [this] {
+ return mApp->getConfig().editor.tabIndentAlignment == CharacterAlignment::Left
+ ? size_t{ 0 }
+ : ( mApp->getConfig().editor.tabIndentAlignment == CharacterAlignment::Center
+ ? size_t{ 1 }
+ : size_t{ 2 } );
+ },
+ [this]( size_t selected ) {
+ static constexpr CharacterAlignment values[] = {
+ CharacterAlignment::Left, CharacterAlignment::Center, CharacterAlignment::Right };
+ auto value = values[std::min( selected, size_t{ 2 } )];
+ mApp->getConfig().editor.tabIndentAlignment = value;
+ 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(); } );
+
+ addCategory( panel, "window.screenshots", mApp->i18n( "window", "Window" ),
+ mApp->i18n( "screenshots", "Screenshots" ) );
+ addAction( panel,
+ { "screenshotSavePath", "window.screenshots",
+ mApp->i18n( "set_screenshot_save_path", "Screenshot Save Path" ),
+ 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(
+ 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(); } );
+
+ addCategory( panel, "window.renderer", mApp->i18n( "window", "Window" ),
+ mApp->i18n( "renderer", "Renderer" ) );
+ addBool(
+ panel,
+ { "vsync", "window.renderer", mApp->i18n( "vsync", "VSync" ),
+ mApp->i18n( "vsync_desc",
+ "Synchronize rendering with the display refresh rate. Restart required." ) },
+ &mApp->getConfig().context.VSync, [this]( bool ) {
+ mApp->saveConfig();
+ mApp->getNotificationCenter()->addNotification(
+ mApp->i18n( "vsync_changed",
+ "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" ) );
+ } );
+ std::vector rendererVersions =
+ Renderer::getAvailableGraphicsLibraryVersions();
+ std::vector rendererVersionNames;
+ rendererVersionNames.reserve( rendererVersions.size() );
+ for ( const auto version : rendererVersions )
+ rendererVersionNames.emplace_back( Renderer::graphicsLibraryVersionToString( version ) );
+ auto selectedRendererVersion = std::find( rendererVersions.begin(), rendererVersions.end(),
+ mApp->getConfig().context.Version );
+ const size_t selectedRendererVersionIndex =
+ selectedRendererVersion == rendererVersions.end()
+ ? 0
+ : static_cast( selectedRendererVersion - rendererVersions.begin() );
+ if ( !rendererVersions.empty() )
+ addChoice(
+ panel,
+ { "rendererVersion", "window.renderer",
+ mApp->i18n( "ui_renderer_version", "Renderer Version" ),
+ mApp->i18n( "ui_renderer_version_desc",
+ "Select the graphics API version used by ecode. Restart required." ) },
+ rendererVersionNames,
+ [selectedRendererVersionIndex] { return selectedRendererVersionIndex; },
+ [this, rendererVersions = std::move( rendererVersions )]( size_t selected ) {
+ mApp->getConfig().context.Version =
+ rendererVersions[std::min( selected, rendererVersions.size() - 1 )];
+ mApp->saveConfig();
+ mApp->getNotificationCenter()->addNotification(
+ mApp->i18n( "glversion_changed",
+ "Renderer version changed.\nRestart ecode to see the changes." )
+ .unescape() );
+ } );
+ addChoice(
+ panel,
+ { "multisamples", "window.renderer",
+ mApp->i18n( "ui_multisamples_level", "Multisample Anti-Aliasing Level" ),
+ mApp->i18n( "ui_multisamples_level_desc",
+ "Set renderer multisampling. Restart required." ) },
+ { "0", "2", "4", "8", "16" },
+ [this] {
+ static constexpr Uint32 values[] = { 0, 2, 4, 8, 16 };
+ auto found = std::find( std::begin( values ), std::end( values ),
+ mApp->getConfig().context.Multisamples );
+ return found == std::end( values ) ? size_t{ 0 }
+ : size_t( found - std::begin( values ) );
+ },
+ [this]( size_t selected ) {
+ static constexpr Uint32 values[] = { 0, 2, 4, 8, 16 };
+ mApp->getConfig().context.Multisamples = values[std::min( selected, size_t{ 4 } )];
+ mApp->saveConfig();
+ mApp->getNotificationCenter()->addNotification(
+ mApp->i18n( "multisamples_changed", "Multisample Anti-Aliasing Level "
+ "applied.\nRestart ecode to see the changes." )
+ .unescape() );
+ },
+ { mApp->i18n( "multisamples_disabled_desc", "Disable multisample anti-aliasing." ),
+ mApp->i18n( "multisamples_2x_desc", "Use 2x multisample anti-aliasing." ),
+ mApp->i18n( "multisamples_4x_desc", "Use 4x multisample anti-aliasing." ),
+ mApp->i18n( "multisamples_8x_desc", "Use 8x multisample anti-aliasing." ),
+ mApp->i18n( "multisamples_16x_desc", "Use 16x multisample anti-aliasing." ) } );
+
+ addCategory( panel, "terminal.behavior", mApp->i18n( "terminal", "Terminal" ),
+ mApp->i18n( "behavior", "Behavior" ) );
+ addChoice(
+ panel,
+ { "newTerminalOrientation", "terminal.behavior",
+ mApp->i18n( "new_terminal_behavior", "New Terminal Behavior" ),
+ mApp->i18n( "new_terminal_behavior_desc",
+ "Choose where newly created terminal sessions are opened." ) },
+ { mApp->i18n( "open_in_same_tabbar", "Open in Current Tab Bar" ),
+ mApp->i18n( "open_in_vertical_split", "Open in New Vertical Split" ),
+ mApp->i18n( "open_in_horizontal_split", "Open in New Horizontal Split" ),
+ mApp->i18n( "open_in_statusbar_panel", "Open in Status Bar Panel" ) },
+ [this] {
+ switch ( mApp->getConfig().term.newTerminalOrientation ) {
+ case NewTerminalOrientation::Vertical:
+ return size_t{ 1 };
+ case NewTerminalOrientation::Horizontal:
+ return size_t{ 2 };
+ case NewTerminalOrientation::StatusBarPanel:
+ return size_t{ 3 };
+ default:
+ return size_t{ 0 };
+ }
+ },
+ [this]( size_t selected ) {
+ static constexpr NewTerminalOrientation::Orientation orientations[] = {
+ NewTerminalOrientation::Same, NewTerminalOrientation::Vertical,
+ NewTerminalOrientation::Horizontal, NewTerminalOrientation::StatusBarPanel };
+ mApp->getConfig().term.newTerminalOrientation =
+ orientations[std::min( selected, size_t{ 3 } )];
+ } );
+ addBool( panel,
+ { "terminalExclusiveMode", "terminal.behavior",
+ mApp->i18n( "enable_exclusive_mode_by_default", "Enable Exclusive Mode by Default" ),
+ mApp->i18n( "enable_exclusive_mode_by_default_tooltip",
+ "Disable global keybindings in newly created terminals." ) },
+ &mApp->getConfig().term.exclusiveMode );
+ addBool( panel,
+ { "closeTerminalTabOnExit", "terminal.behavior",
+ mApp->i18n( "close_terminal_tab_on_exit", "Close Terminal Tab on Exit" ),
+ mApp->i18n( "close_terminal_tab_on_exit_tooltip",
+ "Close a terminal tab when its main process exits." ) },
+ &mApp->getConfig().term.closeTerminalTabOnExit, [this]( bool value ) {
+ mApp->getSplitter()->forEachWidgetType(
+ UI_TYPE_TERMINAL, [value]( UIWidget* widget ) {
+ widget->asType()->getTerm()->setKeepAlive( !value );
+ } );
+ } );
+ addBool( panel,
+ { "warnBeforeClosingTerminal", "terminal.behavior",
+ mApp->i18n( "warn_before_closing_tab", "Warn Before Closing Tab" ),
+ mApp->i18n( "warn_before_closing_tab_tooltip",
+ "Ask before closing a terminal while a program is running." ) },
+ &mApp->getConfig().term.warnBeforeClosingTab );
+ addAction( panel,
+ { "terminalShell", "terminal.behavior",
+ mApp->i18n( "configure_terminal_shell", "Configure Terminal Shell" ),
+ mApp->i18n( "configure_terminal_shell_desc",
+ "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" ); } );
+ addAction( panel,
+ { "terminalWorkingDirectory", "terminal.behavior",
+ mApp->i18n( "configure_terminal_working_dir",
+ "Configure Terminal Default Working Directory" ),
+ mApp->i18n( "configure_terminal_working_dir_desc",
+ "Choose the working directory used by new terminals." ) },
+ mApp->i18n( "configure", "Configure..." ),
+ [this] { mApp->runCommand( "configure-terminal-working-dir" ); } );
+
+ addCategory( panel, "terminal.appearance", mApp->i18n( "terminal", "Terminal" ),
+ mApp->i18n( "appearance", "Appearance" ) );
+ std::vector terminalSchemeNames;
+ std::vector terminalSchemeIds;
+ for ( const auto& [name, scheme] : mApp->getTerminalManager()->getTerminalColorSchemes() ) {
+ terminalSchemeNames.emplace_back( name );
+ terminalSchemeIds.emplace_back( name );
+ }
+ auto selectedTerminalScheme =
+ std::find( terminalSchemeNames.begin(), terminalSchemeNames.end(),
+ String( mApp->getTerminalManager()->getTerminalCurrentColorScheme() ) );
+ const size_t selectedTerminalSchemeIndex =
+ selectedTerminalScheme == terminalSchemeNames.end()
+ ? 0
+ : static_cast( selectedTerminalScheme - terminalSchemeNames.begin() );
+ if ( !terminalSchemeNames.empty() )
+ addChoice(
+ panel,
+ { "terminalColorScheme", "terminal.appearance",
+ mApp->i18n( "terminal_color_scheme", "Terminal Color Scheme" ),
+ mApp->i18n( "terminal_color_scheme_desc",
+ "Choose the colors used by integrated terminals." ) },
+ terminalSchemeNames,
+ [selectedTerminalSchemeIndex] { return selectedTerminalSchemeIndex; },
+ [this, terminalSchemeIds = std::move( terminalSchemeIds )]( size_t selected ) {
+ mApp->getTerminalManager()->setTerminalColorScheme(
+ terminalSchemeIds[std::min( selected, terminalSchemeIds.size() - 1 )] );
+ } );
+ addChoice(
+ panel,
+ { "terminalScrollbarType", "terminal.appearance",
+ mApp->i18n( "scrollbar_type", "Scrollbar Type" ),
+ mApp->i18n( "scrollbar_type_desc",
+ "Place the terminal scrollbar over or outside its content." ) },
+ { mApp->i18n( "overlay", "Overlay" ), mApp->i18n( "outside", "Outside" ) },
+ [this] { return mApp->getConfig().term.scrollBarType == ScrollViewType::Overlay ? 0 : 1; },
+ [this]( size_t selected ) {
+ auto value = selected == 0 ? ScrollViewType::Overlay : ScrollViewType::Outside;
+ mApp->getConfig().term.scrollBarType = value;
+ mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, [value]( UIWidget* widget ) {
+ widget->asType()->setScrollViewType( value );
+ } );
+ },
+ { mApp->i18n( "scroll_overlay_tooltip", "Scrollbar appears over content." ),
+ mApp->i18n( "scroll_outside_tooltip",
+ "Scrollbar has its own space, never covers content." ) } );
+ addChoice(
+ panel,
+ { "terminalCursorStyle", "terminal.appearance",
+ mApp->i18n( "cursor_style", "Cursor Style" ),
+ mApp->i18n( "cursor_style_desc", "Choose the terminal cursor shape and animation." ) },
+ { mApp->i18n( "blinking_block", "Blinking Block" ),
+ mApp->i18n( "steady_block", "Steady Block" ),
+ mApp->i18n( "blink_underline", "Blink Underline" ),
+ mApp->i18n( "steady_underline", "Steady Underline" ),
+ mApp->i18n( "blink_bar", "Blink Bar" ), mApp->i18n( "steady_bar", "Steady Bar" ) },
+ [this] {
+ static constexpr TerminalCursorMode modes[] = {
+ TerminalCursorMode::BlinkingBlock, TerminalCursorMode::SteadyBlock,
+ TerminalCursorMode::BlinkUnderline, TerminalCursorMode::SteadyUnderline,
+ TerminalCursorMode::BlinkBar, TerminalCursorMode::SteadyBar };
+ auto found = std::find( std::begin( modes ), std::end( modes ),
+ mApp->getConfig().term.cursorStyle );
+ return found == std::end( modes ) ? size_t{ 0 } : size_t( found - std::begin( modes ) );
+ },
+ [this]( size_t selected ) {
+ static constexpr TerminalCursorMode modes[] = {
+ TerminalCursorMode::BlinkingBlock, TerminalCursorMode::SteadyBlock,
+ TerminalCursorMode::BlinkUnderline, TerminalCursorMode::SteadyUnderline,
+ TerminalCursorMode::BlinkBar, TerminalCursorMode::SteadyBar };
+ auto value = modes[std::min( selected, size_t{ 5 } )];
+ mApp->getConfig().term.cursorStyle = value;
+ mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, [value]( UIWidget* widget ) {
+ widget->asType()->getTerm()->setCursorMode( value );
+ } );
+ } );
+ addChoice(
+ panel,
+ { "terminalScrollbarMode", "terminal.appearance",
+ mApp->i18n( "scrollbar_mode", "Scrollbar Mode" ),
+ mApp->i18n( "scrollbar_mode_desc", "Control when the terminal scrollbar is visible." ) },
+ { mApp->i18n( "auto", "Auto" ), mApp->i18n( "always_visible", "Always Visible" ),
+ mApp->i18n( "always_hidden", "Always Hidden" ) },
+ [this] {
+ return mApp->getConfig().term.scrollBarMode == ScrollBarMode::Auto
+ ? 0
+ : ( mApp->getConfig().term.scrollBarMode == ScrollBarMode::AlwaysOn ? 1
+ : 2 );
+ },
+ [this]( size_t selected ) {
+ auto value = selected == 0 ? ScrollBarMode::Auto
+ : ( selected == 1 ? ScrollBarMode::AlwaysOn
+ : ScrollBarMode::AlwaysOff );
+ mApp->getConfig().term.scrollBarMode = value;
+ mApp->getSplitter()->forEachWidgetType( UI_TYPE_TERMINAL, [value]( UIWidget* widget ) {
+ widget->asType()->setVerticalScrollMode( value );
+ } );
+ },
+ { mApp->i18n( "scrollbar_mode_auto_tooltip",
+ "With an overlay scrollbar, show it when the mouse moves over the terminal. "
+ "With an outside scrollbar, show it when there is a scrollable area." ),
+ mApp->i18n( "scrollbar_mode_always_visible_tooltip",
+ "Keep the terminal scrollbar visible." ),
+ mApp->i18n( "scrollbar_mode_always_hidden_tooltip",
+ "Keep the terminal scrollbar hidden." ) } );
+}
+
+void SettingsPanel::addProjectSettings( PanelState& panel ) {
+ addCategory( panel, "editor.document", mApp->i18n( "editor", "Editor" ),
+ mApp->i18n( "document", "Document" ) );
+ addBool( panel,
+ { "useGlobalSettings", "editor.document",
+ mApp->i18n( "use_global_settings", "Use Global Settings" ),
+ mApp->i18n( "use_global_settings_desc",
+ "Inherit document defaults from the user settings." ) },
+ &mApp->getProjectConfig().useGlobalSettings );
+ auto addProjectBool = [this, &panel]( std::string id, const char* nameKey, const char* name,
+ const char* descriptionKey, const char* description,
+ bool DocumentConfig::* member ) {
+ addBool( panel,
+ { std::move( id ), "editor.document", mApp->i18n( nameKey, name ),
+ mApp->i18n( descriptionKey, description ) },
+ &( mApp->getProjectConfig().doc.*member ) );
+ };
+ addProjectBool( "trimTrailingWhitespaces", "trim_trailing_whitespaces",
+ "Trim Trailing Whitespaces", "trim_trailing_whitespaces_desc",
+ "Remove trailing whitespace when saving files.",
+ &DocumentConfig::trimTrailingWhitespaces );
+ addProjectBool( "forceNewLineAtEndOfFile", "force_new_line_at_end_of_file",
+ "Force New Line at End of File", "force_new_line_at_end_of_file_desc",
+ "Ensure saved files end with a newline.",
+ &DocumentConfig::forceNewLineAtEndOfFile );
+ addProjectBool( "autoDetectIndentType", "auto_detect_indent_type_and_width",
+ "Auto Detect Indent Type & Width", "auto_detect_indent_type_desc",
+ "Detect indentation settings when opening each document.",
+ &DocumentConfig::autoDetectIndentType );
+ addChoice(
+ panel,
+ { "autoIndent", "editor.document", mApp->i18n( "auto_indent", "Auto-Indent" ),
+ mApp->i18n( "auto_indent_tooltip", "Control indentation added after pressing Enter." ) },
+ { mApp->i18n( "auto_indent_none", "None" ),
+ mApp->i18n( "auto_indent_preserve", "Preserve" ),
+ mApp->i18n( "auto_indent_smart", "Smart" ) },
+ [this] { return static_cast( mApp->getProjectConfig().doc.autoIndent ); },
+ [this]( size_t selected ) {
+ mApp->getProjectConfig().doc.autoIndent =
+ static_cast( std::min( selected, size_t{ 2 } ) );
+ },
+ { mApp->i18n( "auto_indent_none_desc", "No automatic indentation." ),
+ mApp->i18n( "auto_indent_preserve_desc",
+ "Preserve the indentation level of the previous line." ),
+ mApp->i18n( "auto_indent_smart_desc",
+ "Preserve indentation and indent between auto-closed brackets." ) } );
+ addProjectBool( "indentSpaces", "indent_spaces", "Indent Using Spaces", "indent_spaces_desc",
+ "Insert spaces instead of tab characters for indentation.",
+ &DocumentConfig::indentSpaces );
+ 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,
+ { "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 );
+ addChoice(
+ panel,
+ { "lineEndings", "editor.document", mApp->i18n( "line_endings", "Line Endings" ),
+ mApp->i18n( "line_endings_desc", "Default line-ending sequence for new files." ) },
+ { "Windows (CR/LF)", "Unix (LF)", "Macintosh (CR)" },
+ [this] {
+ switch ( mApp->getProjectConfig().doc.lineEndings ) {
+ case TextFormat::LineEnding::CRLF:
+ return size_t{ 0 };
+ case TextFormat::LineEnding::CR:
+ return size_t{ 2 };
+ default:
+ return size_t{ 1 };
+ }
+ },
+ [this]( size_t selected ) {
+ mApp->getProjectConfig().doc.lineEndings =
+ selected == 0
+ ? TextFormat::LineEnding::CRLF
+ : ( selected == 2 ? TextFormat::LineEnding::CR : TextFormat::LineEnding::LF );
+ } );
+
+ addCategory( panel, "languages.file_associations", mApp->i18n( "languages", "Languages" ),
+ mApp->i18n( "file_associations", "File Associations" ) );
+ addChoice(
+ panel,
+ { "hExtLanguageType", "languages.file_associations",
+ mApp->i18n( "treat_h_files_as", "Treat .h Files As" ),
+ mApp->i18n( "treat_h_files_as_desc",
+ "Choose the default language for ambiguous .h header files." ) },
+ { mApp->i18n( "auto-detect", "Auto-Detect" ), "C", "C++", "Objective-C", "Objective-C++" },
+ [this] {
+ switch ( mApp->getProjectConfig().hExtLanguageType ) {
+ case HExtLanguageType::C:
+ return size_t{ 1 };
+ case HExtLanguageType::CPP:
+ return size_t{ 2 };
+ case HExtLanguageType::ObjectiveC:
+ return size_t{ 3 };
+ case HExtLanguageType::ObjectiveCPP:
+ return size_t{ 4 };
+ default:
+ return size_t{ 0 };
+ }
+ },
+ [this]( size_t selected ) {
+ static constexpr HExtLanguageType values[] = {
+ HExtLanguageType::AutoDetect, HExtLanguageType::C, HExtLanguageType::CPP,
+ HExtLanguageType::ObjectiveC, HExtLanguageType::ObjectiveCPP };
+ auto value = values[std::min( selected, size_t{ 4 } )];
+ mApp->getProjectConfig().hExtLanguageType = value;
+ mApp->getSplitter()->forEachEditor( [this, value]( UICodeEditor* editor ) {
+ auto& document = editor->getDocument();
+ document.setHExtLanguageType( value );
+ if ( document.getFileInfo().getExtension() == "h" ) {
+ editor->resetSyntaxDefinition();
+ }
+ } );
+ } );
+}
+
+void SettingsPanel::filter( PanelState& panel ) {
+ String query = panel.search ? panel.search->getText() : String{};
+ query.trim().toLower();
+ if ( !query.empty() ) {
+ panel.pageTitle->setText( mApp->i18n( "search_results", "Search Results" ) );
+ } else if ( auto title = panel.categoryTitles.find( panel.selectedCategory );
+ title != panel.categoryTitles.end() ) {
+ panel.pageTitle->setText( title->second );
+ }
+ UnorderedSet matchingCategories;
+ const std::string queryUtf8 = query.toUtf8();
+ const bool aggregate = String::endsWith( panel.selectedCategory, ".*" );
+ const std::string aggregatePrefix =
+ aggregate ? panel.selectedCategory.substr( 0, panel.selectedCategory.size() - 1 )
+ : std::string{};
+ if ( !query.empty() ) {
+ for ( const auto& binding : panel.bindings ) {
+ if ( String::icontains( binding.name, query ) ||
+ String::icontains( binding.description, query ) ||
+ String::icontains( binding.id, queryUtf8 ) )
+ matchingCategories.insert( binding.category );
+ }
+ }
+ if ( queryUtf8 != panel.categoryFilter ) {
+ panel.categoryFilter = queryUtf8;
+ if ( auto model = std::static_pointer_cast( panel.categoryModel ) )
+ model->filter( queryUtf8, matchingCategories );
+ }
+ panel.categories->expandAll();
+ for ( auto& [category, heading] : panel.categoryHeadings )
+ heading->setVisible( query.empty() && aggregate &&
+ String::startsWith( category, aggregatePrefix ) );
+ for ( auto& binding : panel.bindings ) {
+ const auto categoryName = panel.categorySearchText.find( binding.category );
+ const bool categoryMatches = categoryName != panel.categorySearchText.end() &&
+ String::icontains( categoryName->second, query );
+ const bool matches =
+ query.empty()
+ ? binding.category == panel.selectedCategory ||
+ ( aggregate && String::startsWith( binding.category, aggregatePrefix ) )
+ : categoryMatches || String::icontains( binding.name, query ) ||
+ String::icontains( binding.description, query ) ||
+ String::icontains( binding.id, queryUtf8 );
+ binding.row->setVisible( matches );
+ }
+}
+
+} // namespace ecode
diff --git a/src/tools/ecode/settingspanel.hpp b/src/tools/ecode/settingspanel.hpp
new file mode 100644
index 000000000..abfa37a59
--- /dev/null
+++ b/src/tools/ecode/settingspanel.hpp
@@ -0,0 +1,81 @@
+#ifndef ECODE_SETTINGSPANEL_HPP
+#define ECODE_SETTINGSPANEL_HPP
+
+#include
+#include
+#include
+#include
+
+namespace ecode {
+
+class App;
+
+class SettingsPanel {
+ public:
+ enum class Scope : Uint8 { User, Project };
+
+ explicit SettingsPanel( App* app );
+
+ void show( Scope scope );
+
+ protected:
+ struct SettingBinding {
+ std::string id;
+ std::string category;
+ String name;
+ String description;
+ UIWidget* row{ nullptr };
+ };
+
+ struct PanelState {
+ UIWindow* window{ nullptr };
+ EventConnectionList connections;
+ UITextInput* search{ nullptr };
+ UITreeView* categories{ nullptr };
+ UIScrollView* scroll{ nullptr };
+ UILinearLayout* settings{ nullptr };
+ UITextView* pageTitle{ nullptr };
+ std::shared_ptr categoryModel;
+ UIBindingGroup bindingGroup;
+ std::vector>> categoryItems;
+ UnorderedMap categoryIds;
+ UnorderedMap categorySearchText;
+ UnorderedMap categoryTitles;
+ UnorderedMap categoryHeadings;
+ std::vector bindings;
+ std::string selectedCategory;
+ std::string categoryFilter;
+
+ void reset();
+ };
+
+ App* mApp{ nullptr };
+ PanelState mUser;
+ PanelState mProject;
+
+ PanelState& state( Scope scope );
+ void create( Scope scope );
+ void addUserSettings( PanelState& state );
+ void addProjectSettings( PanelState& state );
+ void addCategory( PanelState& state, const std::string& id, const String& parent,
+ const String& name );
+ void setupCategories( PanelState& state );
+ void addBool( PanelState& state, SettingBinding binding, bool* value,
+ std::function apply = {} );
+ void addBool( PanelState& state, SettingBinding binding, std::function get,
+ std::function set );
+ void addChoice( PanelState& state, SettingBinding binding, const std::vector& choices,
+ std::function get, std::function set,
+ std::vector choiceDescriptions = {} );
+ void addInteger( PanelState& state, SettingBinding binding, int min, int max, int* value,
+ std::function apply = {} );
+ void addAction( PanelState& state, SettingBinding binding, const String& buttonText,
+ std::function action );
+ UIWidget* createRow( PanelState& state, SettingBinding& binding );
+ UICheckBox* createBoolControl( PanelState& state, SettingBinding& binding );
+ void filter( PanelState& state );
+};
+
+} // namespace ecode
+
+#endif