Added a dynamic layout system where UI elements can be moved about on a tiled grid.

This commit is contained in:
RedNicStone committed 2026-11-09 18:14:52 +01:00
1 parent ea6ba54666
commit 26c29df4ad
34 files changed
+1269

No files matched your search

+135
View File
@@ -0,0 +1,135 @@
extends Control
signal overview_changed(active: bool)
@export var grid_size := Vector2i(32, 18):
set(value):
grid_size = Vector2i(maxi(1, value.x), maxi(1, value.y))
if is_node_ready():
_update_grid()
@export var monitor_boundary := 0
@export_range(0.5, 0.95) var overview_scale := 0.82
@export var transition_seconds := 0.22
@export var hot_corner_enabled := true
@export var corner_size := 20.0
@export var corner_dwell := 0.18
var overview_active := false
var _pointer_in_window := false
var _corner_hover := false
var _corner_armed := true
var _dwell := 0.0
var _tween: Tween
@onready var dashboard: Control = $Dashboard
@onready var grid: Control = $Dashboard/Grid
@onready var canvas: Control = $Dashboard/Canvas
func _ready() -> void:
grid.gui_input.connect(canvas.handle_grid_input)
$Toolbar/Add.pressed.connect(func() -> void: $Library.visible = not $Library.visible)
$Library.element_requested.connect(_add_element)
canvas.layout_saved.connect(func() -> void: $Toolbar/Hint.text = "Esc to return")
canvas.load_failed.connect(func() -> void: $Toolbar/Hint.text = "Could not load saved layout")
$Toolbar/Reset.pressed.connect(canvas.reset_layout)
canvas.save_failed.connect(func() -> void: $Toolbar/Hint.text = "Could not save layout")
$Toolbar/Return.pressed.connect(func() -> void: set_overview(false))
resized.connect(_resize)
get_window().mouse_entered.connect(func() -> void: _pointer_in_window = true)
get_window().mouse_exited.connect(_leave_corner)
theme_changed.connect(queue_redraw)
_update_grid()
canvas.load_layout()
_resize()
func _input(event: InputEvent) -> void:
if event is InputEventMouseMotion and hot_corner_enabled:
_pointer_in_window = true
_update_corner(get_window().get_final_transform() * event.position)
elif overview_active and event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
if $Library.visible:
$Library.hide()
else:
set_overview(false)
get_viewport().set_input_as_handled()
elif overview_active and event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
var pointer: Vector2 = get_global_transform_with_canvas().affine_inverse() * event.position
var in_library: bool = $Library.visible and $Library.get_global_rect().has_point(event.position)
if not in_library and not preview_rect().has_point(pointer) and not $Toolbar.get_global_rect().has_point(event.position):
set_overview(false)
get_viewport().set_input_as_handled()
func _process(delta: float) -> void:
var window := get_window()
# Letterbox regions may not send canvas mouse-motion events.
if hot_corner_enabled and window.visible and (_pointer_in_window or window.has_focus()):
_update_corner(window.get_final_transform() * window.get_mouse_position())
else:
_leave_corner()
if hot_corner_enabled and _corner_hover and _corner_armed and not overview_active:
_dwell += delta
if _dwell >= corner_dwell:
_corner_armed = false
set_overview(true)
func _update_corner(window_position: Vector2) -> void:
_corner_hover = Rect2(Vector2.ZERO, Vector2.ONE * corner_size).has_point(window_position)
if not _corner_hover:
_corner_armed = true
_dwell = 0
func _leave_corner() -> void:
_pointer_in_window = false
_corner_hover = false
_dwell = 0
func set_overview(active: bool) -> void:
if overview_active == active:
return
if not active:
$Library.hide()
canvas.set_edit_mode(active)
overview_active = active
_corner_armed = false
_dwell = 0
if is_instance_valid(_tween):
_tween.kill()
grid.visible = active
$Toolbar.visible = active
queue_redraw()
var target := Vector2.ONE * overview_scale if active else Vector2.ONE
if transition_seconds <= 0:
dashboard.scale = target
else:
_tween = create_tween().set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
_tween.tween_property(dashboard, "scale", target, transition_seconds)
overview_changed.emit(active)
func _add_element(element_type: String) -> void:
if canvas.add_element(element_type) == null:
$Library.show_no_space()
else:
$Library.hide()
func preview_rect() -> Rect2:
return Rect2((size - size * dashboard.scale) / 2, size * dashboard.scale)
func _resize() -> void:
_dwell = 0
if is_instance_valid(_tween):
_tween.kill()
dashboard.pivot_offset = size / 2
dashboard.scale = Vector2.ONE * overview_scale if overview_active else Vector2.ONE
queue_redraw()
func _update_grid() -> void:
canvas.grid_size = grid_size
canvas.arrange_items()
grid.grid_size = grid_size
grid.monitor_boundary = monitor_boundary
grid.queue_redraw()
$Toolbar/Title.text = "LAYOUT OVERVIEW %d × %d" % [grid_size.x, grid_size.y]
func _draw() -> void:
if overview_active:
draw_rect(Rect2(Vector2.ZERO, size), get_theme_color("instrument_empty", "SurfaceSide"))