- 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
316 lines
10 KiB
GDScript
316 lines
10 KiB
GDScript
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()
|