extends Control signal overview_changed(active: bool) const FOCUS_SCOPES = preload("res://scripts/selectable_container.gd") @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 FOCUS_SCOPES.dispatch_focus_shortcut(event, get_tree()): get_viewport().set_input_as_handled() return 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"))