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
|
|
@ -5,6 +5,7 @@ extends PanelContainer
|
|||
@onready var grid: DashboardGrid = %DashboardGrid
|
||||
|
||||
var _tile_instances: Array[Control] = []
|
||||
var _last_refresh: Dictionary = {} # tile_instance -> last tick ms
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -32,6 +33,13 @@ func _load_tiles_from_plugins() -> void:
|
|||
if all_tile_defs.is_empty():
|
||||
return
|
||||
|
||||
# Set grid dimensions from layout before placing tiles
|
||||
if LayoutManager.has_method("get_grid_size"):
|
||||
var ls := LayoutManager.get_grid_size()
|
||||
grid.grid_columns = ls.columns
|
||||
grid.grid_rows = ls.rows
|
||||
grid._rebuild_grid()
|
||||
|
||||
# Load saved layout (if any)
|
||||
var layout_data: Dictionary = {}
|
||||
if LayoutManager.has_method("get_layout"):
|
||||
|
|
@ -42,13 +50,14 @@ func _load_tiles_from_plugins() -> void:
|
|||
|
||||
# First pass: place tiles that have a saved layout position
|
||||
for tid in layout_data:
|
||||
var pos: Dictionary = layout_data[tid]
|
||||
var def := _find_tile_def(all_tile_defs, tid)
|
||||
if def.is_empty():
|
||||
# Plugin/tile not installed — skip gracefully
|
||||
continue
|
||||
var instance := PluginManager.instantiate_tile(tid)
|
||||
if instance == null:
|
||||
continue
|
||||
var pos: Dictionary = layout_data[tid]
|
||||
grid.place_module(instance, pos.col, pos.row, pos.w, pos.h)
|
||||
_tile_instances.append(instance)
|
||||
placed_ids.append(tid)
|
||||
|
|
@ -79,36 +88,152 @@ func _load_tiles_from_plugins() -> void:
|
|||
next_col = 0
|
||||
next_row = gs.y
|
||||
|
||||
# Connect auto-save signals
|
||||
grid.module_placed.connect(_save_layout)
|
||||
# Connect auto-save signals and tile tracking
|
||||
grid.module_placed.connect(_on_module_placed)
|
||||
if grid.has_signal("module_resized"):
|
||||
grid.module_resized.connect(_save_layout)
|
||||
|
||||
# Start refresh timer
|
||||
_start_refresh_timer()
|
||||
# Listen for plugin activation changes
|
||||
if PluginManager.has_signal("plugin_active_changed"):
|
||||
PluginManager.plugin_active_changed.connect(_on_plugin_active_changed)
|
||||
|
||||
# Listen for plugin setting changes to update cached intervals
|
||||
if PluginManager.has_signal("plugin_setting_changed"):
|
||||
PluginManager.plugin_setting_changed.connect(_on_plugin_setting_changed)
|
||||
|
||||
# Cache intervals for all placed tiles
|
||||
for mod in _tile_instances:
|
||||
if is_instance_valid(mod):
|
||||
_cache_tile_interval(mod)
|
||||
|
||||
# Start per-tile refresh tick
|
||||
_start_tick_timer()
|
||||
|
||||
|
||||
func _start_refresh_timer() -> void:
|
||||
var interval: float = 1.0
|
||||
if ConfigManager.has_method("get_setting"):
|
||||
var val: Variant = ConfigManager.get_setting("performance_refresh_interval", 1.0)
|
||||
if typeof(val) == TYPE_FLOAT or typeof(val) == TYPE_INT:
|
||||
interval = float(val)
|
||||
|
||||
func _start_tick_timer() -> void:
|
||||
var timer := Timer.new()
|
||||
timer.timeout.connect(_refresh_modules)
|
||||
timer.timeout.connect(_tick_refresh)
|
||||
timer.autostart = true
|
||||
timer.wait_time = interval
|
||||
timer.wait_time = 0.1
|
||||
add_child(timer)
|
||||
|
||||
|
||||
func _refresh_modules() -> void:
|
||||
func _tick_refresh() -> void:
|
||||
var now := Time.get_ticks_msec()
|
||||
var data: Dictionary = {}
|
||||
for mod in _tile_instances:
|
||||
if is_instance_valid(mod) and mod.has_method("refresh"):
|
||||
if not is_instance_valid(mod) or not mod.has_method("refresh"):
|
||||
continue
|
||||
var interval: int = mod.get_meta("update_interval_ms", 1000)
|
||||
var last: int = _last_refresh.get(mod, 0)
|
||||
if now - last >= interval:
|
||||
_last_refresh[mod] = now
|
||||
data["update_interval_ms"] = interval
|
||||
mod.refresh(data)
|
||||
|
||||
|
||||
func _cache_tile_interval(mod: Control) -> void:
|
||||
var tid: String = mod.get_meta("tile_id", "")
|
||||
if tid.is_empty():
|
||||
return
|
||||
var parts := tid.split("/")
|
||||
if parts.size() < 2:
|
||||
return
|
||||
var pid: String = parts[0]
|
||||
var tile_local: String = parts[1]
|
||||
if PluginManager.has_method("get_plugin_setting"):
|
||||
var interval: int = PluginManager.get_plugin_setting(pid, tile_local + "_update_interval_ms", 1000)
|
||||
mod.set_meta("update_interval_ms", interval)
|
||||
|
||||
|
||||
func _on_module_placed(mod: Control, col: int, row: int) -> void:
|
||||
_save_layout(mod, col, row)
|
||||
if mod not in _tile_instances:
|
||||
_tile_instances.append(mod)
|
||||
_cache_tile_interval(mod)
|
||||
# Give the new tile a chance to set up before the next refresh
|
||||
if mod.has_method("refresh"):
|
||||
mod.refresh({})
|
||||
|
||||
|
||||
func _on_plugin_setting_changed(plugin_id: String, key: String, value: Variant) -> void:
|
||||
# Update cached interval on affected tiles
|
||||
var suffix := "_update_interval_ms"
|
||||
if not key.ends_with(suffix):
|
||||
return
|
||||
for mod in _tile_instances:
|
||||
if not is_instance_valid(mod):
|
||||
continue
|
||||
var tid: String = mod.get_meta("tile_id", "")
|
||||
if tid.begins_with(plugin_id + "/"):
|
||||
_cache_tile_interval(mod)
|
||||
|
||||
|
||||
func _on_plugin_active_changed(plugin_id: String, active: bool) -> void:
|
||||
if active:
|
||||
_activate_plugin_tiles(plugin_id)
|
||||
else:
|
||||
_deactivate_plugin_tiles(plugin_id)
|
||||
|
||||
|
||||
func _deactivate_plugin_tiles(plugin_id: String) -> void:
|
||||
# Save layout first to preserve tile positions
|
||||
_save_layout()
|
||||
|
||||
var to_remove: Array[Control] = []
|
||||
for mod in _tile_instances:
|
||||
if not is_instance_valid(mod):
|
||||
continue
|
||||
var tid: String = mod.get_meta("tile_id", "")
|
||||
if tid.begins_with(plugin_id + "/"):
|
||||
to_remove.append(mod)
|
||||
|
||||
for mod in to_remove:
|
||||
grid.remove_module(mod)
|
||||
_tile_instances.erase(mod)
|
||||
_last_refresh.erase(mod)
|
||||
mod.queue_free()
|
||||
|
||||
# Do NOT save layout again — old positions are preserved in the saved layout
|
||||
|
||||
|
||||
func _activate_plugin_tiles(plugin_id: String) -> void:
|
||||
var tile_ids: PackedStringArray = PluginManager.get_plugin_tile_ids(plugin_id)
|
||||
if tile_ids.is_empty():
|
||||
return
|
||||
|
||||
var layout_data: Dictionary = {}
|
||||
if LayoutManager.has_method("get_layout"):
|
||||
layout_data = LayoutManager.get_layout()
|
||||
|
||||
var gs := grid.get_grid_size()
|
||||
for tid in tile_ids:
|
||||
var instance := PluginManager.instantiate_tile(tid)
|
||||
if instance == null:
|
||||
continue
|
||||
|
||||
var pos: Dictionary = layout_data.get(tid, {})
|
||||
if pos.is_empty():
|
||||
# No saved position — skip
|
||||
instance.queue_free()
|
||||
continue
|
||||
|
||||
var col := clampi(pos.get("col", 0), 0, gs.x - 1)
|
||||
var row := clampi(pos.get("row", 0), 0, gs.y - 1)
|
||||
var w := clampi(pos.get("w", 1), 1, gs.x - col)
|
||||
var h := clampi(pos.get("h", 1), 1, gs.y - row)
|
||||
|
||||
# Only place if the cell is free
|
||||
var existing := grid.get_module_at(col, row)
|
||||
if existing != null:
|
||||
instance.queue_free()
|
||||
continue
|
||||
|
||||
grid.place_module(instance, col, row, w, h)
|
||||
_tile_instances.append(instance)
|
||||
_cache_tile_interval(instance)
|
||||
|
||||
|
||||
func _save_layout(_mod: Control = null, _a: int = 0, _b: int = 0) -> void:
|
||||
if not LayoutManager.has_method("set_tile_position"):
|
||||
return
|
||||
|
|
|
|||
195
scenes/plugin_settings_popup.gd
Normal file
195
scenes/plugin_settings_popup.gd
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
extends PopupPanel
|
||||
|
||||
## Dynamic settings popup for a specific plugin.
|
||||
## Touch-friendly: large close button, exclusive modal, ample control sizes.
|
||||
|
||||
const CLOSE_BTN_SIZE: Vector2 = Vector2(56, 56)
|
||||
const WINDOW_CORNER_RADIUS: float = 12.0
|
||||
|
||||
var _plugin_id: String = ""
|
||||
var _controls: Dictionary = {} # setting_key -> Control
|
||||
|
||||
|
||||
func setup(plugin_id: String) -> void:
|
||||
_plugin_id = plugin_id
|
||||
exclusive = true
|
||||
_build_ui()
|
||||
|
||||
|
||||
func _build_ui() -> void:
|
||||
# Window background with rounded corners
|
||||
var bg := StyleBoxFlat.new()
|
||||
bg.bg_color = Color(0.12, 0.12, 0.16, 1.0)
|
||||
bg.corner_radius_top_left = WINDOW_CORNER_RADIUS
|
||||
bg.corner_radius_top_right = WINDOW_CORNER_RADIUS
|
||||
bg.corner_radius_bottom_left = WINDOW_CORNER_RADIUS
|
||||
bg.corner_radius_bottom_right = WINDOW_CORNER_RADIUS
|
||||
add_theme_stylebox_override("panel", bg)
|
||||
|
||||
# Content area — leave right margin for the overlaid close button
|
||||
var mc := MarginContainer.new()
|
||||
mc.add_theme_constant_override("margin_left", 16)
|
||||
mc.add_theme_constant_override("margin_right", 16 + CLOSE_BTN_SIZE.x)
|
||||
mc.add_theme_constant_override("margin_top", 12)
|
||||
mc.add_theme_constant_override("margin_bottom", 12)
|
||||
add_child(mc)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
mc.add_child(vbox)
|
||||
|
||||
# Title bar
|
||||
var title_bar := HBoxContainer.new()
|
||||
var title_label := Label.new()
|
||||
var info: Dictionary = PluginManager.get_plugin(_plugin_id)
|
||||
title_label.text = "%s Settings" % info.get("name", _plugin_id)
|
||||
title_label.add_theme_font_size_override("font_size", 20)
|
||||
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
title_bar.add_child(title_label)
|
||||
vbox.add_child(title_bar)
|
||||
|
||||
vbox.add_child(HSeparator.new())
|
||||
|
||||
# Setting rows
|
||||
var settings: Array[Dictionary] = PluginManager.get_plugin_settings(_plugin_id)
|
||||
if settings.is_empty():
|
||||
var empty := Label.new()
|
||||
empty.text = "No configurable settings for this plugin."
|
||||
empty.modulate = Color(0.5, 0.5, 0.55)
|
||||
empty.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(empty)
|
||||
return
|
||||
|
||||
var inner := MarginContainer.new()
|
||||
inner.add_theme_constant_override("margin_left", 4)
|
||||
inner.add_theme_constant_override("margin_right", 4)
|
||||
inner.add_theme_constant_override("margin_top", 10)
|
||||
inner.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
vbox.add_child(inner)
|
||||
|
||||
var scroll := ScrollContainer.new()
|
||||
scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
inner.add_child(scroll)
|
||||
|
||||
var form := VBoxContainer.new()
|
||||
form.add_theme_constant_override("separation", 14)
|
||||
form.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
scroll.add_child(form)
|
||||
|
||||
for sd in settings:
|
||||
var key: String = sd.get("key", "")
|
||||
if key.is_empty():
|
||||
continue
|
||||
var label_text: String = sd.get("label", key)
|
||||
var stype: String = sd.get("type", "string")
|
||||
var default_val: Variant = sd.get("default", null)
|
||||
var current_val: Variant = PluginManager.get_plugin_setting(_plugin_id, key, default_val)
|
||||
|
||||
var row := HBoxContainer.new()
|
||||
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
var lbl := Label.new()
|
||||
lbl.text = label_text
|
||||
lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
lbl.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||
row.add_child(lbl)
|
||||
|
||||
var ctrl := _build_control(stype, key, current_val, sd)
|
||||
if ctrl != null:
|
||||
ctrl.custom_minimum_size = Vector2(180, 40)
|
||||
_controls[key] = ctrl
|
||||
row.add_child(ctrl)
|
||||
|
||||
form.add_child(row)
|
||||
|
||||
# Red corner close button (overlaid, anchored top-right, flush to edge)
|
||||
var close_button := Button.new()
|
||||
close_button.text = "✕"
|
||||
close_button.add_theme_font_size_override("font_size", 22)
|
||||
close_button.flat = true
|
||||
close_button.pressed.connect(_close)
|
||||
close_button.custom_minimum_size = CLOSE_BTN_SIZE
|
||||
|
||||
var btn_style := StyleBoxFlat.new()
|
||||
btn_style.bg_color = Color(1.0, 0.0, 0.0, 1.0)
|
||||
btn_style.corner_radius_top_right = WINDOW_CORNER_RADIUS
|
||||
btn_style.corner_radius_bottom_left = WINDOW_CORNER_RADIUS
|
||||
btn_style.corner_radius_top_left = 0
|
||||
btn_style.corner_radius_bottom_right = 0
|
||||
btn_style.set_corner_radius(Corner.CORNER_TOP_RIGHT, WINDOW_CORNER_RADIUS)
|
||||
btn_style.set_corner_radius(Corner.CORNER_BOTTOM_LEFT, WINDOW_CORNER_RADIUS)
|
||||
close_button.add_theme_stylebox_override("normal", btn_style)
|
||||
|
||||
var hover_style := StyleBoxFlat.new()
|
||||
hover_style.bg_color = Color(0.85, 0.0, 0.0, 1.0)
|
||||
hover_style.corner_radius_top_right = WINDOW_CORNER_RADIUS
|
||||
hover_style.corner_radius_bottom_left = WINDOW_CORNER_RADIUS
|
||||
hover_style.corner_radius_top_left = 0
|
||||
hover_style.corner_radius_bottom_right = 0
|
||||
close_button.add_theme_stylebox_override("hover", hover_style)
|
||||
|
||||
var pressed_style := StyleBoxFlat.new()
|
||||
pressed_style.bg_color = Color(0.7, 0.0, 0.0, 1.0)
|
||||
pressed_style.corner_radius_top_right = WINDOW_CORNER_RADIUS
|
||||
pressed_style.corner_radius_bottom_left = WINDOW_CORNER_RADIUS
|
||||
pressed_style.corner_radius_top_left = 0
|
||||
pressed_style.corner_radius_bottom_right = 0
|
||||
close_button.add_theme_stylebox_override("pressed", pressed_style)
|
||||
|
||||
close_button.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||
add_child(close_button)
|
||||
|
||||
|
||||
func _build_control(stype: String, key: String, val: Variant, defn: Dictionary) -> Control:
|
||||
match stype:
|
||||
"int":
|
||||
var sb := SpinBox.new()
|
||||
sb.min_value = defn.get("min", -999999)
|
||||
sb.max_value = defn.get("max", 999999)
|
||||
sb.step = 1
|
||||
sb.value = int(val) if val != null else int(defn.get("default", 0))
|
||||
sb.custom_minimum_size = Vector2(0, 40)
|
||||
sb.value_changed.connect(_on_value_changed.bind(key))
|
||||
return sb
|
||||
|
||||
"float":
|
||||
var sb := SpinBox.new()
|
||||
sb.min_value = defn.get("min", -999999.0)
|
||||
sb.max_value = defn.get("max", 999999.0)
|
||||
sb.step = defn.get("step", 0.1)
|
||||
sb.value = float(val) if val != null else float(defn.get("default", 0.0))
|
||||
sb.custom_minimum_size = Vector2(0, 40)
|
||||
sb.value_changed.connect(_on_value_changed.bind(key))
|
||||
return sb
|
||||
|
||||
"bool":
|
||||
var cb := CheckBox.new()
|
||||
cb.button_pressed = bool(val) if val != null else bool(defn.get("default", false))
|
||||
cb.custom_minimum_size = Vector2(0, 40)
|
||||
cb.toggled.connect(_on_toggled.bind(key))
|
||||
return cb
|
||||
|
||||
_:
|
||||
# string / default
|
||||
var le := LineEdit.new()
|
||||
le.text = str(val) if val != null else str(defn.get("default", ""))
|
||||
le.custom_minimum_size = Vector2(0, 40)
|
||||
le.text_changed.connect(_on_text_changed.bind(key))
|
||||
return le
|
||||
|
||||
|
||||
func _on_value_changed(value: float, key: String) -> void:
|
||||
PluginManager.set_plugin_setting(_plugin_id, key, value)
|
||||
|
||||
|
||||
func _on_toggled(checked: bool, key: String) -> void:
|
||||
PluginManager.set_plugin_setting(_plugin_id, key, checked)
|
||||
|
||||
|
||||
func _on_text_changed(text: String, key: String) -> void:
|
||||
PluginManager.set_plugin_setting(_plugin_id, key, text)
|
||||
|
||||
|
||||
func _close() -> void:
|
||||
queue_free()
|
||||
6
scenes/plugin_settings_popup.tscn
Normal file
6
scenes/plugin_settings_popup.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/plugin_settings_popup.gd" id="1"]
|
||||
|
||||
[node name="PluginSettingsPopup" type="PopupPanel"]
|
||||
script = ExtResource("1")
|
||||
316
scenes/settings_menu.gd
Normal file
316
scenes/settings_menu.gd
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
extends PopupPanel
|
||||
|
||||
## Settings menu with tabbed interface: General, Plugins, About.
|
||||
## Touch-friendly: large close button, ample spacing, exclusive modal.
|
||||
|
||||
const VERSION: String = "0.1.0-alpha"
|
||||
const CONTACT_EMAIL: String = "5d@fifthdread.com"
|
||||
|
||||
const CLOSE_BTN_SIZE: Vector2 = Vector2(56, 56)
|
||||
const WINDOW_CORNER_RADIUS: float = 12.0
|
||||
|
||||
var _tab_container: TabContainer
|
||||
var _close_button: Button
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
exclusive = true
|
||||
_build_ui()
|
||||
_popup_centered_clamp(Vector2i(720, 540))
|
||||
|
||||
|
||||
## Clamp popup to fit within screen, handling multi-monitor edge cases.
|
||||
func _popup_centered_clamp(min_size: Vector2i) -> void:
|
||||
popup_centered(min_size)
|
||||
# Ensure the popup doesn't extend past screen edges
|
||||
var screen_rect := DisplayServer.screen_get_usable_rect(
|
||||
DisplayServer.window_get_current_screen()
|
||||
)
|
||||
var max_pos := Vector2(
|
||||
screen_rect.position.x + screen_rect.size.x - size.x,
|
||||
screen_rect.position.y + screen_rect.size.y - size.y
|
||||
)
|
||||
position = Vector2i(
|
||||
mini(maxi(position.x, screen_rect.position.x), max_pos.x),
|
||||
mini(maxi(position.y, screen_rect.position.y), max_pos.y)
|
||||
)
|
||||
|
||||
|
||||
func _build_ui() -> void:
|
||||
# Window background with rounded corners
|
||||
var bg_style := StyleBoxFlat.new()
|
||||
bg_style.bg_color = Color(0.12, 0.12, 0.16, 1.0)
|
||||
bg_style.corner_radius_top_left = WINDOW_CORNER_RADIUS
|
||||
bg_style.corner_radius_top_right = WINDOW_CORNER_RADIUS
|
||||
bg_style.corner_radius_bottom_left = WINDOW_CORNER_RADIUS
|
||||
bg_style.corner_radius_bottom_right = WINDOW_CORNER_RADIUS
|
||||
add_theme_stylebox_override("panel", bg_style)
|
||||
|
||||
# Content area — leave right margin for the overlaid close button
|
||||
var mc := MarginContainer.new()
|
||||
mc.add_theme_constant_override("margin_left", 16)
|
||||
mc.add_theme_constant_override("margin_right", 16 + CLOSE_BTN_SIZE.x)
|
||||
mc.add_theme_constant_override("margin_top", 12)
|
||||
mc.add_theme_constant_override("margin_bottom", 12)
|
||||
add_child(mc)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
mc.add_child(vbox)
|
||||
|
||||
# Title bar (no close button — it's overlaid in the corner)
|
||||
var title_bar := HBoxContainer.new()
|
||||
var title_label := Label.new()
|
||||
title_label.text = "Settings"
|
||||
title_label.add_theme_font_size_override("font_size", 24)
|
||||
title_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
title_bar.add_child(title_label)
|
||||
vbox.add_child(title_bar)
|
||||
|
||||
# Tab container
|
||||
_tab_container = TabContainer.new()
|
||||
_tab_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
vbox.add_child(_tab_container)
|
||||
|
||||
# Build tabs
|
||||
_build_general_tab()
|
||||
_build_plugins_tab()
|
||||
_build_about_tab()
|
||||
|
||||
# Red corner close button (overlaid, anchored top-right, flush to edge)
|
||||
_close_button = Button.new()
|
||||
_close_button.text = "✕"
|
||||
_close_button.add_theme_font_size_override("font_size", 22)
|
||||
_close_button.flat = true
|
||||
_close_button.pressed.connect(_close)
|
||||
_close_button.custom_minimum_size = CLOSE_BTN_SIZE
|
||||
|
||||
var btn_style := StyleBoxFlat.new()
|
||||
btn_style.bg_color = Color(1.0, 0.0, 0.0, 1.0)
|
||||
btn_style.corner_radius_top_right = WINDOW_CORNER_RADIUS
|
||||
btn_style.corner_radius_bottom_left = WINDOW_CORNER_RADIUS
|
||||
btn_style.corner_radius_top_left = 0
|
||||
btn_style.corner_radius_bottom_right = 0
|
||||
btn_style.set_corner_radius(Corner.CORNER_TOP_RIGHT, WINDOW_CORNER_RADIUS)
|
||||
btn_style.set_corner_radius(Corner.CORNER_BOTTOM_LEFT, WINDOW_CORNER_RADIUS)
|
||||
_close_button.add_theme_stylebox_override("normal", btn_style)
|
||||
|
||||
var hover_style := StyleBoxFlat.new()
|
||||
hover_style.bg_color = Color(0.85, 0.0, 0.0, 1.0)
|
||||
hover_style.corner_radius_top_right = WINDOW_CORNER_RADIUS
|
||||
hover_style.corner_radius_bottom_left = WINDOW_CORNER_RADIUS
|
||||
hover_style.corner_radius_top_left = 0
|
||||
hover_style.corner_radius_bottom_right = 0
|
||||
_close_button.add_theme_stylebox_override("hover", hover_style)
|
||||
|
||||
var pressed_style := StyleBoxFlat.new()
|
||||
pressed_style.bg_color = Color(0.7, 0.0, 0.0, 1.0)
|
||||
pressed_style.corner_radius_top_right = WINDOW_CORNER_RADIUS
|
||||
pressed_style.corner_radius_bottom_left = WINDOW_CORNER_RADIUS
|
||||
pressed_style.corner_radius_top_left = 0
|
||||
pressed_style.corner_radius_bottom_right = 0
|
||||
_close_button.add_theme_stylebox_override("pressed", pressed_style)
|
||||
|
||||
_close_button.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||
add_child(_close_button)
|
||||
|
||||
|
||||
func _build_general_tab() -> void:
|
||||
var tab := VBoxContainer.new()
|
||||
tab.name = "General"
|
||||
|
||||
var inner := MarginContainer.new()
|
||||
inner.add_theme_constant_override("margin_left", 4)
|
||||
inner.add_theme_constant_override("margin_right", 4)
|
||||
inner.add_theme_constant_override("margin_top", 12)
|
||||
tab.add_child(inner)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.add_theme_constant_override("separation", 14)
|
||||
inner.add_child(vbox)
|
||||
|
||||
for i in range(1, 4):
|
||||
var row := HBoxContainer.new()
|
||||
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
var label := Label.new()
|
||||
label.text = "Setting %d:" % i
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_child(label)
|
||||
|
||||
var val := Label.new()
|
||||
val.text = "[configure %d]" % i
|
||||
val.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
||||
val.modulate = Color(0.6, 0.6, 0.65)
|
||||
val.custom_minimum_size = Vector2(200, 0)
|
||||
row.add_child(val)
|
||||
|
||||
vbox.add_child(row)
|
||||
|
||||
_tab_container.add_child(tab)
|
||||
|
||||
|
||||
func _build_plugins_tab() -> void:
|
||||
var tab := VBoxContainer.new()
|
||||
tab.name = "Plugins"
|
||||
|
||||
var inner := MarginContainer.new()
|
||||
inner.add_theme_constant_override("margin_left", 4)
|
||||
inner.add_theme_constant_override("margin_right", 4)
|
||||
inner.add_theme_constant_override("margin_top", 12)
|
||||
tab.add_child(inner)
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.add_theme_constant_override("separation", 10)
|
||||
inner.add_child(vbox)
|
||||
|
||||
# Header row
|
||||
var header := HBoxContainer.new()
|
||||
header.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
var hdr_name := Label.new()
|
||||
hdr_name.text = "Plugin"
|
||||
hdr_name.add_theme_font_size_override("font_size", 14)
|
||||
hdr_name.modulate = Color(0.6, 0.6, 0.65)
|
||||
hdr_name.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
header.add_child(hdr_name)
|
||||
|
||||
var hdr_ver := Label.new()
|
||||
hdr_ver.text = "Version"
|
||||
hdr_ver.add_theme_font_size_override("font_size", 14)
|
||||
hdr_ver.modulate = Color(0.6, 0.6, 0.65)
|
||||
hdr_ver.custom_minimum_size = Vector2(80, 0)
|
||||
header.add_child(hdr_ver)
|
||||
|
||||
var hdr_active := Label.new()
|
||||
hdr_active.text = "Active"
|
||||
hdr_active.add_theme_font_size_override("font_size", 14)
|
||||
hdr_active.modulate = Color(0.5, 0.5, 0.55)
|
||||
hdr_active.custom_minimum_size = Vector2(60, 0)
|
||||
hdr_active.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
header.add_child(hdr_active)
|
||||
|
||||
vbox.add_child(header)
|
||||
vbox.add_child(HSeparator.new())
|
||||
|
||||
# Plugin rows
|
||||
if not PluginManager.has_method("get_plugin_ids"):
|
||||
_tab_container.add_child(tab)
|
||||
return
|
||||
|
||||
var plugin_ids: PackedStringArray = PluginManager.get_plugin_ids()
|
||||
if plugin_ids.is_empty():
|
||||
var empty := Label.new()
|
||||
empty.text = "No plugins loaded."
|
||||
empty.modulate = Color(0.5, 0.5, 0.55)
|
||||
empty.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
vbox.add_child(empty)
|
||||
_tab_container.add_child(tab)
|
||||
return
|
||||
|
||||
for pid in plugin_ids:
|
||||
var info: Dictionary = PluginManager.get_plugin(pid)
|
||||
var setting_defs := PluginManager.get_plugin_settings(pid) if PluginManager.has_method("get_plugin_settings") else []
|
||||
var row := HBoxContainer.new()
|
||||
row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_theme_constant_override("separation", 8)
|
||||
|
||||
var name_lbl := Label.new()
|
||||
name_lbl.text = info.get("name", pid)
|
||||
name_lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
row.add_child(name_lbl)
|
||||
|
||||
var ver_lbl := Label.new()
|
||||
ver_lbl.text = "v%s" % info.get("version", "?")
|
||||
ver_lbl.custom_minimum_size = Vector2(80, 0)
|
||||
row.add_child(ver_lbl)
|
||||
|
||||
var cb := CheckBox.new()
|
||||
cb.button_pressed = PluginManager.is_plugin_active(pid)
|
||||
cb.toggled.connect(_on_plugin_toggled.bind(pid))
|
||||
cb.custom_minimum_size = Vector2(60, 48)
|
||||
cb.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
row.add_child(cb)
|
||||
|
||||
if not setting_defs.is_empty():
|
||||
var cog := Button.new()
|
||||
cog.text = "⚙"
|
||||
cog.flat = true
|
||||
cog.tooltip_text = "Settings for %s" % info.get("name", pid)
|
||||
cog.custom_minimum_size = Vector2(44, 44)
|
||||
cog.add_theme_font_size_override("font_size", 20)
|
||||
cog.pressed.connect(_on_plugin_settings_pressed.bind(pid))
|
||||
row.add_child(cog)
|
||||
|
||||
vbox.add_child(row)
|
||||
|
||||
_tab_container.add_child(tab)
|
||||
|
||||
|
||||
func _on_plugin_toggled(checked: bool, plugin_id: String) -> void:
|
||||
if PluginManager.has_method("set_plugin_active"):
|
||||
PluginManager.set_plugin_active(plugin_id, checked)
|
||||
|
||||
|
||||
func _on_plugin_settings_pressed(plugin_id: String) -> void:
|
||||
var popup := preload("res://scenes/plugin_settings_popup.tscn").instantiate()
|
||||
popup.setup(plugin_id)
|
||||
add_sibling(popup)
|
||||
popup.popup_centered(Vector2i(720, 540))
|
||||
|
||||
|
||||
func _build_about_tab() -> void:
|
||||
var tab := VBoxContainer.new()
|
||||
tab.name = "About"
|
||||
|
||||
var center := VBoxContainer.new()
|
||||
center.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
center.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
center.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
tab.add_child(center)
|
||||
|
||||
# Logo
|
||||
var tex := load("res://assets/icons/icon.svg")
|
||||
if tex != null:
|
||||
var logo := TextureRect.new()
|
||||
logo.texture = tex
|
||||
logo.custom_minimum_size = Vector2(80, 80)
|
||||
logo.expand_mode = TextureRect.EXPAND_FIT_WIDTH
|
||||
logo.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
logo.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
center.add_child(logo)
|
||||
|
||||
center.add_spacer(false)
|
||||
|
||||
var name_lbl := Label.new()
|
||||
name_lbl.text = "V Panel"
|
||||
name_lbl.add_theme_font_size_override("font_size", 28)
|
||||
name_lbl.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
center.add_child(name_lbl)
|
||||
|
||||
center.add_spacer(false)
|
||||
|
||||
var ver_lbl := Label.new()
|
||||
ver_lbl.text = "Version %s" % VERSION
|
||||
ver_lbl.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
center.add_child(ver_lbl)
|
||||
|
||||
var contact_lbl := Label.new()
|
||||
contact_lbl.text = "Contact: %s" % CONTACT_EMAIL
|
||||
contact_lbl.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
center.add_child(contact_lbl)
|
||||
|
||||
center.add_spacer(false)
|
||||
|
||||
var desc_lbl := Label.new()
|
||||
desc_lbl.text = "A real-time system status monitor built with Godot 4"
|
||||
desc_lbl.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
desc_lbl.modulate = Color(0.6, 0.6, 0.65)
|
||||
center.add_child(desc_lbl)
|
||||
|
||||
_tab_container.add_child(tab)
|
||||
|
||||
|
||||
func _close() -> void:
|
||||
queue_free()
|
||||
6
scenes/settings_menu.tscn
Normal file
6
scenes/settings_menu.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/settings_menu.gd" id="1"]
|
||||
|
||||
[node name="SettingsMenu" type="PopupPanel"]
|
||||
script = ExtResource("1")
|
||||
Loading…
Add table
Add a link
Reference in a new issue