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
|
|
@ -7,10 +7,12 @@ signal module_placed(module: Control, col: int, row: int)
|
|||
signal module_removed(module: Control)
|
||||
signal module_resized(module: Control, col: int, row: int)
|
||||
|
||||
@export var cell_min_size: Vector2 = Vector2(300, 240)
|
||||
@export var grid_columns: int = 4
|
||||
@export var grid_rows: int = 3
|
||||
@export var cell_spacing: float = 8.0
|
||||
@export var margin: float = 16.0
|
||||
|
||||
## Internal grid dimensions (synced to grid_columns / grid_rows on rebuild).
|
||||
var columns: int = 0
|
||||
var rows: int = 0
|
||||
|
||||
|
|
@ -49,6 +51,10 @@ var _resize_preview: ColorRect = null # ghost showing new size
|
|||
var _cell_w: float = 1.0
|
||||
var _cell_h: float = 1.0
|
||||
|
||||
# Grid origin offset (centered within the control area)
|
||||
var _base_x: float = 0.0
|
||||
var _base_y: float = 0.0
|
||||
|
||||
# Resize edge detection threshold (pixels)
|
||||
const RESIZE_THRESHOLD: float = 10.0
|
||||
const EDGE_LEFT: int = 1
|
||||
|
|
@ -161,6 +167,7 @@ func _show_tile_menu(mouse_pos: Vector2) -> void:
|
|||
vbox.add_child(toolbar)
|
||||
|
||||
var settings_btn := _make_menu_button("⚙", "Settings")
|
||||
settings_btn.pressed.connect(_open_settings.bind(panel))
|
||||
toolbar.add_child(settings_btn)
|
||||
|
||||
var info_btn := _make_menu_button("ℹ", "Info")
|
||||
|
|
@ -269,19 +276,36 @@ func _add_tile_from_def(def: Dictionary) -> void:
|
|||
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)
|
||||
# Find a free position within the grid bounds
|
||||
for r in range(grid_rows):
|
||||
for c in range(grid_columns):
|
||||
if _span_fits(c, r, dw, dh, null):
|
||||
place_module(instance, c, r, dw, dh)
|
||||
return
|
||||
|
||||
# No room — try shrinking span to 1x1
|
||||
if dw > 1 or dh > 1:
|
||||
for r in range(grid_rows):
|
||||
for c in range(grid_columns):
|
||||
if _span_fits(c, r, 1, 1, null):
|
||||
place_module(instance, c, r, 1, 1)
|
||||
return
|
||||
|
||||
# Grid completely full — queue the instance for cleanup
|
||||
if is_instance_valid(instance):
|
||||
instance.queue_free()
|
||||
|
||||
|
||||
func _open_settings(menu_panel: PopupPanel) -> void:
|
||||
if is_instance_valid(menu_panel):
|
||||
menu_panel.queue_free()
|
||||
var settings: PopupPanel = load("res://scenes/settings_menu.tscn").instantiate()
|
||||
add_child(settings)
|
||||
settings.exclusive = true
|
||||
settings.popup_centered(Vector2i(720, 540))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
|
|
@ -400,17 +424,16 @@ func _clear_occupancy(col: int, row: int, w: int, h: int) -> void:
|
|||
|
||||
|
||||
func _ensure_occupancy_for_span(col: int, row: int, w: int, h: int) -> void:
|
||||
var needed_rows := row + h
|
||||
var needed_cols := col + w
|
||||
var needed_rows := mini(row + h, grid_rows)
|
||||
var needed_cols := mini(col + w, grid_columns)
|
||||
while _grid.size() < needed_rows:
|
||||
_grid.append([])
|
||||
for r in range(_grid.size()):
|
||||
while _grid[r].size() < needed_cols:
|
||||
_grid[r].append(null)
|
||||
if columns < needed_cols:
|
||||
columns = needed_cols
|
||||
if rows < needed_rows:
|
||||
rows = needed_rows
|
||||
# Never expand beyond the configured fixed grid
|
||||
columns = grid_columns
|
||||
rows = grid_rows
|
||||
|
||||
|
||||
func _is_valid_cell(col: int, row: int) -> bool:
|
||||
|
|
@ -442,10 +465,8 @@ func _cell_at_position(pos: Vector2) -> Vector2i:
|
|||
if columns == 0 or rows == 0 or _cell_w <= 0 or _cell_h <= 0:
|
||||
return Vector2i(-1, -1)
|
||||
|
||||
var inner_x := margin + cell_spacing
|
||||
var inner_y := margin + cell_spacing
|
||||
var col := int((pos.x - inner_x) / (_cell_w + cell_spacing))
|
||||
var row := int((pos.y - inner_y) / (_cell_h + cell_spacing))
|
||||
var col := int((pos.x - _base_x) / (_cell_w + cell_spacing))
|
||||
var row := int((pos.y - _base_y) / (_cell_h + cell_spacing))
|
||||
col = clampi(col, 0, columns - 1)
|
||||
row = clampi(row, 0, rows - 1)
|
||||
return Vector2i(col, row)
|
||||
|
|
@ -758,8 +779,8 @@ func _finish_drag() -> void:
|
|||
# --------------------------------------------------------------------------
|
||||
|
||||
func _get_module_rect(col: int, row: int, w: int, h: int) -> Rect2:
|
||||
var x := margin + cell_spacing + col * (_cell_w + cell_spacing)
|
||||
var y := margin + cell_spacing + row * (_cell_h + cell_spacing)
|
||||
var x := _base_x + col * (_cell_w + cell_spacing)
|
||||
var y := _base_y + row * (_cell_h + cell_spacing)
|
||||
var mw := w * _cell_w + (w - 1) * cell_spacing
|
||||
var mh := h * _cell_h + (h - 1) * cell_spacing
|
||||
return Rect2(x, y, mw, mh)
|
||||
|
|
@ -805,48 +826,16 @@ func _rebuild_grid() -> void:
|
|||
if child is PopupMenu or child is PopupPanel:
|
||||
child.queue_free()
|
||||
|
||||
var avail := size - Vector2(margin * 2.0, margin * 2.0)
|
||||
|
||||
var new_cols: int
|
||||
var new_rows: int
|
||||
|
||||
if avail.x < cell_min_size.x or avail.y < cell_min_size.y:
|
||||
if _cells.size() > 0:
|
||||
_layout_cells()
|
||||
return
|
||||
new_cols = 1
|
||||
new_rows = 1
|
||||
if new_cols == columns and new_rows == rows and _cells.size() > 0:
|
||||
_layout_cells()
|
||||
return
|
||||
columns = new_cols
|
||||
rows = new_rows
|
||||
# If grid dimensions changed, rebuild cells and re-place modules
|
||||
if columns != grid_columns or rows != grid_rows:
|
||||
columns = grid_columns
|
||||
rows = grid_rows
|
||||
_save_modules()
|
||||
_teardown_cells()
|
||||
_build_cells()
|
||||
_restore_modules()
|
||||
else:
|
||||
_layout_cells()
|
||||
return
|
||||
|
||||
new_cols = maxi(1, int(avail.x / (cell_min_size.x + cell_spacing)))
|
||||
|
||||
var module_count := _module_list.size()
|
||||
var min_rows := maxi(1, ceili(float(module_count) / float(new_cols)))
|
||||
var avail_rows := maxi(1, int(avail.y / (cell_min_size.y + cell_spacing)))
|
||||
new_rows = maxi(avail_rows, min_rows)
|
||||
|
||||
if new_cols == columns and new_rows == rows and _cells.size() > 0:
|
||||
_layout_cells()
|
||||
return
|
||||
|
||||
columns = new_cols
|
||||
rows = new_rows
|
||||
|
||||
_save_modules()
|
||||
_teardown_cells()
|
||||
_build_cells()
|
||||
_restore_modules()
|
||||
_layout_cells()
|
||||
|
||||
|
||||
func _cancel_drag() -> void:
|
||||
|
|
@ -955,12 +944,23 @@ func _layout_cells() -> void:
|
|||
return
|
||||
_ensure_cells_match_grid()
|
||||
|
||||
var inner_w := maxf(0.0, size.x - margin * 2.0 - cell_spacing)
|
||||
var inner_h := maxf(0.0, size.y - margin * 2.0 - cell_spacing)
|
||||
var gap_x := cell_spacing * (columns - 1)
|
||||
var gap_y := cell_spacing * (rows - 1)
|
||||
_cell_w = maxf(1.0, (inner_w - gap_x) / columns)
|
||||
_cell_h = maxf(1.0, (inner_h - gap_y) / rows)
|
||||
var gap_x := cell_spacing * maxi(0, columns - 1)
|
||||
var gap_y := cell_spacing * maxi(0, rows - 1)
|
||||
|
||||
# Compute square cell size that fits within the available area
|
||||
var avail_x := maxf(0.0, size.x - margin * 2.0)
|
||||
var avail_y := maxf(0.0, size.y - margin * 2.0)
|
||||
var cell_w_from_x := maxf(1.0, (avail_x - gap_x) / columns)
|
||||
var cell_h_from_y := maxf(1.0, (avail_y - gap_y) / rows)
|
||||
var cell_size := minf(cell_w_from_x, cell_h_from_y)
|
||||
_cell_w = cell_size
|
||||
_cell_h = cell_size
|
||||
|
||||
# Center the grid within the available space
|
||||
var grid_w := columns * _cell_w + gap_x
|
||||
var grid_h := rows * _cell_h + gap_y
|
||||
_base_x = margin + maxf(0.0, (size.x - margin * 2.0 - grid_w) / 2.0)
|
||||
_base_y = margin + maxf(0.0, (size.y - margin * 2.0 - grid_h) / 2.0)
|
||||
|
||||
# Position cells
|
||||
for row in range(rows):
|
||||
|
|
@ -970,8 +970,8 @@ func _layout_cells() -> void:
|
|||
var cell: PanelContainer = _cells[row][col]
|
||||
if not is_instance_valid(cell):
|
||||
continue
|
||||
var x := margin + cell_spacing + col * (_cell_w + cell_spacing)
|
||||
var y := margin + cell_spacing + row * (_cell_h + cell_spacing)
|
||||
var x := _base_x + col * (_cell_w + cell_spacing)
|
||||
var y := _base_y + row * (_cell_h + cell_spacing)
|
||||
cell.set_position(Vector2(x, y))
|
||||
cell.set_size(Vector2(_cell_w, _cell_h))
|
||||
|
||||
|
|
@ -1057,31 +1057,39 @@ func _ensure_occupancy_from_grid() -> void:
|
|||
if _grid[r][c] != null:
|
||||
max_c = maxi(max_c, c + 1)
|
||||
max_r = maxi(max_r, r + 1)
|
||||
columns = maxi(columns, max_c)
|
||||
rows = maxi(rows, max_r)
|
||||
# Clamp to fixed grid — never expand beyond configured size
|
||||
columns = clampi(maxi(columns, max_c), 1, grid_columns)
|
||||
rows = clampi(maxi(rows, max_r), 1, grid_rows)
|
||||
|
||||
|
||||
func _find_and_place(module: Control) -> void:
|
||||
var d := _get_module_grid_data(module)
|
||||
# Try the desired position first
|
||||
if _span_fits(d.col, d.row, d.w, d.h, null):
|
||||
_ensure_occupancy_for_span(d.col, d.row, d.w, d.h)
|
||||
_occupy_cells(module, d.col, d.row, d.w, d.h)
|
||||
# Clamp span to grid bounds
|
||||
var sw := mini(d.w, columns)
|
||||
var sh := mini(d.h, rows)
|
||||
|
||||
# Try the desired position first (clamped)
|
||||
var try_col := clampi(d.col, 0, columns - sw)
|
||||
var try_row := clampi(d.row, 0, rows - sh)
|
||||
if _span_fits(try_col, try_row, sw, sh, null):
|
||||
_set_module_grid_data(module, try_col, try_row, sw, sh)
|
||||
_ensure_occupancy_for_span(try_col, try_row, sw, sh)
|
||||
_occupy_cells(module, try_col, try_row, sw, sh)
|
||||
return
|
||||
|
||||
# Find first available position
|
||||
# Find first available position within bounds
|
||||
for r in range(rows):
|
||||
for c in range(columns):
|
||||
if _span_fits(c, r, d.w, d.h, null):
|
||||
_set_module_grid_data(module, c, r, d.w, d.h)
|
||||
_ensure_occupancy_for_span(c, r, d.w, d.h)
|
||||
_occupy_cells(module, c, r, d.w, d.h)
|
||||
var fit_sw := mini(sw, columns - c)
|
||||
var fit_sh := mini(sh, rows - r)
|
||||
if _span_fits(c, r, fit_sw, fit_sh, null):
|
||||
_set_module_grid_data(module, c, r, fit_sw, fit_sh)
|
||||
_ensure_occupancy_for_span(c, r, fit_sw, fit_sh)
|
||||
_occupy_cells(module, c, r, fit_sw, fit_sh)
|
||||
return
|
||||
|
||||
# No room — expand grid
|
||||
var new_col := columns
|
||||
var new_row := 0
|
||||
_ensure_occupancy_for_span(new_col, new_row, d.w, d.h)
|
||||
columns = new_col + d.w
|
||||
_set_module_grid_data(module, new_col, new_row, d.w, d.h)
|
||||
_occupy_cells(module, new_col, new_row, d.w, d.h)
|
||||
# No room — place at (0,0), nudging whatever is there
|
||||
_clear_occupancy(0, 0, sw, sh)
|
||||
_set_module_grid_data(module, 0, 0, sw, sh)
|
||||
_ensure_occupancy_for_span(0, 0, sw, sh)
|
||||
_occupy_cells(module, 0, 0, sw, sh)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue