135 lines
4.7 KiB
GDScript
135 lines
4.7 KiB
GDScript
extends Node
|
|
|
|
signal interaction_started
|
|
|
|
const BASE_Z := 100
|
|
const Z_STEP := 32
|
|
var _windows: Array[Dictionary] = []
|
|
var _active: Control
|
|
var _dragged: Control
|
|
var _drag_offset := Vector2.ZERO
|
|
var modal_overlay: Control
|
|
|
|
func _ready() -> void:
|
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
|
get_viewport().size_changed.connect(_clamp_all)
|
|
|
|
func register_window(root: Control, panel: Control, header: Control) -> void:
|
|
for entry in _windows:
|
|
if entry["root"] == root:
|
|
entry["header"] = header
|
|
_prepare_header(header)
|
|
return
|
|
panel.mouse_filter = Control.MOUSE_FILTER_STOP
|
|
_prepare_header(header)
|
|
var original := panel.get_theme_stylebox("panel").duplicate() as StyleBoxFlat
|
|
_windows.append({"root": root, "panel": panel, "header": header, "style": original})
|
|
root.visibility_changed.connect(func() -> void:
|
|
if root.is_visible_in_tree() and panel.is_visible_in_tree():
|
|
focus_window.call_deferred(root)
|
|
elif _dragged == root:
|
|
_dragged = null
|
|
)
|
|
_update_order()
|
|
|
|
func _prepare_header(header: Control) -> void:
|
|
header.custom_minimum_size.y = maxf(header.custom_minimum_size.y, 28.0)
|
|
header.mouse_default_cursor_shape = Control.CURSOR_DRAG
|
|
header.tooltip_text = "Przeciągnij, aby przesunąć okno"
|
|
|
|
func focus_window(root: Control) -> void:
|
|
if not is_instance_valid(root) or not root.is_visible_in_tree():
|
|
return
|
|
for index in _windows.size():
|
|
if _windows[index]["root"] == root:
|
|
var entry := _windows[index]
|
|
_windows.remove_at(index)
|
|
_windows.append(entry)
|
|
root.get_parent().move_child(root, root.get_parent().get_child_count() - 1)
|
|
_active = root
|
|
_update_order()
|
|
return
|
|
|
|
func _update_order() -> void:
|
|
for index in _windows.size():
|
|
var entry := _windows[index]
|
|
if not is_instance_valid(entry["root"]):
|
|
continue
|
|
var root: Control = entry["root"]
|
|
root.z_index = BASE_Z + index * Z_STEP
|
|
var panel: Control = entry["panel"]
|
|
var style: StyleBoxFlat = entry["style"]
|
|
if style != null:
|
|
var current := style.duplicate() as StyleBoxFlat
|
|
if root == _active:
|
|
current.border_color = Color("88c9e2")
|
|
current.shadow_color = Color(0, 0, 0, 0.4)
|
|
current.shadow_size = 8
|
|
panel.add_theme_stylebox_override("panel", current)
|
|
|
|
func window_at(point: Vector2) -> Control:
|
|
for index in range(_windows.size() - 1, -1, -1):
|
|
var entry := _windows[index]
|
|
var root: Control = entry["root"]
|
|
var panel: Control = entry["panel"]
|
|
if not is_instance_valid(root) or not is_instance_valid(panel) or not panel.is_visible_in_tree():
|
|
continue
|
|
if is_instance_valid(modal_overlay) and modal_overlay.is_visible_in_tree() and not modal_overlay.is_ancestor_of(root):
|
|
continue
|
|
if panel.get_global_rect().has_point(point):
|
|
return root
|
|
return null
|
|
|
|
func _header_at(root: Control, point: Vector2) -> bool:
|
|
for entry in _windows:
|
|
if entry["root"] != root:
|
|
continue
|
|
var header: Control = entry["header"]
|
|
if not is_instance_valid(header) or not header.get_global_rect().has_point(point):
|
|
return false
|
|
for button in header.find_children("*", "BaseButton", true, false):
|
|
if button.is_visible_in_tree() and button.get_global_rect().has_point(point):
|
|
return false
|
|
return true
|
|
return false
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if event is InputEventMouseButton:
|
|
if event.button_index == MOUSE_BUTTON_LEFT and not event.pressed and is_instance_valid(_dragged):
|
|
_dragged = null
|
|
get_viewport().set_input_as_handled()
|
|
return
|
|
if not event.pressed or get_viewport().gui_is_dragging():
|
|
return
|
|
var target := window_at(event.position)
|
|
if target == null:
|
|
return
|
|
focus_window(target)
|
|
interaction_started.emit()
|
|
if event.button_index == MOUSE_BUTTON_LEFT and _header_at(target, event.position):
|
|
_dragged = target
|
|
_drag_offset = event.position - target.global_position
|
|
target.set_meta("floating_window_dragged", true)
|
|
get_viewport().set_input_as_handled()
|
|
elif event is InputEventMouseMotion and is_instance_valid(_dragged):
|
|
if not _dragged.is_visible_in_tree():
|
|
_dragged = null
|
|
return
|
|
_dragged.global_position = event.position - _drag_offset
|
|
clamp_window(_dragged)
|
|
get_viewport().set_input_as_handled()
|
|
|
|
func clamp_window(root: Control) -> void:
|
|
if not is_instance_valid(root) or not root.is_inside_tree():
|
|
return
|
|
var viewport_size := get_viewport().get_visible_rect().size
|
|
var dimensions := root.size * root.get_global_transform().get_scale().abs()
|
|
var minimum := Vector2(8, preload("res://scripts/ui/resource_bar.gd").HEIGHT + 8)
|
|
root.global_position = root.global_position.clamp(minimum, (viewport_size - dimensions - Vector2(8, 8)).max(minimum))
|
|
|
|
func _clamp_all() -> void:
|
|
for entry in _windows:
|
|
var root: Control = entry["root"]
|
|
if is_instance_valid(root) and root.has_meta("floating_window_dragged"):
|
|
clamp_window(root)
|