diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index 9c0e8011dc28..99906bf5f5ca 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -646,6 +646,10 @@
[b]Note:[/b] On Windows, the portion of a window that lies outside the region is not drawn, while on Linux (X11) and macOS it is.
[b]Note:[/b] This property is implemented on Linux (X11), macOS and Windows.
+
+ If [code]false[/code], only a single focused [Control] is allowed in [Viewport] nodes within this window. If [code]true[/code], every [Viewport] in this window keeps an independent state of their own focused [Control] node.
+ This setting affects only the window itself and child [SubViewport] nodes. Child [Window] nodes and their child nodes aren't affected by this setting. This is useful for splitscreen input handling.
+
If [code]true[/code], the [Window] will be considered a popup. Popups are sub-windows that don't show as separate windows in system's window manager's window list and will send close request when anything is clicked outside of them (unless [member exclusive] is enabled).
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index c8d2d71c2afa..d5e4dd9c4c78 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -2509,11 +2509,34 @@ bool Viewport::_gui_control_has_focus(const Control *p_control) {
}
void Viewport::_gui_control_grab_focus(Control *p_control) {
- if (gui.key_focus && gui.key_focus == p_control) {
- // No need for change.
- return;
+ Window *base_window = get_base_window();
+ if (base_window->is_multi_viewport_focus_enabled()) {
+ // Release previous focus.
+ if (gui.key_focus && gui.key_focus != p_control) {
+ gui_release_focus();
+ }
+
+ // Ensure chain of SubViewportContainer focus in parent Viewport. Needs to happen, even if control has focus in this Viewport.
+ Viewport *vp = this;
+ if (Object::cast_to(vp)) {
+ SubViewportContainer *svc = Object::cast_to(get_parent());
+ if (svc) {
+ svc->grab_focus();
+ }
+ }
+ if (gui.key_focus == p_control) {
+ // No need for change.
+ return;
+ }
+ } else {
+ if (gui.key_focus && gui.key_focus == p_control) {
+ // No need for change.
+ return;
+ }
+ get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", (Node *)base_window);
}
- get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", (Node *)get_base_window());
+
+ // Perform necessary adjustments for focuschange.
if (p_control->is_inside_tree() && p_control->get_viewport() == this) {
gui.key_focus = p_control;
emit_signal(SNAME("gui_focus_changed"), p_control);
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 6632dd903394..eec55de13d6b 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -2749,6 +2749,16 @@ void Window::_mouse_leave_viewport() {
}
}
+void Window::set_multi_viewport_focus(bool p_enable) {
+ ERR_MAIN_THREAD_GUARD;
+ multi_viewport_focus = p_enable;
+}
+
+bool Window::is_multi_viewport_focus_enabled() const {
+ ERR_READ_THREAD_GUARD_V(false);
+ return multi_viewport_focus;
+}
+
void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_title", "title"), &Window::set_title);
ClassDB::bind_method(D_METHOD("get_title"), &Window::get_title);
@@ -2903,6 +2913,9 @@ void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_layout_direction"), &Window::get_layout_direction);
ClassDB::bind_method(D_METHOD("is_layout_rtl"), &Window::is_layout_rtl);
+ ClassDB::bind_method(D_METHOD("set_multi_viewport_focus", "enable"), &Window::set_multi_viewport_focus);
+ ClassDB::bind_method(D_METHOD("is_multi_viewport_focus_enabled"), &Window::is_multi_viewport_focus_enabled);
+
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("set_auto_translate", "enable"), &Window::set_auto_translate);
ClassDB::bind_method(D_METHOD("is_auto_translating"), &Window::is_auto_translating);
@@ -2961,6 +2974,8 @@ void Window::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_stretch", PROPERTY_HINT_ENUM, "Fractional,Integer"), "set_content_scale_stretch", "get_content_scale_stretch");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor", PROPERTY_HINT_RANGE, "0.5,8.0,0.01"), "set_content_scale_factor", "get_content_scale_factor");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multi_viewport_focus"), "set_multi_viewport_focus", "is_multi_viewport_focus_enabled");
+
#ifndef DISABLE_DEPRECATED
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_auto_translate", "is_auto_translating");
#endif
diff --git a/scene/main/window.h b/scene/main/window.h
index ffcf50ccdd5f..e226f892656d 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -135,6 +135,8 @@ class Window : public Viewport {
bool unparent_when_invisible = false;
bool keep_title_visible = false;
+ bool multi_viewport_focus = false;
+
LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED;
void _update_child_controls();
@@ -474,6 +476,9 @@ class Window : public Viewport {
virtual bool is_directly_attached_to_screen() const override;
virtual bool is_attached_in_viewport() const override;
+ void set_multi_viewport_focus(bool p_enable);
+ bool is_multi_viewport_focus_enabled() const;
+
Rect2i get_parent_rect() const;
virtual DisplayServer::WindowID get_window_id() const override;