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:
parent
ffc98a4b7f
commit
ae4b5046ed
|
@ -24,7 +24,7 @@ Files extracted from source:
|
||||||
## godot-dockable-container
|
## godot-dockable-container
|
||||||
|
|
||||||
- Upstream: https://github.com/gilzoide/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)
|
- License: [CC0-1.0](https://github.com/gilzoide/godot-dockable-container/blob/main/LICENSE)
|
||||||
|
|
||||||
## SmartSlicer
|
## SmartSlicer
|
||||||
|
|
|
@ -54,6 +54,7 @@ const DragNDropPanel := preload("drag_n_drop_panel.gd")
|
||||||
|
|
||||||
var _layout := DockableLayout.new()
|
var _layout := DockableLayout.new()
|
||||||
var _panel_container := Container.new()
|
var _panel_container := Container.new()
|
||||||
|
var _windows_container := Container.new()
|
||||||
var _split_container := Container.new()
|
var _split_container := Container.new()
|
||||||
var _drag_n_drop_panel := DragNDropPanel.new()
|
var _drag_n_drop_panel := DragNDropPanel.new()
|
||||||
var _drag_panel: DockablePanel
|
var _drag_panel: DockablePanel
|
||||||
|
@ -80,6 +81,8 @@ func _ready() -> void:
|
||||||
_split_container.name = "_split_container"
|
_split_container.name = "_split_container"
|
||||||
_split_container.mouse_filter = MOUSE_FILTER_PASS
|
_split_container.mouse_filter = MOUSE_FILTER_PASS
|
||||||
_panel_container.add_child(_split_container)
|
_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.name = "_drag_n_drop_panel"
|
||||||
_drag_n_drop_panel.mouse_filter = MOUSE_FILTER_PASS
|
_drag_n_drop_panel.mouse_filter = MOUSE_FILTER_PASS
|
||||||
|
@ -161,6 +164,61 @@ func _drop_data(_position: Vector2, data) -> void:
|
||||||
queue_sort()
|
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:
|
func set_control_as_current_tab(control: Control) -> void:
|
||||||
assert(
|
assert(
|
||||||
control.get_parent_control() == self,
|
control.get_parent_control() == self,
|
||||||
|
@ -195,6 +253,16 @@ func set_layout(value: DockableLayout) -> void:
|
||||||
_layout.changed.disconnect(queue_sort)
|
_layout.changed.disconnect(queue_sort)
|
||||||
_layout = value
|
_layout = value
|
||||||
_layout.changed.connect(queue_sort)
|
_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
|
_layout_dirty = true
|
||||||
queue_sort()
|
queue_sort()
|
||||||
|
|
||||||
|
@ -202,7 +270,7 @@ func set_layout(value: DockableLayout) -> void:
|
||||||
func set_use_hidden_tabs_for_min_size(value: bool) -> void:
|
func set_use_hidden_tabs_for_min_size(value: bool) -> void:
|
||||||
_use_hidden_tabs_for_min_size = value
|
_use_hidden_tabs_for_min_size = value
|
||||||
for i in range(1, _panel_container.get_child_count()):
|
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
|
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.hide_single_tab = _hide_single_tab
|
||||||
panel.use_hidden_tabs_for_min_size = _use_hidden_tabs_for_min_size
|
panel.use_hidden_tabs_for_min_size = _use_hidden_tabs_for_min_size
|
||||||
panel.set_tabs_rearrange_group(maxi(0, rearrange_group))
|
panel.set_tabs_rearrange_group(maxi(0, rearrange_group))
|
||||||
|
_add_floating_options(panel)
|
||||||
_panel_container.add_child(panel)
|
_panel_container.add_child(panel)
|
||||||
panel.tab_layout_changed.connect(_on_panel_tab_layout_changed.bind(panel))
|
panel.tab_layout_changed.connect(_on_panel_tab_layout_changed.bind(panel))
|
||||||
return panel
|
return panel
|
||||||
|
|
|
@ -40,6 +40,8 @@ func _exit_tree() -> void:
|
||||||
active_tab_rearranged.disconnect(_on_tab_changed)
|
active_tab_rearranged.disconnect(_on_tab_changed)
|
||||||
tab_selected.disconnect(_on_tab_selected)
|
tab_selected.disconnect(_on_tab_selected)
|
||||||
tab_changed.disconnect(_on_tab_changed)
|
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:
|
func track_nodes(nodes: Array[Control], new_leaf: DockableLayoutPanel) -> void:
|
||||||
|
|
73
addons/dockable_container/floating_window.gd
Normal file
73
addons/dockable_container/floating_window.gd
Normal 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, {})
|
|
@ -23,6 +23,14 @@ enum { MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MARGIN_BOTTOM, MARGIN_CENTER }
|
||||||
if value != _hidden_tabs:
|
if value != _hidden_tabs:
|
||||||
_hidden_tabs = value
|
_hidden_tabs = value
|
||||||
changed.emit()
|
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:
|
@export var save_on_change := false:
|
||||||
set(value):
|
set(value):
|
||||||
save_on_change = 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 _changed_signal_queued := false
|
||||||
var _first_leaf: DockableLayoutPanel
|
var _first_leaf: DockableLayoutPanel
|
||||||
var _hidden_tabs: Dictionary
|
var _hidden_tabs: Dictionary
|
||||||
|
var _windows: Dictionary
|
||||||
var _leaf_by_node_name: Dictionary
|
var _leaf_by_node_name: Dictionary
|
||||||
var _root: DockableLayoutNode = DockableLayoutPanel.new()
|
var _root: DockableLayoutNode = DockableLayoutPanel.new()
|
||||||
|
|
||||||
|
@ -182,6 +191,15 @@ func set_tab_hidden(name: String, hidden: bool) -> void:
|
||||||
_on_root_changed()
|
_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:
|
func is_tab_hidden(name: String) -> bool:
|
||||||
return _hidden_tabs.get(name, false)
|
return _hidden_tabs.get(name, false)
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@ resource_name = "Layout"
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
root = SubResource("Resource_hl8y1")
|
root = SubResource("Resource_hl8y1")
|
||||||
hidden_tabs = {}
|
hidden_tabs = {}
|
||||||
|
windows = {}
|
||||||
|
save_on_change = false
|
||||||
|
|
||||||
[sub_resource type="Resource" id="Resource_ntwfj"]
|
[sub_resource type="Resource" id="Resource_ntwfj"]
|
||||||
resource_name = "Tabs"
|
resource_name = "Tabs"
|
||||||
|
@ -71,6 +73,8 @@ resource_name = "Layout"
|
||||||
script = ExtResource("2")
|
script = ExtResource("2")
|
||||||
root = SubResource("Resource_jhibs")
|
root = SubResource("Resource_jhibs")
|
||||||
hidden_tabs = {}
|
hidden_tabs = {}
|
||||||
|
windows = {}
|
||||||
|
save_on_change = false
|
||||||
|
|
||||||
[node name="SampleScene" type="VBoxContainer"]
|
[node name="SampleScene" type="VBoxContainer"]
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
|
|
|
@ -175,4 +175,5 @@ hidden_tabs = {
|
||||||
"Recorder": true,
|
"Recorder": true,
|
||||||
"Second Canvas": true
|
"Second Canvas": true
|
||||||
}
|
}
|
||||||
|
windows = {}
|
||||||
save_on_change = false
|
save_on_change = false
|
||||||
|
|
|
@ -145,4 +145,5 @@ hidden_tabs = {
|
||||||
"Recorder": true,
|
"Recorder": true,
|
||||||
"Second Canvas": true
|
"Second Canvas": true
|
||||||
}
|
}
|
||||||
|
windows = {}
|
||||||
save_on_change = false
|
save_on_change = false
|
||||||
|
|
Loading…
Reference in a new issue