mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 03:00:23 +03:00
ecode: add structured settings pages for plugins
- introduce reusable settings models, documents, and plugin settings pages - expose all registered plugin configuration options in the Settings panel - support groups, choices, editable values, lists, secrets, and JSON objects - stage plugin changes and persist them when the Settings window closes - preserve raw JSON access for advanced configuration - safely migrate legacy AI provider API keys - add translated plugin setting labels and descriptions - remove the obsolete plugin GUI configuration API - fix autocomplete signature documentation persistence - fix AI model catalog refresh interval loading
This commit is contained in:
@@ -102,7 +102,7 @@ const char DOC_FIND_REPLACE_CSS[] = R"css(
|
||||
}
|
||||
.ce_find_replace_box .input-find.error,
|
||||
.ce_find_replace_box .input-replace.error {
|
||||
border-color: #ff4040;
|
||||
border-color: var(--theme-error);
|
||||
}
|
||||
)css";
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ StatusBar > #doc_info {
|
||||
}
|
||||
#search_find.error,
|
||||
#search_replace.error {
|
||||
border-color: #ff4040;
|
||||
border-color: var(--theme-error);
|
||||
}
|
||||
TableView#locate_bar_table > tableview::row > tableview::cell:nth-child(2),
|
||||
TableView#locate_bar_table > tableview::row > tableview::cell:nth-child(3) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "../../appconfig.hpp"
|
||||
#include "../../notificationcenter.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include "../../widgetcommandexecuter.hpp"
|
||||
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
@@ -13,6 +14,66 @@ using json = nlohmann::json;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void AIAssistantPlugin::registerSettings( SettingsPage& page ) {
|
||||
auto general = i18n( "general", "General" );
|
||||
page.addGroup( general );
|
||||
page.addBool( "display-reasoning", "/config/display_reasoning",
|
||||
i18n( "aiassistant_display_reasoning", "Display Reasoning" ),
|
||||
i18n( "aiassistant_display_reasoning_desc",
|
||||
"Display reasoning content returned by supported models." ),
|
||||
false, general );
|
||||
page.addBool( "model-catalog-enabled", "/config/model_catalog_enabled",
|
||||
i18n( "aiassistant_model_catalog_enabled", "Enable Model Catalog" ),
|
||||
i18n( "aiassistant_model_catalog_enabled_desc",
|
||||
"Download model metadata from the configured catalog." ),
|
||||
true, general );
|
||||
page.addText(
|
||||
"model-catalog-url", "/config/model_catalog_url",
|
||||
i18n( "aiassistant_model_catalog_url", "Model Catalog URL" ),
|
||||
i18n( "aiassistant_model_catalog_url_desc",
|
||||
"URL used to download model catalog metadata." ),
|
||||
"https://models.dev/api.json", []( const std::string& value ) { return !value.empty(); },
|
||||
false, general );
|
||||
page.addInteger(
|
||||
"model-catalog-refresh-hours", "/config/model_catalog_refresh_hours",
|
||||
i18n( "aiassistant_model_catalog_refresh_hours", "Model Catalog Refresh Interval" ),
|
||||
i18n( "aiassistant_model_catalog_refresh_hours_desc",
|
||||
"Hours between model catalog refreshes." ),
|
||||
1, 8760, 24, general );
|
||||
|
||||
auto apiKeys = i18n( "aiassistant_api_keys", "API Keys" );
|
||||
page.addGroup( apiKeys );
|
||||
struct Provider {
|
||||
const char* id;
|
||||
const char* name;
|
||||
const char* legacyKey;
|
||||
};
|
||||
static constexpr Provider providers[] = {
|
||||
{ "openai", "OpenAI", "openai_api_key" },
|
||||
{ "anthropic", "Anthropic", "anthropic_api_key" },
|
||||
{ "google", "Google AI", "google_ai_api_key" },
|
||||
{ "deepseek", "DeepSeek", "deepseek_api_key" },
|
||||
{ "mistral", "Mistral", "mistral_api_key" },
|
||||
{ "xai", "xAI", "xai_api_key" },
|
||||
{ "github", "GitHub Models", "github_api_key" },
|
||||
{ "perplexity", "Perplexity", "perplexity_api_key" },
|
||||
{ "openrouter", "OpenRouter", "openrouter_api_key" },
|
||||
{ "moonshotai", "Moonshot AI", "moonshot_api_key" },
|
||||
{ "nvidia", "NVIDIA", "nvidia_api_key" },
|
||||
{ "togetherai", "Together AI", "together_api_key" },
|
||||
{ "xiaomi", "Xiaomi MiMo", "mimo_api_key" },
|
||||
};
|
||||
for ( const auto& provider : providers ) {
|
||||
page.addSecret(
|
||||
std::string{ "api-key-" } + provider.id,
|
||||
std::string{ "/config/api_keys/" } + provider.id,
|
||||
i18n( std::string{ "aiassistant_api_key_" } + provider.id,
|
||||
String::fromUtf8( std::string_view{ provider.name } ) + " API Key" ),
|
||||
i18n( "aiassistant_api_key_desc", "Credential used to access this model provider." ),
|
||||
{}, apiKeys, std::string{ "/config/" } + provider.legacyKey );
|
||||
}
|
||||
}
|
||||
|
||||
static std::initializer_list<std::string> AIAssistantUnlockedCommandList = {
|
||||
|
||||
"new-ai-assistant"
|
||||
@@ -285,7 +346,7 @@ void AIAssistantPlugin::load( PluginManager* pluginManager ) {
|
||||
userConfig["model_catalog_url"].is_string() )
|
||||
catalogSettings.url = userConfig["model_catalog_url"].get<std::string>();
|
||||
if ( userConfig.contains( "model_catalog_refresh_hours" ) &&
|
||||
userConfig["model_catalog_refresh_hours"].is_number_unsigned() )
|
||||
userConfig["model_catalog_refresh_hours"].is_number_integer() )
|
||||
catalogSettings.refreshIntervalHours =
|
||||
userConfig["model_catalog_refresh_hours"].get<std::uint32_t>();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,12 @@ class AIAssistantPlugin : public PluginBase {
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
const LLMProviders& getProviders() { return mProviders; }
|
||||
|
||||
const ACPAgents& getAgents() { return mAgents; }
|
||||
|
||||
std::string getPluginStatePath() const;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "autocompleteplugin.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include "../../universallocator.hpp"
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/graphics/text.hpp>
|
||||
@@ -25,6 +26,55 @@ using namespace std::literals;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void AutoCompletePlugin::registerSettings( SettingsPage& page ) {
|
||||
page.addGroup( i18n( "general", "General" ) );
|
||||
page.addBool(
|
||||
"suggestions-syntax-highlight", "/config/suggestions_syntax_highlight",
|
||||
i18n( "autocomplete_suggestions_syntax_highlight", "Syntax Highlight Suggestions" ),
|
||||
i18n( "autocomplete_suggestions_syntax_highlight_desc",
|
||||
"Apply syntax highlighting to completion suggestions." ),
|
||||
true );
|
||||
page.addInteger( "max-label-characters", "/config/max_label_characters",
|
||||
i18n( "autocomplete_max_label_characters", "Maximum Label Characters" ),
|
||||
i18n( "autocomplete_max_label_characters_desc",
|
||||
"Maximum number of characters shown in a suggestion label." ),
|
||||
1, 10000, 100 );
|
||||
auto cssLength = []( const std::string& text ) { return StyleSheetLength::isLength( text ); };
|
||||
page.addText( "max-suggestion-documentation-width",
|
||||
"/config/max_suggestion_documentation_width",
|
||||
i18n( "autocomplete_max_suggestion_documentation_width",
|
||||
"Maximum Suggestion Documentation Width" ),
|
||||
i18n( "autocomplete_max_suggestion_documentation_width_desc",
|
||||
"Maximum documentation popup width as a CSS length." ),
|
||||
"100%", cssLength );
|
||||
page.addText( "max-signature-helper-width", "/config/max_signature_helper_width",
|
||||
i18n( "autocomplete_max_signature_helper_width", "Maximum Signature Help Width" ),
|
||||
i18n( "autocomplete_max_signature_helper_width_desc",
|
||||
"Maximum signature help popup width as a CSS length." ),
|
||||
"90%", cssLength );
|
||||
page.addBool( "signature-help-multi-line", "/config/signature_help_multi_line",
|
||||
i18n( "autocomplete_signature_help_multi_line", "Multiline Signature Help" ),
|
||||
i18n( "autocomplete_signature_help_multi_line_desc",
|
||||
"Allow signature help to use multiple lines." ),
|
||||
true );
|
||||
page.addBool( "suggestion-documentation", "/config/suggestion_documentation",
|
||||
i18n( "autocomplete_suggestion_documentation", "Suggestion Documentation" ),
|
||||
i18n( "autocomplete_suggestion_documentation_desc",
|
||||
"Show documentation alongside completion suggestions." ),
|
||||
true );
|
||||
page.addBool(
|
||||
"signature-help-documentation", "/config/signature_help_documentation",
|
||||
i18n( "autocomplete_signature_help_documentation", "Signature Help Documentation" ),
|
||||
i18n( "autocomplete_signature_help_documentation_desc",
|
||||
"Show documentation alongside signature help." ),
|
||||
true );
|
||||
page.addBool( "load-vscode-snippets", "/config/load_vscode_snippets",
|
||||
i18n( "autocomplete_load_vscode_snippets", "Load VS Code Snippets" ),
|
||||
i18n( "autocomplete_load_vscode_snippets_desc",
|
||||
"Load compatible snippets installed for VS Code." ),
|
||||
true );
|
||||
}
|
||||
|
||||
class SnippetLocatorModel : public Model {
|
||||
public:
|
||||
struct Row {
|
||||
@@ -380,7 +430,7 @@ void AutoCompletePlugin::load( PluginManager* pluginManager ) {
|
||||
if ( config.contains( "signature_help_documentation" ) )
|
||||
mSignatureHelpDocumentation = config.value( "signature_help_documentation", true );
|
||||
else {
|
||||
config["suggestion_documentation"] = mSignatureHelpDocumentation;
|
||||
config["signature_help_documentation"] = mSignatureHelpDocumentation;
|
||||
updateConfigFile = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,21 +95,38 @@ class AutoCompletePlugin : public Plugin {
|
||||
|
||||
bool isReady() const override { return true; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
void onRegister( UICodeEditor* ) override;
|
||||
|
||||
void onUnregister( UICodeEditor* ) override;
|
||||
|
||||
bool onKeyDown( UICodeEditor*, const KeyEvent& ) override;
|
||||
|
||||
bool onTextInput( UICodeEditor*, const TextInputEvent& ) override;
|
||||
|
||||
void update( UICodeEditor* ) override;
|
||||
|
||||
void postDraw( UICodeEditor*, const Vector2f& startScroll, const Float& lineHeight,
|
||||
const TextPosition& cursor ) override;
|
||||
|
||||
void drawBeforeLineText( UICodeEditor*, const Int64&, Vector2f, const Float&,
|
||||
const Float& ) override;
|
||||
|
||||
bool onMouseDown( UICodeEditor*, const Vector2i&, const Uint32& ) override;
|
||||
|
||||
bool onMouseUp( UICodeEditor*, const Vector2i&, const Uint32& ) override;
|
||||
|
||||
bool onMouseDoubleClick( UICodeEditor*, const Vector2i&, const Uint32& ) override;
|
||||
|
||||
bool onMouseMove( UICodeEditor*, const Vector2i&, const Uint32& ) override;
|
||||
|
||||
void onFileSystemEvent( const FileEvent&, const FileInfo& ) override;
|
||||
|
||||
FileSystemListenerOptions getFileSystemListenerOptions() const override;
|
||||
|
||||
void onLoadProject( const std::string& projectFolder,
|
||||
const std::string& projectStatePath ) override;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "debuggerplugin.hpp"
|
||||
#include "../../jsonhelper.hpp"
|
||||
#include "../../notificationcenter.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include "../../terminalmanager.hpp"
|
||||
#include "../../uistatusbar.hpp"
|
||||
#include "../../widgetcommandexecuter.hpp"
|
||||
@@ -30,6 +31,27 @@ static constexpr auto REQUEST_TYPE_ATTACH = "attach";
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void DebuggerPlugin::registerSettings( SettingsPage& page ) {
|
||||
page.addGroup( i18n( "general", "General" ) );
|
||||
page.addBool(
|
||||
"fetch-registers", "/config/fetch_registers",
|
||||
i18n( "debugger_fetch_registers", "Fetch Registers" ),
|
||||
i18n( "debugger_fetch_registers_desc", "Request processor registers while debugging." ),
|
||||
false );
|
||||
page.addBool(
|
||||
"fetch-globals", "/config/fetch_globals",
|
||||
i18n( "debugger_fetch_globals", "Fetch Global Variables" ),
|
||||
i18n( "debugger_fetch_globals_desc", "Request global variables while debugging." ), false );
|
||||
page.addBool( "silent", "/config/silent", i18n( "debugger_silent", "Silent Debugger Logs" ),
|
||||
i18n( "debugger_silent_desc", "Hide non-critical debugger log messages." ),
|
||||
true );
|
||||
page.addBool( "load-vscode-launch-config", "/config/load_vscode_launch_config",
|
||||
i18n( "debugger_load_vscode_launch_config", "Load VS Code Launch Configuration" ),
|
||||
i18n( "debugger_load_vscode_launch_config_desc",
|
||||
"Load compatible launch configurations from .vscode/launch.json." ),
|
||||
true );
|
||||
}
|
||||
|
||||
static constexpr auto INPUT_PATTERN = "%$%{input%:([%w_]+)%}"sv;
|
||||
static constexpr auto ENV_PATTERN = "%$%{env%:([%w_]+)%}"sv;
|
||||
static constexpr auto COMMAND_PATTERN = "%$%{command%:([%w_]+)%}"sv;
|
||||
|
||||
@@ -67,6 +67,10 @@ class DebuggerPlugin : public PluginBase {
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
void onSaveState( IniFile* state ) override;
|
||||
|
||||
void onSaveProject( const std::string& projectFolder, const std::string& projectStatePath,
|
||||
@@ -150,16 +154,27 @@ class DebuggerPlugin : public PluginBase {
|
||||
mDoc( doc ), mParent( parent ) {}
|
||||
|
||||
virtual void onDocumentTextChanged( const DocumentContentChange& ) {}
|
||||
|
||||
virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& ) {}
|
||||
|
||||
virtual void onDocumentCursorChange( const TextPosition& ) {}
|
||||
|
||||
virtual void onDocumentSelectionChange( const TextRange& ) {}
|
||||
|
||||
virtual void onDocumentLineCountChange( const size_t&, const size_t& ) {}
|
||||
|
||||
virtual void onDocumentLineChanged( const Int64& ) {}
|
||||
|
||||
virtual void onDocumentSaved( TextDocument* ) {}
|
||||
|
||||
virtual void onDocumentClosed( TextDocument* doc ) { onDocumentReset( doc ); }
|
||||
|
||||
virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {}
|
||||
|
||||
virtual void onDocumentMoved( TextDocument* ) {}
|
||||
|
||||
virtual void onDocumentReset( TextDocument* ) {}
|
||||
|
||||
Client::Type getTextDocumentClientType() { return TextDocument::Client::Auxiliary; }
|
||||
|
||||
virtual void onDocumentLineMove( const Int64& fromLine, const Int64& toLine,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "discordRPCplugin.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
@@ -6,6 +7,34 @@ namespace ecode {
|
||||
|
||||
static const auto DEFAULT_CLIENT_ID = "1339026777158455336";
|
||||
static const auto DebounceUniqueId = String::hash( "DiscordRPCplugin::debounce" );
|
||||
|
||||
void DiscordRPCplugin::registerSettings( SettingsPage& page ) {
|
||||
auto general = i18n( "general", "General" );
|
||||
page.addGroup( general );
|
||||
page.addText( "application-id", "/config/appID",
|
||||
i18n( "discordrpc_application_id", "Discord Application ID" ),
|
||||
i18n( "discordrpc_application_id_desc",
|
||||
"Discord application identifier used for Rich Presence." ),
|
||||
DEFAULT_CLIENT_ID, {}, false, general );
|
||||
page.addBool( "language-icons", "/config/doLanguageIcons",
|
||||
i18n( "discordrpc_language_icons", "Language Icons" ),
|
||||
i18n( "discordrpc_language_icons_desc",
|
||||
"Display an icon for the active document language." ),
|
||||
true, general );
|
||||
page.addBool( "git-integration", "/config/doGitIntegration",
|
||||
i18n( "discordrpc_git_integration", "Git Integration" ),
|
||||
i18n( "discordrpc_git_integration_desc",
|
||||
"Include repository information in Rich Presence." ),
|
||||
true, general );
|
||||
auto advanced = i18n( "advanced", "Advanced" );
|
||||
page.addGroup( advanced );
|
||||
page.addJsonObject( "icon-bindings", "/config/iconBindings",
|
||||
i18n( "discordrpc_icon_bindings", "Language Icon Bindings" ),
|
||||
i18n( "discordrpc_icon_bindings_desc",
|
||||
"JSON object mapping language identifiers to Discord image assets." ),
|
||||
nlohmann::json::object(), advanced );
|
||||
}
|
||||
|
||||
static const std::vector<std::string> DiscordRPCCommandList = { "discordrpc-reconnect" };
|
||||
|
||||
static const auto DISCORDRPC_DEFAULT_ICON =
|
||||
|
||||
@@ -43,6 +43,10 @@ class DiscordRPCplugin : public PluginBase {
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
protected:
|
||||
DiscordIPC mIPC;
|
||||
Mutex mDataMutex;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "formatterplugin.hpp"
|
||||
#include "../../notificationcenter.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/iostreamstring.hpp>
|
||||
#include <eepp/system/lock.hpp>
|
||||
@@ -18,6 +19,15 @@ using json = nlohmann::json;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void FormatterPlugin::registerSettings( SettingsPage& page ) {
|
||||
page.addGroup( i18n( "general", "General" ) );
|
||||
page.addBool( "auto-format-on-save", "/config/auto_format_on_save",
|
||||
i18n( "formatter_auto_format_on_save", "Format on Save" ),
|
||||
i18n( "formatter_auto_format_on_save_desc",
|
||||
"Automatically format supported documents when saving." ),
|
||||
false );
|
||||
}
|
||||
|
||||
Plugin* FormatterPlugin::New( PluginManager* pluginManager ) {
|
||||
return eeNew( FormatterPlugin, ( pluginManager, false ) );
|
||||
}
|
||||
|
||||
@@ -50,6 +50,10 @@ class FormatterPlugin : public Plugin {
|
||||
|
||||
std::string getDescription() { return Definition().description; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
virtual String::HashType getConfigFileHash() { return mConfigHash; }
|
||||
|
||||
void onRegister( UICodeEditor* );
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "gitplugin.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include "gitbranchmodel.hpp"
|
||||
#include "gitstatusmodel.hpp"
|
||||
#include <eepp/graphics/image.hpp>
|
||||
@@ -33,6 +34,45 @@ using json = nlohmann::json;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void GitPlugin::registerSettings( SettingsPage& page ) {
|
||||
page.addGroup( i18n( "general", "General" ) );
|
||||
page.addText( "ui-refresh-frequency", "/config/ui_refresh_frequency",
|
||||
i18n( "git_ui_refresh_frequency", "Refresh Frequency" ),
|
||||
i18n( "git_ui_refresh_frequency_desc",
|
||||
"How often Git information in the interface is refreshed." ),
|
||||
mRefreshFreq.toString(), []( const std::string& text ) {
|
||||
Time value;
|
||||
return SettingsPage::parseNonNegativeSettingsTime( text, value );
|
||||
} );
|
||||
page.addBool( "statusbar-display-branch", "/config/statusbar_display_branch",
|
||||
i18n( "git_statusbar_display_branch", "Show Branch in Status Bar" ),
|
||||
i18n( "git_statusbar_display_branch_desc",
|
||||
"Display the current Git branch in the status bar." ),
|
||||
true );
|
||||
page.addBool( "filetree-highlight-changes", "/config/filetree_highlight_changes",
|
||||
i18n( "git_filetree_highlight_changes", "Highlight File Tree Changes" ),
|
||||
i18n( "git_filetree_highlight_changes_desc",
|
||||
"Highlight files with Git changes in the file tree." ),
|
||||
true );
|
||||
page.addText( "filetree-highlight-style-color", "/config/filetree_highlight_style_color",
|
||||
i18n( "git_filetree_highlight_style_color", "File Tree Highlight Color" ),
|
||||
i18n( "git_filetree_highlight_style_color_desc",
|
||||
"CSS color used to highlight changed files." ),
|
||||
"var(--font-highlight)" );
|
||||
page.addBool( "statusbar-display-modifications", "/config/statusbar_display_modifications",
|
||||
i18n( "git_statusbar_display_modifications", "Show Modifications in Status Bar" ),
|
||||
i18n( "git_statusbar_display_modifications_desc",
|
||||
"Display repository modification counts in the status bar." ),
|
||||
true );
|
||||
page.addBool( "status-recurse-submodules", "/config/status_recurse_submodules",
|
||||
i18n( "git_status_recurse_submodules", "Recurse into Submodules" ),
|
||||
i18n( "git_status_recurse_submodules_desc",
|
||||
"Include submodule changes when collecting repository status." ),
|
||||
true );
|
||||
page.addBool( "silent", "/config/silent", i18n( "git_silent", "Silent Git Logs" ),
|
||||
i18n( "git_silent_desc", "Hide non-critical Git plugin log messages." ), true );
|
||||
}
|
||||
|
||||
static constexpr auto DEFAULT_HIGHLIGHT_COLOR = "var(--font-highlight)"sv;
|
||||
static constexpr auto GIT_STATUS_UPDATE_TAG = String::hash( "git::status-update" );
|
||||
|
||||
|
||||
@@ -52,6 +52,10 @@ class GitPlugin : public PluginBase {
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
void onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) override;
|
||||
|
||||
FileSystemListenerOptions getFileSystemListenerOptions() const override;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "linterplugin.hpp"
|
||||
#include "../../notificationcenter.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include <algorithm>
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/graphics/text.hpp>
|
||||
@@ -30,6 +31,41 @@ using json = nlohmann::json;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void LinterPlugin::registerSettings( SettingsPage& page ) {
|
||||
page.addGroup( i18n( "general", "General" ) );
|
||||
page.addText( "delay-time", "/config/delay_time", i18n( "linter_delay_time", "Lint Delay" ),
|
||||
i18n( "linter_delay_time_desc", "Time to wait before linting after an edit." ),
|
||||
getDelayTime().toString(), []( const std::string& text ) {
|
||||
Time value;
|
||||
return SettingsPage::parseNonNegativeSettingsTime( text, value );
|
||||
} );
|
||||
page.addBool( "enable-lsp-diagnostics", "/config/enable_lsp_diagnostics",
|
||||
i18n( "linter_enable_lsp_diagnostics", "Language Server Diagnostics" ),
|
||||
i18n( "linter_enable_lsp_diagnostics_desc",
|
||||
"Display diagnostics reported by language servers." ),
|
||||
true );
|
||||
page.addBool( "enable-error-lens", "/config/enable_error_lens",
|
||||
i18n( "linter_enable_error_lens", "Error Lens" ),
|
||||
i18n( "linter_enable_error_lens_desc",
|
||||
"Display diagnostic messages inline in the editor." ),
|
||||
true );
|
||||
page.addStringList(
|
||||
"disable-lsp-languages", "/config/disable_lsp_languages",
|
||||
i18n( "linter_disable_lsp_languages", "Disable LSP Diagnostic Languages" ),
|
||||
i18n( "linter_disable_lsp_languages_desc",
|
||||
"Comma-separated language identifiers where LSP diagnostics are disabled." ) );
|
||||
page.addStringList(
|
||||
"disable-languages", "/config/disable_languages",
|
||||
i18n( "linter_disable_languages", "Disable Linter Languages" ),
|
||||
i18n( "linter_disable_languages_desc",
|
||||
"Comma-separated language identifiers where linters are disabled." ) );
|
||||
page.addBool( "goto-ignore-warnings", "/config/goto_ignore_warnings",
|
||||
i18n( "linter_goto_ignore_warnings", "Ignore Warnings When Navigating" ),
|
||||
i18n( "linter_goto_ignore_warnings_desc",
|
||||
"Skip warnings when navigating between diagnostics." ),
|
||||
false );
|
||||
}
|
||||
|
||||
Plugin* LinterPlugin::New( PluginManager* pluginManager ) {
|
||||
return eeNew( LinterPlugin, ( pluginManager, false ) );
|
||||
}
|
||||
@@ -1145,9 +1181,8 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index,
|
||||
Color wcolor(
|
||||
editor->getColorScheme().getEditorSyntaxStyle( "warning"_sst ).color );
|
||||
if ( mLightbulbIcon == nullptr )
|
||||
mLightbulbIcon =
|
||||
editor->getUISceneNode()->getUIIconThemeManager()->findIcon(
|
||||
"lightbulb-autofix" );
|
||||
mLightbulbIcon = editor->getUISceneNode()->getUIIconThemeManager()->findIcon(
|
||||
"lightbulb-autofix" );
|
||||
if ( nullptr != mLightbulbIcon ) {
|
||||
const int iconSize = (int)eefloor( lineHeight );
|
||||
Drawable* drawable = mLightbulbIcon->getSource( iconSize ).get();
|
||||
|
||||
@@ -76,6 +76,10 @@ class LinterPlugin : public Plugin {
|
||||
|
||||
std::string getDescription() { return Definition().description; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
virtual String::HashType getConfigFileHash() { return mConfigHash; }
|
||||
|
||||
void onRegister( UICodeEditor* );
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "lspclientplugin.hpp"
|
||||
#include "../../notificationcenter.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include "../../version.hpp"
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
@@ -26,6 +27,52 @@ using json = nlohmann::json;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void LSPClientPlugin::registerSettings( SettingsPage& page ) {
|
||||
page.addGroup( i18n( "general", "General" ) );
|
||||
page.addText( "hover-delay", "/config/hover_delay", i18n( "lsp_hover_delay", "Hover Delay" ),
|
||||
i18n( "lsp_hover_delay_desc",
|
||||
"Time to wait before showing language server information while hovering." ),
|
||||
mHoverDelay.toString(), []( const std::string& text ) {
|
||||
Time value;
|
||||
return SettingsPage::parseNonNegativeSettingsTime( text, value );
|
||||
} );
|
||||
page.addText( "server-close-after-idle-time", "/config/server_close_after_idle_time",
|
||||
i18n( "lsp_server_close_after_idle_time", "Server Close After Idle Time" ),
|
||||
i18n( "lsp_server_close_after_idle_time_desc",
|
||||
"Time to keep an unused language server running before closing it." ),
|
||||
mClientManager.getLSPDecayTime().toString(), []( const std::string& text ) {
|
||||
Time value;
|
||||
return SettingsPage::parseNonNegativeSettingsTime( text, value );
|
||||
} );
|
||||
page.addBool( "silent", "/config/silent", i18n( "lsp_silent", "Silent Language Server Logs" ),
|
||||
i18n( "lsp_silent_desc", "Hide non-critical language server log messages." ),
|
||||
mSilence );
|
||||
page.addBool(
|
||||
"trim-logs", "/config/trim_logs", i18n( "lsp_trim_logs", "Trim Language Server Logs" ),
|
||||
i18n( "lsp_trim_logs_desc", "Limit each language server log line to 1 KiB." ), mTrimLogs );
|
||||
page.addBool( "semantic-highlighting", "/config/semantic_highlighting",
|
||||
i18n( "lsp_semantic_highlighting", "Semantic Highlighting" ),
|
||||
i18n( "lsp_semantic_highlighting_desc",
|
||||
"Use semantic tokens provided by language servers when available." ),
|
||||
mSemanticHighlighting );
|
||||
page.addStringList(
|
||||
"disable-semantic-highlighting-lang", "/config/disable_semantic_highlighting_lang",
|
||||
i18n( "lsp_disable_semantic_highlighting_lang", "Disable Semantic Highlighting Languages" ),
|
||||
i18n( "lsp_disable_semantic_highlighting_lang_desc",
|
||||
"Comma-separated language identifiers where semantic highlighting is disabled." ) );
|
||||
page.addBool( "breadcrumb-navigation", "/config/breadcrumb_navigation",
|
||||
i18n( "lsp_breadcrumb_navigation", "Breadcrumb Navigation" ),
|
||||
i18n( "lsp_breadcrumb_navigation_desc",
|
||||
"Display language-aware breadcrumb navigation above the editor." ),
|
||||
mBreadcrumb );
|
||||
page.addText(
|
||||
"breadcrumb-height", "/config/breadcrumb_height",
|
||||
i18n( "lsp_breadcrumb_height", "Breadcrumb Height" ),
|
||||
i18n( "lsp_breadcrumb_height_desc", "Height of the editor breadcrumb using a CSS length." ),
|
||||
mBreadcrumbHeight.toString(),
|
||||
[]( const std::string& text ) { return StyleSheetLength::isLength( text ); } );
|
||||
}
|
||||
|
||||
static Action::UniqueID getMouseMoveHash( UICodeEditor* editor ) {
|
||||
return hashCombine( String::hash( "LSPClientPlugin::onMouseMove-" ),
|
||||
reinterpret_cast<Action::UniqueID>( editor ) );
|
||||
@@ -1982,8 +2029,8 @@ void LSPClientPlugin::drawTop( UICodeEditor* editor, const Vector2f& screenStart
|
||||
Vector2f iconPos( { pos.x, screenStart.y + textOffsetY +
|
||||
eefloor( ( textHeight - iconHeight ) * 0.5f ) } );
|
||||
separatorDrawable->draw( iconPos );
|
||||
pos.x +=
|
||||
separatorDrawable->getPixelsSize().getWidth() + eefloor( PixelDensity::dpToPx( 8 ) );
|
||||
pos.x += separatorDrawable->getPixelsSize().getWidth() +
|
||||
eefloor( PixelDensity::dpToPx( 8 ) );
|
||||
separatorDrawable->setColor( c );
|
||||
} else {
|
||||
pos.x += eefloor( PixelDensity::dpToPx( 16 ) );
|
||||
|
||||
@@ -46,6 +46,10 @@ class LSPClientPlugin : public Plugin {
|
||||
|
||||
virtual String::HashType getConfigFileHash() { return mConfigHash; }
|
||||
|
||||
bool hasSettingsPage() const { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page );
|
||||
|
||||
void onRegister( UICodeEditor* );
|
||||
|
||||
void onUnregister( UICodeEditor* );
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace ecode {
|
||||
|
||||
class PluginManager;
|
||||
class PluginContextProvider;
|
||||
class SettingsPage;
|
||||
|
||||
class Plugin : public UICodeEditorPlugin {
|
||||
public:
|
||||
@@ -45,6 +46,10 @@ class Plugin : public UICodeEditorPlugin {
|
||||
|
||||
virtual std::string getFileConfigPath();
|
||||
|
||||
virtual bool hasSettingsPage() const { return false; }
|
||||
|
||||
virtual void registerSettings( SettingsPage& ) {}
|
||||
|
||||
PluginManager* getManager() const;
|
||||
|
||||
PluginContextProvider* getPluginContext() const;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "spellcheckerplugin.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include "eepp/ui/abstract/uiabstractview.hpp"
|
||||
#include "eepp/ui/models/itemlistmodel.hpp"
|
||||
#include "eepp/window/engine.hpp"
|
||||
@@ -15,6 +16,18 @@ using json = nlohmann::json;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void SpellCheckerPlugin::registerSettings( SettingsPage& page ) {
|
||||
page.addGroup( i18n( "general", "General" ) );
|
||||
page.addText( "delay-time", "/config/delay_time",
|
||||
i18n( "spellchecker_delay_time", "Spell Check Delay" ),
|
||||
i18n( "spellchecker_delay_time_desc",
|
||||
"Time to wait before checking spelling after an edit." ),
|
||||
getDelayTime().toString(), []( const std::string& text ) {
|
||||
Time value;
|
||||
return SettingsPage::parseNonNegativeSettingsTime( text, value );
|
||||
} );
|
||||
}
|
||||
|
||||
static constexpr auto SPELL_CHECKER_CMD = "typos";
|
||||
static constexpr auto SPELL_CHECKER_ARGS = "--format=brief";
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@ class SpellCheckerPlugin : public PluginBase {
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
void drawAfterLineText( UICodeEditor* editor, const Int64& index, Vector2f position,
|
||||
const Float& fontSize, const Float& lineHeight ) override;
|
||||
|
||||
@@ -98,7 +102,8 @@ class SpellCheckerPlugin : public PluginBase {
|
||||
|
||||
void invalidateEditors( TextDocument* doc );
|
||||
|
||||
std::optional<SpellCheckerMatch> getMatchFromTextPosition( UICodeEditor* editor, TextPosition pos );
|
||||
std::optional<SpellCheckerMatch> getMatchFromTextPosition( UICodeEditor* editor,
|
||||
TextPosition pos );
|
||||
|
||||
std::optional<SpellCheckerMatch> getMatchFromScreenPos( UICodeEditor* editor, Vector2f pos );
|
||||
|
||||
@@ -109,7 +114,6 @@ class SpellCheckerPlugin : public PluginBase {
|
||||
void goToNextError( UICodeEditor* editor );
|
||||
|
||||
void goToPrevError( UICodeEditor* editor );
|
||||
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "xmltoolsplugin.hpp"
|
||||
#include "../../settingspage.hpp"
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/scopedop.hpp>
|
||||
@@ -8,6 +9,20 @@ using json = nlohmann::json;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
void XMLToolsPlugin::registerSettings( SettingsPage& page ) {
|
||||
page.addGroup( i18n( "general", "General" ) );
|
||||
page.addBool( "highlight-match", "/config/highlight_match",
|
||||
i18n( "xmltools_highlight_match", "Highlight Matching Tags" ),
|
||||
i18n( "xmltools_highlight_match_desc",
|
||||
"Highlight the matching XML or HTML tag at the cursor." ),
|
||||
true );
|
||||
page.addBool( "auto-edit-match", "/config/auto_edit_match",
|
||||
i18n( "xmltools_auto_edit_match", "Edit Matching Tags" ),
|
||||
i18n( "xmltools_auto_edit_match_desc",
|
||||
"Update the matching tag automatically while editing." ),
|
||||
true );
|
||||
}
|
||||
|
||||
Plugin* XMLToolsPlugin::New( PluginManager* pluginManager ) {
|
||||
return eeNew( XMLToolsPlugin, ( pluginManager, false ) );
|
||||
}
|
||||
|
||||
@@ -36,6 +36,10 @@ class XMLToolsPlugin : public PluginBase {
|
||||
|
||||
std::string getDescription() override { return Definition().description; }
|
||||
|
||||
bool hasSettingsPage() const override { return true; }
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
bool getHighlightMatch() const;
|
||||
|
||||
bool getAutoEditMatch() const;
|
||||
@@ -73,17 +77,29 @@ class XMLToolsPlugin : public PluginBase {
|
||||
mDoc( doc ), mParent( parent ) {}
|
||||
|
||||
virtual void onDocumentTextChanged( const DocumentContentChange& );
|
||||
|
||||
virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& ) {}
|
||||
|
||||
virtual void onDocumentCursorChange( const TextPosition& ) {}
|
||||
|
||||
virtual void onDocumentInterestingCursorChange( const TextPosition& ) {}
|
||||
|
||||
virtual void onDocumentSelectionChange( const TextRange& );
|
||||
|
||||
virtual void onDocumentLineCountChange( const size_t&, const size_t& ) {}
|
||||
|
||||
virtual void onDocumentLineChanged( const Int64& ) {}
|
||||
|
||||
virtual void onDocumentSaved( TextDocument* ) {}
|
||||
|
||||
virtual void onDocumentClosed( TextDocument* doc ) { onDocumentReset( doc ); }
|
||||
|
||||
virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {}
|
||||
|
||||
virtual void onDocumentMoved( TextDocument* ) {};
|
||||
|
||||
virtual void onDocumentReset( TextDocument* ) { mSelections.clear(); }
|
||||
|
||||
Client::Type getTextDocumentClientType() { return TextDocument::Client::Auxiliary; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "settingsdocument.hpp"
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
|
||||
using namespace EE::System;
|
||||
|
||||
namespace ecode {
|
||||
|
||||
SettingsDocument::SettingsDocument( std::string path, nlohmann::json data ) :
|
||||
mPath( std::move( path ) ), mOriginal( data ), mData( std::move( data ) ) {}
|
||||
|
||||
std::shared_ptr<SettingsDocument> SettingsDocument::load( std::string path, std::string& error ) {
|
||||
std::string contents;
|
||||
if ( !FileSystem::fileGet( path, contents ) ) {
|
||||
error = "Unable to read settings file";
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
auto data = nlohmann::json::parse( contents, nullptr, true, true );
|
||||
if ( !data.is_object() ) {
|
||||
error = "Settings file root must be an object";
|
||||
return {};
|
||||
}
|
||||
return std::shared_ptr<SettingsDocument>(
|
||||
new SettingsDocument( std::move( path ), std::move( data ) ) );
|
||||
} catch ( const nlohmann::json::exception& exception ) {
|
||||
error = exception.what();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsDocument::save() {
|
||||
if ( !mDirty )
|
||||
return true;
|
||||
if ( !FileSystem::fileWrite( mPath, mData.dump( 2 ) ) )
|
||||
return false;
|
||||
mOriginal = mData;
|
||||
mDirty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
nlohmann::json SettingsDocument::getValue( const std::string& pointer,
|
||||
nlohmann::json fallback ) const {
|
||||
try {
|
||||
const auto path = nlohmann::json::json_pointer( pointer );
|
||||
if ( mData.contains( path ) )
|
||||
return mData.at( path );
|
||||
} catch ( const nlohmann::json::exception& ) {
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
bool SettingsDocument::setValue( const std::string& pointer, nlohmann::json value ) {
|
||||
try {
|
||||
const auto path = nlohmann::json::json_pointer( pointer );
|
||||
if ( mData.contains( path ) && mData.at( path ) == value )
|
||||
return false;
|
||||
mData[path] = std::move( value );
|
||||
mDirty = true;
|
||||
return true;
|
||||
} catch ( const nlohmann::json::exception& ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsDocument::contains( const std::string& pointer ) const {
|
||||
try {
|
||||
return mData.contains( nlohmann::json::json_pointer( pointer ) );
|
||||
} catch ( const nlohmann::json::exception& ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDocument::discard() {
|
||||
mData = mOriginal;
|
||||
mDirty = false;
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef ECODE_SETTINGSDOCUMENT_HPP
|
||||
#define ECODE_SETTINGSDOCUMENT_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace ecode {
|
||||
|
||||
class SettingsDocument {
|
||||
public:
|
||||
static std::shared_ptr<SettingsDocument> load( std::string path, std::string& error );
|
||||
|
||||
template <typename T> T get( const std::string& pointer, T fallback ) const {
|
||||
try {
|
||||
const auto path = nlohmann::json::json_pointer( pointer );
|
||||
if ( mData.contains( path ) )
|
||||
return mData.at( path ).get<T>();
|
||||
} catch ( const nlohmann::json::exception& ) {
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
template <typename T> bool set( const std::string& pointer, T value ) {
|
||||
try {
|
||||
const auto path = nlohmann::json::json_pointer( pointer );
|
||||
if ( mData.contains( path ) && mData.at( path ) == value )
|
||||
return false;
|
||||
mData[path] = std::move( value );
|
||||
mDirty = true;
|
||||
return true;
|
||||
} catch ( const nlohmann::json::exception& ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nlohmann::json getValue( const std::string& pointer, nlohmann::json fallback ) const;
|
||||
|
||||
bool setValue( const std::string& pointer, nlohmann::json value );
|
||||
|
||||
bool contains( const std::string& pointer ) const;
|
||||
|
||||
bool save();
|
||||
|
||||
void discard();
|
||||
|
||||
bool isDirty() const { return mDirty; }
|
||||
|
||||
const std::string& path() const { return mPath; }
|
||||
|
||||
private:
|
||||
SettingsDocument( std::string path, nlohmann::json data );
|
||||
|
||||
std::string mPath;
|
||||
nlohmann::json mOriginal;
|
||||
nlohmann::json mData;
|
||||
bool mDirty{ false };
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
#endif
|
||||
@@ -52,6 +52,7 @@ struct TextSetting {
|
||||
std::function<std::string()> get;
|
||||
std::function<bool( const std::string& )> set;
|
||||
bool commitOnFocusLoss{ false };
|
||||
bool password{ false };
|
||||
};
|
||||
|
||||
struct FloatSetting {
|
||||
@@ -129,8 +130,11 @@ class SettingsModel {
|
||||
}
|
||||
|
||||
const std::vector<SettingsCategory>& categories() const { return mCategories; }
|
||||
|
||||
const std::vector<SettingsGroup>& groups() const { return mGroups; }
|
||||
|
||||
std::vector<SettingDefinition>& settings() { return mSettings; }
|
||||
|
||||
const std::vector<SettingDefinition>& settings() const { return mSettings; }
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
#include "settingspage.hpp"
|
||||
|
||||
namespace ecode {
|
||||
|
||||
bool SettingsPage::parseNonNegativeSettingsTime( const std::string& text, Time& time ) {
|
||||
auto parts = String::split( text, " " );
|
||||
bool hasValue = false;
|
||||
for ( auto& part : parts ) {
|
||||
String::trimInPlace( part );
|
||||
if ( part.empty() )
|
||||
continue;
|
||||
size_t suffixLength =
|
||||
String::endsWith( part, "ms" )
|
||||
? 2
|
||||
: ( String::endsWith( part, "s" ) || String::endsWith( part, "m" ) ? 1 : 0 );
|
||||
std::string number = part.substr( 0, part.size() - suffixLength );
|
||||
double value;
|
||||
if ( number.empty() || !String::fromString( value, number ) || value < 0 )
|
||||
return false;
|
||||
hasValue = true;
|
||||
}
|
||||
if ( !hasValue )
|
||||
return false;
|
||||
time = Time::fromString( text );
|
||||
return true;
|
||||
}
|
||||
|
||||
SettingsPage::SettingsPage( SettingsModel& model, std::shared_ptr<SettingsDocument> document,
|
||||
std::string category, std::string idPrefix ) :
|
||||
mModel( model ),
|
||||
mDocument( std::move( document ) ),
|
||||
mCategory( std::move( category ) ),
|
||||
mIdPrefix( std::move( idPrefix ) ) {}
|
||||
|
||||
bool SettingsPage::addGroup( String name ) {
|
||||
return mModel.addGroup( { mCategory, std::move( name ), mModel.settings().size() } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addBool( std::string id, std::string jsonPointer, String name,
|
||||
String description, bool defaultValue, String group ) {
|
||||
if ( !mDocument )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
return mModel.addSetting( { { mIdPrefix + "." + id, mCategory, std::move( name ),
|
||||
std::move( description ), std::move( group ) },
|
||||
BoolSetting{ [document, jsonPointer, defaultValue] {
|
||||
return document->get<bool>( jsonPointer,
|
||||
defaultValue );
|
||||
},
|
||||
[document, jsonPointer]( bool value ) {
|
||||
document->set( jsonPointer, value );
|
||||
} } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addChoice( std::string id, std::string jsonPointer, String name,
|
||||
String description, std::vector<String> choices,
|
||||
std::vector<nlohmann::json> values, nlohmann::json defaultValue,
|
||||
std::vector<String> choiceDescriptions, String group ) {
|
||||
if ( !mDocument || choices.empty() || choices.size() != values.size() )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
auto mappedValues = std::make_shared<const std::vector<nlohmann::json>>( std::move( values ) );
|
||||
return mModel.addSetting(
|
||||
{ { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
|
||||
std::move( group ) },
|
||||
ChoiceSetting{
|
||||
std::move( choices ), std::move( choiceDescriptions ),
|
||||
[document, jsonPointer, mappedValues, defaultValue = std::move( defaultValue )] {
|
||||
const auto current = document->getValue( jsonPointer, defaultValue );
|
||||
auto found = std::find( mappedValues->begin(), mappedValues->end(), current );
|
||||
return found == mappedValues->end()
|
||||
? size_t{ 0 }
|
||||
: static_cast<size_t>( std::distance( mappedValues->begin(), found ) );
|
||||
},
|
||||
[document, jsonPointer, mappedValues]( size_t selected ) {
|
||||
if ( selected < mappedValues->size() )
|
||||
document->setValue( jsonPointer, ( *mappedValues )[selected] );
|
||||
} } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addEditableChoice( std::string id, std::string jsonPointer, String name,
|
||||
String description, std::vector<String> choices,
|
||||
std::string defaultValue,
|
||||
std::function<bool( const std::string& )> validate,
|
||||
String group ) {
|
||||
if ( !mDocument )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
return mModel.addSetting(
|
||||
{ { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
|
||||
std::move( group ) },
|
||||
EditableChoiceSetting{
|
||||
std::move( choices ),
|
||||
[document, jsonPointer, defaultValue = std::move( defaultValue )] {
|
||||
return String::fromUtf8(
|
||||
document->get<std::string>( jsonPointer, defaultValue ) );
|
||||
},
|
||||
[document, jsonPointer, validate = std::move( validate )]( const String& value ) {
|
||||
const std::string text = value.toUtf8();
|
||||
if ( validate && !validate( text ) )
|
||||
return false;
|
||||
document->set( jsonPointer, text );
|
||||
return true;
|
||||
} } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addInteger( std::string id, std::string jsonPointer, String name,
|
||||
String description, int min, int max, int defaultValue,
|
||||
String group ) {
|
||||
if ( !mDocument || min > max )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
return mModel.addSetting( { { mIdPrefix + "." + id, mCategory, std::move( name ),
|
||||
std::move( description ), std::move( group ) },
|
||||
IntegerSetting{ min, max,
|
||||
[document, jsonPointer, defaultValue] {
|
||||
return document->get<int>( jsonPointer,
|
||||
defaultValue );
|
||||
},
|
||||
[document, jsonPointer]( int value ) {
|
||||
document->set( jsonPointer, value );
|
||||
} } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addText( std::string id, std::string jsonPointer, String name,
|
||||
String description, std::string defaultValue,
|
||||
std::function<bool( const std::string& )> validate,
|
||||
bool commitOnFocusLoss, String group ) {
|
||||
if ( !mDocument )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
return mModel.addSetting(
|
||||
{ { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
|
||||
std::move( group ) },
|
||||
TextSetting{ [document, jsonPointer, defaultValue = std::move( defaultValue )] {
|
||||
return document->get<std::string>( jsonPointer, defaultValue );
|
||||
},
|
||||
[document, jsonPointer,
|
||||
validate = std::move( validate )]( const std::string& value ) {
|
||||
if ( validate && !validate( value ) )
|
||||
return false;
|
||||
document->set( jsonPointer, value );
|
||||
return true;
|
||||
},
|
||||
commitOnFocusLoss } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addSecret( std::string id, std::string jsonPointer, String name,
|
||||
String description, std::string defaultValue, String group,
|
||||
std::string legacyJsonPointer ) {
|
||||
if ( !mDocument )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
return mModel.addSetting(
|
||||
{ { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
|
||||
std::move( group ) },
|
||||
TextSetting{
|
||||
[document, jsonPointer, legacyJsonPointer, defaultValue = std::move( defaultValue )] {
|
||||
if ( !legacyJsonPointer.empty() && !document->contains( jsonPointer ) )
|
||||
return document->get<std::string>( legacyJsonPointer, defaultValue );
|
||||
return document->get<std::string>( jsonPointer, defaultValue );
|
||||
},
|
||||
[document, jsonPointer]( const std::string& value ) {
|
||||
document->set( jsonPointer, value );
|
||||
return true;
|
||||
},
|
||||
true, true } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addJsonObject( std::string id, std::string jsonPointer, String name,
|
||||
String description, nlohmann::json defaultValue, String group ) {
|
||||
if ( !mDocument || !defaultValue.is_object() )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
return mModel.addSetting(
|
||||
{ { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
|
||||
std::move( group ) },
|
||||
TextSetting{ [document, jsonPointer, defaultValue = std::move( defaultValue )] {
|
||||
return document->getValue( jsonPointer, defaultValue ).dump();
|
||||
},
|
||||
[document, jsonPointer]( const std::string& value ) {
|
||||
auto parsed = nlohmann::json::parse( value, nullptr, false );
|
||||
if ( !parsed.is_object() )
|
||||
return false;
|
||||
document->setValue( jsonPointer, std::move( parsed ) );
|
||||
return true;
|
||||
},
|
||||
true } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addStringList( std::string id, std::string jsonPointer, String name,
|
||||
String description, std::vector<std::string> defaultValue,
|
||||
String group ) {
|
||||
if ( !mDocument )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
return mModel.addSetting(
|
||||
{ { mIdPrefix + "." + id, mCategory, std::move( name ), std::move( description ),
|
||||
std::move( group ) },
|
||||
TextSetting{ [document, jsonPointer, defaultValue = std::move( defaultValue )] {
|
||||
return String::join(
|
||||
document->get<std::vector<std::string>>( jsonPointer, defaultValue ),
|
||||
',' );
|
||||
},
|
||||
[document, jsonPointer]( const std::string& value ) {
|
||||
std::vector<std::string> values;
|
||||
for ( auto& item : String::split( value, "," ) ) {
|
||||
String::trimInPlace( item );
|
||||
if ( !item.empty() )
|
||||
values.emplace_back( std::move( item ) );
|
||||
}
|
||||
document->set( jsonPointer, std::move( values ) );
|
||||
return true;
|
||||
},
|
||||
true } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addFloat( std::string id, std::string jsonPointer, String name,
|
||||
String description, double min, double max, double step,
|
||||
double defaultValue, String group ) {
|
||||
if ( !mDocument || min > max || step <= 0 )
|
||||
return false;
|
||||
auto document = mDocument;
|
||||
return mModel.addSetting( { { mIdPrefix + "." + id, mCategory, std::move( name ),
|
||||
std::move( description ), std::move( group ) },
|
||||
FloatSetting{ min, max, step,
|
||||
[document, jsonPointer, defaultValue] {
|
||||
return document->get<double>( jsonPointer,
|
||||
defaultValue );
|
||||
},
|
||||
[document, jsonPointer]( double value ) {
|
||||
document->set( jsonPointer, value );
|
||||
} } } );
|
||||
}
|
||||
|
||||
bool SettingsPage::addAction( std::string id, String name, String description, String buttonText,
|
||||
std::function<void()> action, String group ) {
|
||||
if ( !action )
|
||||
return false;
|
||||
return mModel.addSetting( { { mIdPrefix + "." + id, mCategory, std::move( name ),
|
||||
std::move( description ), std::move( group ) },
|
||||
ActionSetting{ std::move( buttonText ), std::move( action ) } } );
|
||||
}
|
||||
|
||||
} // namespace ecode
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef ECODE_SETTINGSPAGE_HPP
|
||||
#define ECODE_SETTINGSPAGE_HPP
|
||||
|
||||
#include "settingsdocument.hpp"
|
||||
#include "settingsmodel.hpp"
|
||||
|
||||
namespace ecode {
|
||||
|
||||
class SettingsPage {
|
||||
public:
|
||||
static bool parseNonNegativeSettingsTime( const std::string& text, Time& time );
|
||||
|
||||
SettingsPage( SettingsModel& model, std::shared_ptr<SettingsDocument> document,
|
||||
std::string category, std::string idPrefix );
|
||||
|
||||
bool addGroup( String name );
|
||||
|
||||
bool addBool( std::string id, std::string jsonPointer, String name, String description,
|
||||
bool defaultValue, String group = {} );
|
||||
|
||||
bool addChoice( std::string id, std::string jsonPointer, String name, String description,
|
||||
std::vector<String> choices, std::vector<nlohmann::json> values,
|
||||
nlohmann::json defaultValue, std::vector<String> choiceDescriptions = {},
|
||||
String group = {} );
|
||||
|
||||
bool addEditableChoice( std::string id, std::string jsonPointer, String name,
|
||||
String description, std::vector<String> choices,
|
||||
std::string defaultValue,
|
||||
std::function<bool( const std::string& )> validate = {},
|
||||
String group = {} );
|
||||
|
||||
bool addInteger( std::string id, std::string jsonPointer, String name, String description,
|
||||
int min, int max, int defaultValue, String group = {} );
|
||||
|
||||
bool addText( std::string id, std::string jsonPointer, String name, String description,
|
||||
std::string defaultValue, std::function<bool( const std::string& )> validate = {},
|
||||
bool commitOnFocusLoss = false, String group = {} );
|
||||
|
||||
bool addSecret( std::string id, std::string jsonPointer, String name, String description,
|
||||
std::string defaultValue = {}, String group = {},
|
||||
std::string legacyJsonPointer = {} );
|
||||
|
||||
bool addJsonObject( std::string id, std::string jsonPointer, String name, String description,
|
||||
nlohmann::json defaultValue = nlohmann::json::object(), String group = {} );
|
||||
|
||||
bool addStringList( std::string id, std::string jsonPointer, String name, String description,
|
||||
std::vector<std::string> defaultValue = {}, String group = {} );
|
||||
|
||||
bool addFloat( std::string id, std::string jsonPointer, String name, String description,
|
||||
double min, double max, double step, double defaultValue, String group = {} );
|
||||
|
||||
bool addAction( std::string id, String name, String description, String buttonText,
|
||||
std::function<void()> action, String group = {} );
|
||||
|
||||
private:
|
||||
SettingsModel& mModel;
|
||||
std::shared_ptr<SettingsDocument> mDocument;
|
||||
std::string mCategory;
|
||||
std::string mIdPrefix;
|
||||
};
|
||||
|
||||
} // namespace ecode
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,10 @@
|
||||
#include "settingspanel.hpp"
|
||||
#include "datetimecontroller.hpp"
|
||||
#include "ecode.hpp"
|
||||
#include "plugins/plugin.hpp"
|
||||
#include "plugins/pluginmanager.hpp"
|
||||
#include "settingsdocument.hpp"
|
||||
#include "settingspage.hpp"
|
||||
#include "uitreeviewfs.hpp"
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
@@ -220,6 +224,9 @@ static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
|
||||
layout-width: 210dp;
|
||||
layout-height: wrap_content;
|
||||
}
|
||||
.settings_panel .settings_text.error {
|
||||
border-color: var(--theme-error);
|
||||
}
|
||||
.settings_panel .settings_integer {
|
||||
layout-width: 110dp;
|
||||
layout-height: wrap_content;
|
||||
@@ -230,7 +237,7 @@ static constexpr const char* SETTINGS_PANEL_LAYOUT = R"xml(
|
||||
<Splitter id="settings_splitter" lw="mp" lh="mp" orientation="horizontal" splitter-partition="220dp">
|
||||
<vbox id="settings_sidebar" lw="0" lh="0" min-width="160dp">
|
||||
<TextInput id="settings_filter" lw="mp" lh="wc" hint="@string(search_settings, Search settings...)" />
|
||||
<TreeView id="settings_categories" lw="mp" lh="mp" />
|
||||
<TreeView id="settings_categories" lw="mp" lh="o" lw8="1" />
|
||||
</vbox>
|
||||
<ScrollView id="settings_scroll" lw="0" lw8="1" lh="mp" focusable="false">
|
||||
<vbox id="settings_rows" lw="mp" lh="wc">
|
||||
@@ -327,6 +334,7 @@ void SettingsPanel::PanelState::reset() {
|
||||
materializedCategories.clear();
|
||||
subcategoryHeadings.clear();
|
||||
model.clear();
|
||||
documents.clear();
|
||||
settingViews.clear();
|
||||
selectedCategory.clear();
|
||||
categoryFilter.clear();
|
||||
@@ -426,10 +434,12 @@ void SettingsPanel::create( Scope scope ) {
|
||||
panel.window->getKeyBindings().addKeybind(
|
||||
{ KEY_E, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "focusSettingsCategories" );
|
||||
panel.settings->beginAttributesTransaction();
|
||||
if ( scope == Scope::User )
|
||||
if ( scope == Scope::User ) {
|
||||
addUserSettings( panel );
|
||||
else
|
||||
addPluginSettings( panel );
|
||||
} else {
|
||||
addProjectSettings( panel );
|
||||
}
|
||||
materializeCategory( panel, panel.selectedCategory );
|
||||
setupCategories( panel );
|
||||
panel.settings->endAttributesTransaction();
|
||||
@@ -459,6 +469,10 @@ void SettingsPanel::create( Scope scope ) {
|
||||
} );
|
||||
panel.connections +=
|
||||
panel.window->connect( Event::OnWindowClose, [this, &panel, scope]( const Event* ) {
|
||||
for ( const auto& document : panel.documents ) {
|
||||
if ( !document->save() )
|
||||
Log::error( "Could not save settings document: %s", document->path() );
|
||||
}
|
||||
if ( scope == Scope::User )
|
||||
mApp->saveConfig();
|
||||
else
|
||||
@@ -501,7 +515,6 @@ void SettingsPanel::addCategory( PanelState& panel, const std::string& id, const
|
||||
void SettingsPanel::addSubcategoryHeading( PanelState& panel, const std::string& category,
|
||||
const String& name ) {
|
||||
panel.model.addGroup( { category, name, panel.model.settings().size() } );
|
||||
panel.subcategoryHeadings.push_back( { category, name, nullptr } );
|
||||
}
|
||||
|
||||
void SettingsPanel::setupCategories( PanelState& panel ) {
|
||||
@@ -636,14 +649,7 @@ void SettingsPanel::materializeCategory( PanelState& panel, const std::string& c
|
||||
->loadLayoutFromString( SETTINGS_SUBCATEGORY_HEADING_LAYOUT, container->second )
|
||||
->asType<UITextView>();
|
||||
heading->setText( group.name );
|
||||
auto subcategory =
|
||||
std::find_if( panel.subcategoryHeadings.begin(), panel.subcategoryHeadings.end(),
|
||||
[&group]( const auto& item ) {
|
||||
return item.category == group.category &&
|
||||
item.name == group.name && !item.heading;
|
||||
} );
|
||||
if ( subcategory != panel.subcategoryHeadings.end() )
|
||||
subcategory->heading = heading;
|
||||
panel.subcategoryHeadings.push_back( { group.category, group.name, heading } );
|
||||
}
|
||||
auto& view = panel.settingViews[i];
|
||||
if ( auto* value = std::get_if<BoolPointerSetting>( &setting.value ) ) {
|
||||
@@ -712,6 +718,8 @@ void SettingsPanel::materializeCategory( PanelState& panel, const std::string& c
|
||||
} else if ( auto* value = std::get_if<TextSetting>( &setting.value ) ) {
|
||||
auto* row = createRow( panel, setting, view, SETTINGS_TEXT_ROW_LAYOUT.root() );
|
||||
auto* input = row->find<UITextInput>( "setting_control_widget" );
|
||||
if ( value->password )
|
||||
input->setMode( UITextInput::TextInputMode::Password );
|
||||
input->setText( String::fromUtf8( value->get() ) );
|
||||
auto commit = [input, value]( const Event* ) {
|
||||
if ( !value->set( input->getText().toUtf8() ) ) {
|
||||
@@ -787,29 +795,6 @@ static void setNodeTreeEnabled( Node* node, bool enabled ) {
|
||||
setNodeTreeEnabled( child, enabled );
|
||||
}
|
||||
|
||||
static bool parseNonNegativeTime( const std::string& text, Time& time ) {
|
||||
auto parts = String::split( text, " " );
|
||||
bool hasValue = false;
|
||||
for ( auto& part : parts ) {
|
||||
String::trimInPlace( part );
|
||||
if ( part.empty() )
|
||||
continue;
|
||||
size_t suffixLength =
|
||||
String::endsWith( part, "ms" )
|
||||
? 2
|
||||
: ( String::endsWith( part, "s" ) || String::endsWith( part, "m" ) ? 1 : 0 );
|
||||
std::string number = part.substr( 0, part.size() - suffixLength );
|
||||
double value;
|
||||
if ( number.empty() || !String::fromString( value, number ) || value < 0 )
|
||||
return false;
|
||||
hasValue = true;
|
||||
}
|
||||
if ( !hasValue )
|
||||
return false;
|
||||
time = Time::fromString( text );
|
||||
return true;
|
||||
}
|
||||
|
||||
void SettingsPanel::setCategoryEnabled( PanelState& panel, const std::string& category,
|
||||
bool enabled, const std::string& excludedSetting ) {
|
||||
auto& settings = panel.model.settings();
|
||||
@@ -1633,7 +1618,7 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
|
||||
[this] { return mApp->getConfig().editor.cursorBlinkingTime.toString(); },
|
||||
[this]( const std::string& text ) {
|
||||
Time value;
|
||||
if ( !parseNonNegativeTime( text, value ) )
|
||||
if ( !SettingsPage::parseNonNegativeSettingsTime( text, value ) )
|
||||
return false;
|
||||
mApp->getConfig().editor.cursorBlinkingTime = value;
|
||||
mApp->getSplitter()->forEachEditor(
|
||||
@@ -1695,7 +1680,8 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
|
||||
[this] { return mApp->getConfig().editor.codeFoldingRefreshFreq.toString(); },
|
||||
[this]( const std::string& text ) {
|
||||
Time value;
|
||||
if ( !parseNonNegativeTime( text, value ) || value < Seconds( 1 ) )
|
||||
if ( !SettingsPage::parseNonNegativeSettingsTime( text, value ) ||
|
||||
value < Seconds( 1 ) )
|
||||
return false;
|
||||
mApp->getConfig().editor.codeFoldingRefreshFreq = value;
|
||||
mApp->getSplitter()->forEachEditor(
|
||||
@@ -2036,6 +2022,42 @@ void SettingsPanel::addUserSettings( PanelState& panel ) {
|
||||
"Keep the terminal scrollbar hidden." ) } );
|
||||
}
|
||||
|
||||
void SettingsPanel::addPluginSettings( PanelState& panel ) {
|
||||
auto* manager = mApp->getPluginManager();
|
||||
if ( !manager )
|
||||
return;
|
||||
manager->forEachPlugin( [this, &panel]( Plugin* plugin ) {
|
||||
if ( !plugin || !plugin->isReady() || !plugin->hasSettingsPage() ||
|
||||
!plugin->hasFileConfig() )
|
||||
return;
|
||||
std::string error;
|
||||
auto document = SettingsDocument::load( plugin->getFileConfigPath(), error );
|
||||
if ( !document ) {
|
||||
Log::warning( "Could not load settings for plugin %s: %s", plugin->getId(), error );
|
||||
return;
|
||||
}
|
||||
const std::string category = "plugins." + plugin->getId();
|
||||
addCategory( panel, category, mApp->i18n( "plugins", "Plugins" ), plugin->getTitle() );
|
||||
SettingsPage page( panel.model, document, category, plugin->getId() );
|
||||
plugin->registerSettings( page );
|
||||
const std::string path = document->path();
|
||||
addAction( panel,
|
||||
{ plugin->getId() + ".edit-json", category,
|
||||
mApp->i18n( "edit_settings_json", "Edit Settings JSON" ),
|
||||
mApp->i18n( "edit_settings_json_desc",
|
||||
"Open the complete plugin configuration file." ) },
|
||||
mApp->i18n( "open_file_ellipsis", "Open File..." ), [this, &panel, path] {
|
||||
auto* window = panel.window;
|
||||
window->runOnMainThread( [this, window, path] {
|
||||
window->closeWindow();
|
||||
mApp->getUISceneNode()->runOnMainThread(
|
||||
[this, path] { mApp->focusOrLoadFile( path ); } );
|
||||
} );
|
||||
} );
|
||||
panel.documents.emplace_back( std::move( document ) );
|
||||
} );
|
||||
}
|
||||
|
||||
void SettingsPanel::addProjectSettings( PanelState& panel ) {
|
||||
addCategory( panel, "editor.document", mApp->i18n( "editor", "Editor" ),
|
||||
mApp->i18n( "document", "Document" ) );
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace ecode {
|
||||
|
||||
class App;
|
||||
class SettingsDocument;
|
||||
|
||||
class SettingsPanel {
|
||||
public:
|
||||
@@ -40,6 +41,7 @@ class SettingsPanel {
|
||||
std::shared_ptr<Model> categoryModel;
|
||||
UIBindingGroup bindingGroup;
|
||||
SettingsModel model;
|
||||
std::vector<std::shared_ptr<SettingsDocument>> documents;
|
||||
std::vector<std::pair<std::string, std::vector<std::string>>> categoryItems;
|
||||
UnorderedMap<std::string, std::string> categoryIds;
|
||||
UnorderedMap<std::string, String> categorySearchText;
|
||||
@@ -61,42 +63,65 @@ class SettingsPanel {
|
||||
PanelState mProject;
|
||||
|
||||
PanelState& state( Scope scope );
|
||||
|
||||
void create( Scope scope );
|
||||
|
||||
void selectCategory( PanelState& state, const std::string& category );
|
||||
|
||||
void addUserSettings( PanelState& state );
|
||||
|
||||
void addPluginSettings( PanelState& state );
|
||||
|
||||
void addProjectSettings( PanelState& state );
|
||||
|
||||
void addCategory( PanelState& state, const std::string& id, const String& parent,
|
||||
const String& name );
|
||||
|
||||
void addSubcategoryHeading( PanelState& state, const std::string& category,
|
||||
const String& name );
|
||||
|
||||
void setupCategories( PanelState& state );
|
||||
|
||||
void addBool( PanelState& state, SettingDescriptor binding, bool* value,
|
||||
std::function<void( bool )> apply = {} );
|
||||
|
||||
void addBool( PanelState& state, SettingDescriptor binding, std::function<bool()> get,
|
||||
std::function<void( bool )> set );
|
||||
|
||||
void addChoice( PanelState& state, SettingDescriptor binding,
|
||||
const std::vector<String>& choices, std::function<size_t()> get,
|
||||
std::function<void( size_t )> set,
|
||||
std::vector<String> choiceDescriptions = {} );
|
||||
|
||||
void addEditableChoice( PanelState& state, SettingDescriptor binding,
|
||||
const std::vector<String>& choices, std::function<String()> get,
|
||||
std::function<bool( const String& )> set );
|
||||
|
||||
void addInteger( PanelState& state, SettingDescriptor binding, int min, int max,
|
||||
std::function<int()> get, std::function<void( int )> set );
|
||||
|
||||
void addText( PanelState& state, SettingDescriptor binding, std::function<std::string()> get,
|
||||
std::function<bool( const std::string& )> set, bool commitOnFocusLoss = false );
|
||||
|
||||
void addFloat( PanelState& state, SettingDescriptor binding, double min, double max,
|
||||
double step, std::function<double()> get, std::function<void( double )> set );
|
||||
|
||||
void addAction( PanelState& state, SettingDescriptor binding, const String& buttonText,
|
||||
std::function<void()> action );
|
||||
|
||||
UIWidget* createRow( PanelState& state, SettingDefinition& setting, SettingView& view,
|
||||
pugi::xml_node layout );
|
||||
|
||||
UICheckBox* createBoolControl( PanelState& state, SettingDefinition& setting,
|
||||
SettingView& view );
|
||||
|
||||
void materializeCategory( PanelState& state, const std::string& category );
|
||||
|
||||
void materializeVisibleSettings( PanelState& state, const String& query );
|
||||
|
||||
void setCategoryEnabled( PanelState& state, const std::string& category, bool enabled,
|
||||
const std::string& excludedSetting = {} );
|
||||
|
||||
void filter( PanelState& state );
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user