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