V-Panel/scenes/settings_menu.gd

317 lines
10 KiB
GDScript3
Raw Normal View History

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()