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
|
||||
|
||||
- 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
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:
|
||||
_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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -175,4 +175,5 @@ hidden_tabs = {
|
|||
"Recorder": true,
|
||||
"Second Canvas": true
|
||||
}
|
||||
windows = {}
|
||||
save_on_change = false
|
||||
|
|
|
@ -145,4 +145,5 @@ hidden_tabs = {
|
|||
"Recorder": true,
|
||||
"Second Canvas": true
|
||||
}
|
||||
windows = {}
|
||||
save_on_change = false
|
||||
|
|
Loading…
Reference in a new issue