Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement floating windows #36

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

OverloadedOrama
Copy link
Collaborator

@OverloadedOrama OverloadedOrama commented Oct 9, 2024

Implements #4, a continuation of #18 for Godot 4. Many thanks to @Variable-ind for starting the work!

Peek 2024-10-09 13-53

Tab containers now have settings with a "Make Floating" option. This is what makes panels into windows. To make windows into panels again, simply close the window. If the original tab container of the panel still exists when the window closes (meaning that it has more tabs and it did not get freed), the panel returns to that tab container. If it doesn't exist, it is being added to the first available tab container.

Based on my testing, it works well with the embed subwindows option set to both true or false. Windows and their data (position and size) are being stored in the layout, and thus are being remembered between sessions.

Probably the biggest issue with this is that child controls are being reparented to window nodes. Given how windows work in Godot, I don't think soft parenting is possible, especially now that we can have separate windows. But of course I may be wrong, so ideas are welcome!

Copy link
Owner

@gilzoide gilzoide left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have yet to test this branch to get more insights, but I already have some comments based on the code.
Sorry for taking this long to review this 😅

Comment on lines +179 to +184
var new_windows = windows.duplicate(true)
if data.is_empty():
new_windows.erase(window_name)
else:
new_windows[window_name] = data
windows = new_windows
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to duplicate windows here? If it's about emitting the changed signal, we could just modify _windows and call _on_root_changed instead.

@@ -0,0 +1,80 @@
class_name FloatingWindow
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not too sure about it, but maybe this class should be called DockableFloatingWindow to namespace the type, avoiding possible name clashes? The other "private" classes in this package don't use a global class_name, not sure if this one should too. People shouldn't be creating instances of FloatingWindow in the editor, for example.

Comment on lines +24 to +26
set_deferred(&"size", Vector2(300, 300))
await get_tree().process_frame
await get_tree().process_frame
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is another signal that makes more sense that we could wait here instead of generically waiting for 2 frames? If there isn't any, just ignore this comment.

@@ -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)
Copy link
Owner

@gilzoide gilzoide Feb 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this script creates _windows_container, it should also destroy it, likely in _exit_tree.
By the way, can't _windows_container be a direct child of DockableContainer instead of a sibling? It could be a special child that is not docked, just as _panel_container, _split_container and _drag_n_drop_panel are.

Comment on lines +167 to +173
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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method could be implemented in DockablePanel directly, what do you think? DockablePanel would then have a toggle_floating_window signal or something like this, that already passes all required values like current tab index, current tab name, leaf control, etc... and DockableContainer simply connects it to _toggle_floating.

By the way, I think the popup doesn't need to be a child of _windows_container or anything else, setting a popup to a TabContainer ties the popup to it and you don't need to worry about it ever again: it will be managed by the tab container. Doesn't it? 🤔

Comment on lines +198 to +200
var data := {}
if content.name in layout.windows:
data = layout.windows[content.name]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could use Dictionary.get

Suggested change
var data := {}
if content.name in layout.windows:
data = layout.windows[content.name]
var data := layout.windows.get(content.name, {}) as Dictionary

Comment on lines +43 to +44
if is_instance_valid(get_popup()):
get_popup().queue_free()
Copy link
Owner

@gilzoide gilzoide Feb 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't add the popup as a child of _window_container as mentioned above, this would likely be unnecessary. As per the documentation:

This is a required internal node, removing and freeing it may cause a crash.

Comment on lines +257 to +259
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.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using prevent_data_erasure, closing all windows and reopening them later, we could use a pool of FloatingWindow nodes, much like we pool DockablePanel and SplitHandle nodes. In _resort, we would call _untrack_children_after(_windows_container) after setting all active floating windows, which would delete unused windows, making them close anyway.

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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this continue unnecessary, since it's the last statement of the for block?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants