From ebf97a6fc8a0cb74da4021c305121b88639b441f Mon Sep 17 00:00:00 2001 From: Eric Smith <5d@fifthdread.com> Date: Thu, 21 May 2026 09:19:29 -0400 Subject: [PATCH] redesign tile action menu with toolbar and submenus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - replace old PopupMenu (flat preset list) with PopupPanel-based menu - add 3-icon toolbar: ⚙ Settings, ℹ Info, ✕ Remove (+) + Add - red ✕ on tiles removes the tile; green + on empty space opens add-tile submenu - 'Themes ▸' button opens a submenu with all ShaderPresets - add-tile submenu populates from PluginManager.get_all_tile_defs() - all toolbar buttons are 48×48 flat buttons with 24px font for touch - orphan PopupPanel children cleaned up during grid rebuild --- config/layouts/Main.cfg | 10 +-- project.godot | 3 +- scripts/dashboard_grid.gd | 179 +++++++++++++++++++++++++++++++++----- 3 files changed, 164 insertions(+), 28 deletions(-) diff --git a/config/layouts/Main.cfg b/config/layouts/Main.cfg index 667d41d..b01fcbd 100644 --- a/config/layouts/Main.cfg +++ b/config/layouts/Main.cfg @@ -1,7 +1,7 @@ [layout] name="Main" -saved_at="2026-05-21T09:09:37" +saved_at="2026-05-21T09:16:49" [tiles] @@ -10,7 +10,7 @@ count=3 [tile_0] id="system_monitor/cpu" -col=0 +col=1 row=0 w=1 h=1 @@ -18,7 +18,7 @@ h=1 [tile_1] id="system_monitor/memory" -col=1 +col=2 row=0 w=1 h=1 @@ -26,7 +26,7 @@ h=1 [tile_2] id="system_monitor/testing" -col=2 +col=0 row=0 w=1 -h=2 +h=3 diff --git a/project.godot b/project.godot index bfe107d..d84695e 100644 --- a/project.godot +++ b/project.godot @@ -32,4 +32,5 @@ window/stretch/mode="viewport" [rendering] -renderer/rendering_method.mobile="forward_plus" +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/scripts/dashboard_grid.gd b/scripts/dashboard_grid.gd index 8233ba1..558f29b 100644 --- a/scripts/dashboard_grid.gd +++ b/scripts/dashboard_grid.gd @@ -92,7 +92,7 @@ func _on_gui_input(event: InputEvent) -> void: var mb: InputEventMouseButton = event if mb.button_index == MOUSE_BUTTON_LEFT and mb.double_click and mb.pressed: # Double-click still works for mouse users - _show_preset_popup(mb.position) + _show_tile_menu(mb.position) elif mb.button_index == MOUSE_BUTTON_LEFT and not mb.double_click: if mb.pressed: # Try resize edge first (instant, no long-press) @@ -120,33 +120,168 @@ func _on_gui_input(event: InputEvent) -> void: _update_hover_cursor(mm.position) -func _show_preset_popup(mouse_pos: Vector2) -> void: - var cell_pos := _cell_at_position(mouse_pos) - if cell_pos.x < 0 or cell_pos.y < 0: - return - var module := _get_module_at(cell_pos.x, cell_pos.y) - if module == null: - return - # Don't show popup if already dragging or resizing +const MENU_BUTTON_MIN_SIZE: Vector2 = Vector2(48, 48) +const MENU_BUTTON_FONT_SIZE: int = 24 + + +# -------------------------------------------------------------------------- +# Tile action menu (replaces old preset-only popup) +# -------------------------------------------------------------------------- + +func _show_tile_menu(mouse_pos: Vector2) -> void: if _is_dragging or _is_resizing: return - var popup := PopupMenu.new() - popup.name = "PresetPopup" + var cell_pos := _cell_at_position(mouse_pos) + if cell_pos.x < 0 or cell_pos.y < 0: + return + + var module := _get_module_at(cell_pos.x, cell_pos.y) + + var panel := PopupPanel.new() + panel.name = "TileMenu" + panel.position = Vector2i(get_screen_position()) + Vector2i(mouse_pos) + add_child(panel) + + var margin := MarginContainer.new() + margin.add_theme_constant_override("margin_left", 8) + margin.add_theme_constant_override("margin_right", 8) + margin.add_theme_constant_override("margin_top", 8) + margin.add_theme_constant_override("margin_bottom", 8) + panel.add_child(margin) + + var vbox := VBoxContainer.new() + vbox.add_theme_constant_override("separation", 6) + margin.add_child(vbox) + + # -- toolbar row -- + var toolbar := HBoxContainer.new() + toolbar.add_theme_constant_override("separation", 6) + toolbar.alignment = BoxContainer.ALIGNMENT_CENTER + vbox.add_child(toolbar) + + var settings_btn := _make_menu_button("⚙", "Settings") + toolbar.add_child(settings_btn) + + var info_btn := _make_menu_button("ℹ", "Info") + toolbar.add_child(info_btn) + + if module != null: + # On a tile: show remove button (red X) + var remove_btn := _make_menu_button("✕", "Remove Tile") + remove_btn.modulate = Color(1.0, 0.25, 0.25, 1.0) + remove_btn.pressed.connect(_remove_tile.bind(module, panel)) + toolbar.add_child(remove_btn) + else: + # On empty space: show add button (green +) + var add_btn := _make_menu_button("+", "Add Tile") + add_btn.modulate = Color(0.2, 1.0, 0.2, 1.0) + add_btn.pressed.connect(_show_add_tile_submenu.bind(add_btn, panel)) + toolbar.add_child(add_btn) + + # -- Themes button (only on a tile) -- + if module != null: + var themes_btn := Button.new() + themes_btn.text = "Themes ▸" + themes_btn.flat = true + themes_btn.size_flags_horizontal = Control.SIZE_SHRINK_CENTER + themes_btn.pressed.connect(_show_themes_submenu.bind(themes_btn, module, panel)) + vbox.add_child(themes_btn) + + panel.popup() + + +func _make_menu_button(text: String, tooltip: String) -> Button: + var btn := Button.new() + btn.text = text + btn.tooltip_text = tooltip + btn.flat = true + btn.custom_min_size = MENU_BUTTON_MIN_SIZE + btn.add_theme_font_size_override("font_size", MENU_BUTTON_FONT_SIZE) + return btn + + +func _show_themes_submenu(parent_btn: Button, module: Control, panel: PopupPanel) -> void: + var submenu := PopupMenu.new() + submenu.name = "ThemeSubmenu" var names := ShaderPresets.get_preset_names() for i in names.size(): - popup.add_item(names[i], i) - popup.position = Vector2i(get_screen_position()) + Vector2i(mouse_pos) - add_child(popup) - popup.popup() + submenu.add_item(names[i], i) - var on_select := func(id: int): + # Position the submenu to the right of the parent button + var btn_global: Vector2 = parent_btn.global_position + Vector2(parent_btn.size.x, 0.0) + submenu.position = Vector2i(btn_global) + add_child(submenu) + submenu.popup() + + submenu.id_pressed.connect(func(id: int): if id >= 0 and id < names.size(): ShaderPresets.apply_preset(module, names[id]) - if is_instance_valid(popup): - popup.queue_free() + if is_instance_valid(submenu): + submenu.queue_free() + if is_instance_valid(panel): + panel.queue_free() + ) - popup.id_pressed.connect(on_select) + +func _show_add_tile_submenu(parent_btn: Button, panel: PopupPanel) -> void: + if not PluginManager.has_method("get_all_tile_defs"): + return + + var defs: Array[Dictionary] = PluginManager.get_all_tile_defs() + if defs.is_empty(): + return + + var submenu := PopupMenu.new() + submenu.name = "AddTileSubmenu" + for i in defs.size(): + var label: String = defs[i].get("name", defs[i].get("id", "Tile %d" % i)) + submenu.add_item(label, i) + + var btn_global: Vector2 = parent_btn.global_position + Vector2(parent_btn.size.x, 0.0) + submenu.position = Vector2i(btn_global) + add_child(submenu) + submenu.popup() + + submenu.id_pressed.connect(func(id: int): + if id >= 0 and id < defs.size(): + _add_tile_from_def(defs[id]) + if is_instance_valid(submenu): + submenu.queue_free() + if is_instance_valid(panel): + panel.queue_free() + ) + + +func _remove_tile(module: Control, panel: PopupPanel) -> void: + remove_module(module) + if is_instance_valid(panel): + panel.queue_free() + + +func _add_tile_from_def(def: Dictionary) -> void: + if not PluginManager.has_method("instantiate_tile"): + return + var tid: String = def.get("id", "") + if tid.is_empty(): + return + var instance := PluginManager.instantiate_tile(tid) + if instance == null: + return + + # Find a free position at the end of the grid + var gs := get_grid_size() + var target_col := 0 + var target_row := gs.y + # Try current row first + var dw: int = def.get("default_w", 1) + var dh: int = def.get("default_h", 1) + for c in range(gs.x): + if _span_fits(c, target_row, dw, dh, null): + target_col = c + break + + place_module(instance, target_col, target_row, dw, dh) # -------------------------------------------------------------------------- @@ -177,7 +312,7 @@ func _cancel_long_press() -> void: func _on_long_press_timeout() -> void: _long_press_active = false _long_press_timer = null - _show_preset_popup(_long_press_pos) + _show_tile_menu(_long_press_pos) # -------------------------------------------------------------------------- @@ -665,9 +800,9 @@ func _rebuild_grid() -> void: _cancel_drag() if _is_resizing: _end_resize(Vector2.ZERO) - # Clean up orphaned popups from preset selection + # Clean up orphaned popups from tile menu for child in get_children(): - if child is PopupMenu: + if child is PopupMenu or child is PopupPanel: child.queue_free() var avail := size - Vector2(margin * 2.0, margin * 2.0)