add plugin settings, per-tile refresh, settings menu, touch-friendly UI
- Plugin settings infrastructure in PluginManager with config/plugin_settings.cfg storage, manifest [settings] sections, CRUD API, plugin_setting_changed signal - Per-tile refresh intervals: 100ms base tick, per-tile update_interval_ms metadata, dynamic tween durations clamped to refresh window - Settings menu (PopupPanel) with General/Plugins/About tabs, plugin activation toggles, per-plugin settings cog buttons - Plugin settings popup (PopupPanel) with dynamic UI from setting definitions (SpinBox/CheckBox/LineEdit), auto-save on change - Modal behavior: exclusive = true on settings windows - Touch-friendly sizing: enlarged close buttons, cog buttons, controls, spacing - Red corner close button redesign with rounded corners, hover/pressed states - Split system_monitor into local_system_monitor (CPU, Memory) and test plugins - Plugin activation/deactivation with layout preservation
This commit is contained in:
parent
12b45b2685
commit
57b36798b9
24 changed files with 1065 additions and 183 deletions
|
|
@ -18,6 +18,10 @@ var current_layout_name: String = "Main"
|
|||
## The active layout data: tile_id -> {col, row, w, h}
|
||||
var _layout: Dictionary = {}
|
||||
|
||||
## Grid dimensions for this layout.
|
||||
var grid_columns: int = 4
|
||||
var grid_rows: int = 3
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
if not DirAccess.dir_exists_absolute(LAYOUT_DIR):
|
||||
|
|
@ -64,6 +68,17 @@ func remove_tile(tile_id: String) -> void:
|
|||
_layout.erase(tile_id)
|
||||
|
||||
|
||||
## Set grid dimensions stored in this layout.
|
||||
func set_grid_size(cols: int, rows: int) -> void:
|
||||
grid_columns = cols
|
||||
grid_rows = rows
|
||||
|
||||
|
||||
## Get grid dimensions for the current layout.
|
||||
func get_grid_size() -> Dictionary:
|
||||
return { columns = grid_columns, rows = grid_rows }
|
||||
|
||||
|
||||
## Save the current layout to config/layouts/{name}.cfg
|
||||
func save_layout(layout_name: String = "") -> void:
|
||||
if layout_name.is_empty():
|
||||
|
|
@ -72,6 +87,8 @@ func save_layout(layout_name: String = "") -> void:
|
|||
var cfg := ConfigFile.new()
|
||||
cfg.set_value("layout", "name", layout_name)
|
||||
cfg.set_value("layout", "saved_at", Time.get_datetime_string_from_system())
|
||||
cfg.set_value("layout", "grid_columns", grid_columns)
|
||||
cfg.set_value("layout", "grid_rows", grid_rows)
|
||||
|
||||
var tile_ids: Array = _layout.keys()
|
||||
cfg.set_value("tiles", "count", tile_ids.size())
|
||||
|
|
@ -106,6 +123,9 @@ func load_layout(name: String) -> bool:
|
|||
_layout.clear()
|
||||
current_layout_name = name
|
||||
|
||||
grid_columns = cfg.get_value("layout", "grid_columns", 4)
|
||||
grid_rows = cfg.get_value("layout", "grid_rows", 3)
|
||||
|
||||
var tile_count: int = cfg.get_value("tiles", "count", 0)
|
||||
for i in range(tile_count):
|
||||
var section := "tile_%d" % i
|
||||
|
|
|
|||
|
|
@ -5,19 +5,30 @@ extends Node
|
|||
|
||||
signal plugin_loaded(plugin_id: String, plugin_name: String)
|
||||
signal plugin_load_failed(plugin_id: String, reason: String)
|
||||
signal plugin_active_changed(plugin_id: String, active: bool)
|
||||
signal plugin_setting_changed(plugin_id: String, key: String, value: Variant)
|
||||
|
||||
## All loaded plugin data: plugin_id -> Dictionary
|
||||
var _plugins: Dictionary = {}
|
||||
|
||||
## Tracks which plugins are active (plugin_id -> bool). Default true.
|
||||
var _plugin_active: Dictionary = {}
|
||||
|
||||
## All tile definitions: tile_id -> Dictionary
|
||||
var _tiles: Dictionary = {}
|
||||
|
||||
## Plugin-id -> Array[tile_id] for reverse lookup
|
||||
var _plugin_tiles: Dictionary = {}
|
||||
|
||||
## Per-plugin settings: plugin_id -> { key -> value }
|
||||
var _plugin_settings: Dictionary = {}
|
||||
|
||||
const PLUGIN_SETTINGS_PATH: String = "res://config/plugin_settings.cfg"
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_scan_plugins()
|
||||
_load_plugin_settings()
|
||||
|
||||
|
||||
## Scan res://plugins/*/plugin.cfg and load all manifests.
|
||||
|
|
@ -57,6 +68,7 @@ func _load_manifest(plugin_id: String, path: String) -> void:
|
|||
base_path = path.get_base_dir(),
|
||||
}
|
||||
_plugins[plugin_id] = plugin_info
|
||||
_plugin_active[plugin_id] = true
|
||||
|
||||
# Load tile definitions
|
||||
var tile_count: int = cfg.get_value("tiles", "count", 0)
|
||||
|
|
@ -91,6 +103,31 @@ func _load_manifest(plugin_id: String, path: String) -> void:
|
|||
_plugin_tiles[plugin_id] = []
|
||||
_plugin_tiles[plugin_id].append(scoped_id)
|
||||
|
||||
# Load setting definitions
|
||||
var setting_defs: Array[Dictionary] = []
|
||||
var setting_count: int = cfg.get_value("settings", "count", 0)
|
||||
for i in range(setting_count):
|
||||
var s_section := "setting_%d" % i
|
||||
var skey: String = cfg.get_value(s_section, "key", "")
|
||||
if skey.is_empty():
|
||||
continue
|
||||
var sd: Dictionary = {
|
||||
key = skey,
|
||||
label = cfg.get_value(s_section, "label", skey),
|
||||
type = cfg.get_value(s_section, "type", "string"),
|
||||
}
|
||||
# Read optional fields that don't have a default value
|
||||
if cfg.has_section_key(s_section, "default"):
|
||||
sd.default = cfg.get_value(s_section, "default")
|
||||
if cfg.has_section_key(s_section, "min"):
|
||||
sd.min = cfg.get_value(s_section, "min")
|
||||
if cfg.has_section_key(s_section, "max"):
|
||||
sd.max = cfg.get_value(s_section, "max")
|
||||
if cfg.has_section_key(s_section, "step"):
|
||||
sd.step = cfg.get_value(s_section, "step")
|
||||
setting_defs.append(sd)
|
||||
plugin_info.settings = setting_defs
|
||||
|
||||
plugin_loaded.emit(plugin_id, plugin_info.name)
|
||||
|
||||
|
||||
|
|
@ -107,6 +144,71 @@ func get_plugin(plugin_id: String) -> Dictionary:
|
|||
return _plugins.get(plugin_id, {})
|
||||
|
||||
|
||||
## Returns true if the plugin is active (tiles can be instantiated).
|
||||
func is_plugin_active(plugin_id: String) -> bool:
|
||||
return _plugin_active.get(plugin_id, true)
|
||||
|
||||
|
||||
## Enable or disable a plugin. When disabled, tiles cannot be instantiated.
|
||||
func set_plugin_active(plugin_id: String, active: bool) -> void:
|
||||
if _plugin_active.get(plugin_id, true) == active:
|
||||
return
|
||||
_plugin_active[plugin_id] = active
|
||||
plugin_active_changed.emit(plugin_id, active)
|
||||
|
||||
|
||||
## Returns setting definitions for a plugin (from its manifest).
|
||||
func get_plugin_settings(plugin_id: String) -> Array[Dictionary]:
|
||||
var info: Dictionary = _plugins.get(plugin_id, {})
|
||||
var defs: Array = info.get("settings", [])
|
||||
return defs.duplicate()
|
||||
|
||||
|
||||
## Read a plugin setting value, falling back to the default from the manifest.
|
||||
func get_plugin_setting(plugin_id: String, key: String, default_value: Variant = null) -> Variant:
|
||||
if _plugin_settings.has(plugin_id) and _plugin_settings[plugin_id].has(key):
|
||||
return _plugin_settings[plugin_id][key]
|
||||
# Fall back to manifest default
|
||||
var info: Dictionary = _plugins.get(plugin_id, {})
|
||||
for sd in info.get("settings", []):
|
||||
if sd.get("key", "") == key:
|
||||
return sd.get("default", default_value)
|
||||
return default_value
|
||||
|
||||
|
||||
## Persist a plugin setting and emit change signal.
|
||||
func set_plugin_setting(plugin_id: String, key: String, value: Variant) -> void:
|
||||
if not _plugin_settings.has(plugin_id):
|
||||
_plugin_settings[plugin_id] = {}
|
||||
if _plugin_settings[plugin_id].get(key) == value:
|
||||
return
|
||||
_plugin_settings[plugin_id][key] = value
|
||||
_save_plugin_settings()
|
||||
plugin_setting_changed.emit(plugin_id, key, value)
|
||||
|
||||
|
||||
func _load_plugin_settings() -> void:
|
||||
var cfg := ConfigFile.new()
|
||||
if cfg.load(PLUGIN_SETTINGS_PATH) != OK:
|
||||
return
|
||||
for section in cfg.get_sections():
|
||||
_plugin_settings[section] = {}
|
||||
for key in cfg.get_section_keys(section):
|
||||
_plugin_settings[section][key] = cfg.get_value(section, key)
|
||||
|
||||
|
||||
func _save_plugin_settings() -> void:
|
||||
var cfg := ConfigFile.new()
|
||||
for pid in _plugin_settings:
|
||||
for key in _plugin_settings[pid]:
|
||||
cfg.set_value(pid, key, _plugin_settings[pid][key])
|
||||
# Only write if there's data
|
||||
if cfg.get_sections().is_empty():
|
||||
return
|
||||
DirAccess.make_dir_recursive_absolute("res://config")
|
||||
cfg.save(PLUGIN_SETTINGS_PATH)
|
||||
|
||||
|
||||
## Returns all tile definitions across all plugins.
|
||||
func get_all_tile_defs() -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
|
|
@ -134,6 +236,10 @@ func instantiate_tile(tile_id: String) -> Control:
|
|||
var def: Dictionary = _tiles.get(tile_id, {})
|
||||
if def.is_empty():
|
||||
return null
|
||||
# Don't instantiate tiles from inactive plugins
|
||||
var pid: String = def.get("plugin_id", "")
|
||||
if not pid.is_empty() and not is_plugin_active(pid):
|
||||
return null
|
||||
var scene_path: String = def.get("scene", "")
|
||||
if scene_path.is_empty():
|
||||
return null
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue