1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-01-18 00:59:47 +00:00

Implement support for floating windows

This commit is contained in:
Emmanouil Papadeas 2024-10-11 12:13:13 +03:00
parent ffc98a4b7f
commit ae4b5046ed
8 changed files with 170 additions and 2 deletions

View file

@ -24,7 +24,7 @@ Files extracted from source:
## godot-dockable-container
- Upstream: https://github.com/gilzoide/godot-dockable-container
- Version: Based on [ddff84aa31e466101b4a75c7ff68d3a82701e387](https://github.com/gilzoide/godot-dockable-container/commit/ddff84aa31e466101b4a75c7ff68d3a82701e387), but with changes in layout.gd that add a `save_on_change` variable and a `save()` method.
- Version: Based on [e852cbeeb3f06f62c559898b4cf5756858367766](https://github.com/OverloadedOrama/godot-dockable-container/commit/e852cbeeb3f06f62c559898b4cf5756858367766), but with changes in layout.gd that add a `save_on_change` variable and a `save()` method.
- License: [CC0-1.0](https://github.com/gilzoide/godot-dockable-container/blob/main/LICENSE)
## SmartSlicer

View file

@ -54,6 +54,7 @@ const DragNDropPanel := preload("drag_n_drop_panel.gd")
var _layout := DockableLayout.new()
var _panel_container := Container.new()
var _windows_container := Container.new()
var _split_container := Container.new()
var _drag_n_drop_panel := DragNDropPanel.new()
var _drag_panel: DockablePanel
@ -80,6 +81,8 @@ func _ready() -> void:
_split_container.name = "_split_container"
_split_container.mouse_filter = MOUSE_FILTER_PASS
_panel_container.add_child(_split_container)
_windows_container.name = "_windows_container"
get_parent().call_deferred("add_child", _windows_container)
_drag_n_drop_panel.name = "_drag_n_drop_panel"
_drag_n_drop_panel.mouse_filter = MOUSE_FILTER_PASS
@ -161,6 +164,61 @@ func _drop_data(_position: Vector2, data) -> void:
queue_sort()
func _add_floating_options(tab_container: DockablePanel) -> void:
var options := PopupMenu.new()
options.add_item("Make Floating")
options.id_pressed.connect(_toggle_floating.bind(tab_container))
options.size.y = 0
_windows_container.add_child(options)
tab_container.set_popup(options)
## Required when converting a window back to panel.
func _refresh_tabs_visible() -> void:
if tabs_visible:
tabs_visible = false
await get_tree().process_frame
await get_tree().process_frame
tabs_visible = true
func _toggle_floating(_id: int, tab_container: DockablePanel) -> void:
var node_name := tab_container.get_tab_title(tab_container.current_tab)
var node := get_node(node_name)
if is_instance_valid(node):
var tab_position := maxi(tab_container.leaf.find_child(node), 0)
_convert_to_window(node, {"tab_position": tab_position, "tab_container": tab_container})
else:
print("Node ", node_name, " not found!")
## Converts a panel to floating window.
func _convert_to_window(content: Control, previous_data := {}) -> void:
var old_owner := content.owner
var data := {}
if content.name in layout.windows:
data = layout.windows[content.name]
var window := FloatingWindow.new(content, data)
_windows_container.add_child(window)
window.show()
_refresh_tabs_visible()
window.close_requested.connect(_convert_to_panel.bind(window, old_owner, previous_data))
window.data_changed.connect(layout.save_window_properties)
## Converts a floating window into a panel.
func _convert_to_panel(window: FloatingWindow, old_owner: Node, previous_data := {}) -> void:
var content := window.window_content
window.remove_child(content)
window.destroy()
add_child(content)
content.owner = old_owner
if previous_data.has("tab_container") and is_instance_valid(previous_data["tab_container"]):
var tab_position := previous_data.get("tab_position", 0) as int
previous_data["tab_container"].leaf.insert_node(tab_position, content)
_refresh_tabs_visible()
func set_control_as_current_tab(control: Control) -> void:
assert(
control.get_parent_control() == self,
@ -195,6 +253,16 @@ func set_layout(value: DockableLayout) -> void:
_layout.changed.disconnect(queue_sort)
_layout = value
_layout.changed.connect(queue_sort)
for window in _windows_container.get_children():
if not window.name in _layout.windows and window is FloatingWindow:
window.prevent_data_erasure = true # We don't want to delete data.
window.close_requested.emit() # Removes the window.
continue
for window: String in _layout.windows.keys():
var panel := find_child(window, false)
# Only those windows get created which were not previously created.
if panel:
_convert_to_window(panel)
_layout_dirty = true
queue_sort()
@ -202,7 +270,7 @@ func set_layout(value: DockableLayout) -> void:
func set_use_hidden_tabs_for_min_size(value: bool) -> void:
_use_hidden_tabs_for_min_size = value
for i in range(1, _panel_container.get_child_count()):
var panel = _panel_container.get_child(i)
var panel := _panel_container.get_child(i) as DockablePanel
panel.use_hidden_tabs_for_min_size = value
@ -401,6 +469,7 @@ func _get_panel(idx: int) -> DockablePanel:
panel.hide_single_tab = _hide_single_tab
panel.use_hidden_tabs_for_min_size = _use_hidden_tabs_for_min_size
panel.set_tabs_rearrange_group(maxi(0, rearrange_group))
_add_floating_options(panel)
_panel_container.add_child(panel)
panel.tab_layout_changed.connect(_on_panel_tab_layout_changed.bind(panel))
return panel

View file

@ -40,6 +40,8 @@ func _exit_tree() -> void:
active_tab_rearranged.disconnect(_on_tab_changed)
tab_selected.disconnect(_on_tab_selected)
tab_changed.disconnect(_on_tab_changed)
if is_instance_valid(get_popup()):
get_popup().queue_free()
func track_nodes(nodes: Array[Control], new_leaf: DockableLayoutPanel) -> void:

View file

@ -0,0 +1,73 @@
class_name FloatingWindow
extends Window
## Emitted when the window's position or size changes, or when it's closed.
signal data_changed
var window_content: Control
var prevent_data_erasure := false
var _is_initialized := false
func _init(content: Control, data := {}) -> void:
window_content = content
title = window_content.name
name = window_content.name
min_size = window_content.get_minimum_size()
unresizable = false
wrap_controls = true
always_on_top = true
ready.connect(_deserialize.bind(data))
func _ready() -> void:
set_deferred(&"size", Vector2(300, 300))
await get_tree().process_frame
await get_tree().process_frame
if get_tree().current_scene.get_window().gui_embed_subwindows:
position = DisplayServer.window_get_size() / 2 - size / 2
else:
position = DisplayServer.screen_get_usable_rect().size / 2 - size / 2
func _input(event: InputEvent) -> void:
if event is InputEventMouse:
# Emit `data_changed` when the window is being moved.
if not window_content.get_rect().has_point(event.position) and _is_initialized:
data_changed.emit(name, serialize())
func serialize() -> Dictionary:
return {"size": size, "position": position}
func _deserialize(data: Dictionary) -> void:
window_content.get_parent().remove_child(window_content)
window_content.visible = true
window_content.global_position = Vector2.ZERO
add_child(window_content)
size_changed.connect(window_size_changed)
if "position" in data:
await get_tree().process_frame
await get_tree().process_frame
position = data["position"]
if "size" in data:
set_deferred(&"size", data["size"])
_is_initialized = true
func window_size_changed() -> void:
window_content.size = size
window_content.position = Vector2.ZERO
if _is_initialized:
data_changed.emit(name, serialize())
func destroy() -> void:
size_changed.disconnect(window_size_changed)
queue_free()
func _exit_tree() -> void:
if _is_initialized and !prevent_data_erasure:
data_changed.emit(name, {})

View file

@ -23,6 +23,14 @@ enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }
if value != _hidden_tabs:
_hidden_tabs = value
changed.emit()
## A [Dictionary] of [StringName] and [Dictionary], containing data such as position and size.
@export var windows := {}:
get:
return _windows
set(value):
if value != _windows:
_windows = value
changed.emit()
@export var save_on_change := false:
set(value):
save_on_change = value
@ -36,6 +44,7 @@ enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }
var _changed_signal_queued := false
var _first_leaf: DockableLayoutPanel
var _hidden_tabs: Dictionary
var _windows: Dictionary
var _leaf_by_node_name: Dictionary
var _root: DockableLayoutNode = DockableLayoutPanel.new()
@ -182,6 +191,15 @@ func set_tab_hidden(name: String, hidden: bool) -> void:
_on_root_changed()
func save_window_properties(window_name: StringName, data: Dictionary) -> void:
var new_windows = windows.duplicate(true)
if data.is_empty():
new_windows.erase(window_name)
else:
new_windows[window_name] = data
windows = new_windows
func is_tab_hidden(name: String) -> bool:
return _hidden_tabs.get(name, false)

View file

@ -31,6 +31,8 @@ resource_name = "Layout"
script = ExtResource("2")
root = SubResource("Resource_hl8y1")
hidden_tabs = {}
windows = {}
save_on_change = false
[sub_resource type="Resource" id="Resource_ntwfj"]
resource_name = "Tabs"
@ -71,6 +73,8 @@ resource_name = "Layout"
script = ExtResource("2")
root = SubResource("Resource_jhibs")
hidden_tabs = {}
windows = {}
save_on_change = false
[node name="SampleScene" type="VBoxContainer"]
anchors_preset = 15

View file

@ -175,4 +175,5 @@ hidden_tabs = {
"Recorder": true,
"Second Canvas": true
}
windows = {}
save_on_change = false

View file

@ -145,4 +145,5 @@ hidden_tabs = {
"Recorder": true,
"Second Canvas": true
}
windows = {}
save_on_change = false