godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 75 forks source link

Get direct access to windows from `DisplayServer`, including embedded windows #7242

Open rsubtil opened 1 year ago

rsubtil commented 1 year ago

Describe the project you are working on

A "retrogaming library frontend", heavily reliant on the functionality of loading external PCK files to implement core functionality.

Describe the problem or limitation you are having in your project

cc @Sauermann from Rocket.Chat discussion

To support text input through controllers on an application I'm developing, I've implemented a custom on-screen keyboard:

https://github.com/godotengine/godot-proposals/assets/6501975/5bd1ea0f-7956-4817-8c8d-419b9ae34379

To have a generic system that works on all relevant text fields, as well as working on user-loaded content (external PCK files), this system runs on the root node, and works by querying the currently focused Control node and showing up on controller input.

In Godot 3, since there's only one Control node that can be focused through all the app, this was simple and required only to:

var control := get_focus_owner()

In Godot 4, Popup nodes are now Window nodes, meaning they each have their own Viewport. Therefore, the app can now have multiple focused controls, one for each existing Viewport. Therefore, the same code on Godot 4 doesn't work, because the relevant focused object only exists inside a Popup, and this code is running on the root node, therefore on the /root viewport.

However, if I have a reference to the relevant Viewport, this behavior can be preserved with:

var control := viewport.gui_get_focus_owner()

The issue is in obtaining this viewport reference, since currently there seems to be no way to find which current window is focused.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

The DisplayServer singleton has plenty of window_* methods that can reveal this information, but it has two problems:

The proposal is to fix these two issues: allow direct access to the Window objects, and work in environments where windows are being embedded as well (popups).

Furthermore, the first point would be benefitial in existing engine code, since there are already some hacks to overcome this limitation: https://github.com/godotengine/godot/blob/c3b0a92c3cd9a219c1b1776b48c147f1d0602f07/scene/gui/color_picker.cpp#L1526-L1536 https://github.com/godotengine/godot/blob/83cc5d4914a6bff76069ac19191192337e4df3de/scene/main/viewport.cpp#L2040-L2043

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

If this enhancement will not be used often, can it be worked around with a few lines of script?

This can be worked around on my project, but up to a certain point. If my script runs inside the desired viewport, gui_get_focus_owner works, but this then requires me to add my script to every single Viewport/Window/Popup that I have to work.

However for my purposes that's not enough, since the app loads external scenes from PCK files, not developed by me (kinda like mods). This same workaround would have to be done by those developers as well, which is not ideal.

Is there a reason why this should be core and not an add-on in the asset library?

This requires core engine modification.

Sauermann commented 1 year ago

I disagree with the concept, that DisplayServer should know about every embedded Window. DisplayServer should handle only platform-specific (Linux, Windows, macOS) details and embedded Windows are not platform specific. So I suggest rather, that they should be made accessible by Viewport in its function as a window-manager.

I am not sure, if DisplayServer should know about the Window class. This might introduce cyclic dependencies (Window depends on DisplayServer, DisplayServer depends on Window). There might even be rules prohibiting platform from accessing Nodes. Accessing Windows via their instance-id, which is defined in core, seems reasonable to me.

Finding the currently focused Control-node could be made possible by:

  1. Expose a function DisplayServer::get_focused_window_or_popup to GDScript, which utilizes the currently available implementation DisplayServerX11::_get_focused_window_or_popup (and Windows/macOS variant).
  2. Expose a function Viewport::get_focused_window_or_popup to GDScript, which returns the currently focused embedded Window.

With these changes, finding the currently focused Control can then be done:

  1. Get the focused native Window from DisplayServer
  2. Get (recursively) the focused embedded Window within (if available) from Viewport
  3. Get the focused Control in the found Window with Viewport.gui_get_focus_owner()
Sauermann commented 1 year ago

@rsubtil I have implemented my proposal and it would help, if you could provide feedback on https://github.com/godotengine/godot/pull/79261

Sauermann commented 1 year ago

Related to #7229, which goes a few steps further.