diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index be8811d629a4..5a671700866e 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -544,6 +544,12 @@
+
+
+
+ Returns ID of the active popup window, or [constant INVALID_WINDOW_ID] if there is none.
+
+
@@ -592,6 +598,13 @@
[b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows.
+
+
+
+
+ Returns the bounding box of control, or menu item that was used to open the popup window, in the screen coordinate system.
+
+
@@ -749,6 +762,14 @@
[b]Note:[/b] This method is implemented on Linux, macOS and Windows.
+
+
+
+
+
+ Sets the bounding box of control, or menu item that was used to open the popup window, in the screen coordinate system. Clicking this area will not auto-close this popup.
+
+
@@ -930,16 +951,24 @@
Regardless of the platform, enabling fullscreen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling fullscreen mode.
+ Window can't be resizing by dragging its resize grip. It's still possible to resize the window using [method window_set_size]. This flag is ignored for full screen windows.
+ Window do not have native title bar and other decorations. This flag is ignored for full-screen windows.
+ Window is floating above other regular windows. This flag is ignored for full-screen windows.
+ Window is will be destroyed with its transient parent and displayed on top of non-exclusive full-screen parent window. Transient windows can't enter full-screen mode.
+ Window can't be focused. No-focus window will ignore all input, except mouse clicks.
+
+
+ Window is part of menu or [OptionButton] dropdown. This flag can't be changed when window is visible. An active popup window will exclusivly receive all input, without stealing focus from its parent. Popup windows are automatically closed when uses click outside it, or when an application is switched. Popup window must have [constant WINDOW_FLAG_TRANSPARENT] set.
-
+
diff --git a/doc/classes/Popup.xml b/doc/classes/Popup.xml
index 06efadb071f3..3fcf0a9b8fc7 100644
--- a/doc/classes/Popup.xml
+++ b/doc/classes/Popup.xml
@@ -4,15 +4,13 @@
Popup is a base window container for popup-like subwindows.
- Popup is a base window container for popup-like subwindows. It's a modal by default (see [member close_on_parent_focus]) and has helpers for custom popup behavior.
+ Popup is a base window container for popup-like subwindows. It's a modal by default (see [member popup_window]) and has helpers for custom popup behavior.
-
- If true, the [Popup] will close when its parent [Window] is focused.
-
+
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index 912d3cb16cc5..aa888a596a55 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -306,6 +306,8 @@
Set's the window's current mode.
[b]Note:[/b] Fullscreen mode is not exclusive fullscreen on Windows and Linux.
+
+
The window's position in pixels.
@@ -416,7 +418,9 @@
-
+
+
+
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 8e3aa4f59d58..4a0fc0b29f07 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -4024,7 +4024,7 @@ TileMapEditor::TileMapEditor() {
// Layer selector.
layers_selection_popup = memnew(PopupMenu);
layers_selection_popup->connect("id_pressed", callable_mp(this, &TileMapEditor::_layers_selection_id_pressed));
- layers_selection_popup->set_close_on_parent_focus(false);
+ layers_selection_popup->set_flag(Window::FLAG_POPUP, false);
layers_selection_button = memnew(Button);
layers_selection_button->set_toggle_mode(true);
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index bca38d9f2049..bf3bfe95535a 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -1173,6 +1173,7 @@ void DisplayServerX11::show_window(WindowID p_id) {
_THREAD_SAFE_METHOD_
const WindowData &wd = windows[p_id];
+ popup_open(p_id);
DEBUG_LOG_X11("show_window: %lu (%u) \n", wd.x11_window, p_id);
@@ -1183,7 +1184,9 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
_THREAD_SAFE_METHOD_
ERR_FAIL_COND(!windows.has(p_id));
- ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted"); //ma
+ ERR_FAIL_COND_MSG(p_id == MAIN_WINDOW_ID, "Main window can't be deleted");
+
+ popup_close(p_id);
WindowData &wd = windows[p_id];
@@ -1458,8 +1461,8 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent
// Set focus to parent sub window to avoid losing all focus when closing a nested sub-menu.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if (wd_window.menu_type && !wd_window.no_focus && wd_window.focused) {
- if (!wd_parent.no_focus) {
+ if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) {
+ if (!wd_parent.no_focus && !wd_window.is_popup) {
XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime);
}
}
@@ -2073,6 +2076,18 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
case WINDOW_FLAG_TRANSPARENT: {
//todo reimplement
} break;
+ case WINDOW_FLAG_NO_FOCUS: {
+ wd.no_focus = p_enabled;
+ } break;
+ case WINDOW_FLAG_POPUP: {
+ XWindowAttributes xwa;
+ XSync(x11_display, False);
+ XGetWindowAttributes(x11_display, wd.x11_window, &xwa);
+
+ ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
+ ERR_FAIL_COND_MSG((xwa.map_state == IsViewable) && (wd.is_popup != p_enabled), "Pupup flag can't changed while window is opened.");
+ wd.is_popup = p_enabled;
+ } break;
default: {
}
}
@@ -2114,6 +2129,12 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co
case WINDOW_FLAG_TRANSPARENT: {
//todo reimplement
} break;
+ case WINDOW_FLAG_NO_FOCUS: {
+ return wd.no_focus;
+ } break;
+ case WINDOW_FLAG_POPUP: {
+ return wd.is_popup;
+ } break;
default: {
}
}
@@ -3028,23 +3049,36 @@ void DisplayServerX11::_dispatch_input_event(const Ref &p_event) {
Variant ret;
Callable::CallError ce;
+ {
+ List::Element *E = popup_list.front();
+ if (E && Object::cast_to(*p_event)) {
+ // Redirect keyboard input to active popup.
+ if (windows.has(E->get())) {
+ Callable callable = windows[E->get()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+ return;
+ }
+ }
+
Ref event_from_window = p_event;
if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
- //send to a window
- ERR_FAIL_COND(!windows.has(event_from_window->get_window_id()));
- Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
- if (callable.is_null()) {
- return;
+ // Send to a single window.
+ if (windows.has(event_from_window->get_window_id())) {
+ Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
}
- callable.call((const Variant **)&evp, 1, ret, ce);
} else {
- //send to all windows
+ // Send to all windows.
for (KeyValue &E : windows) {
Callable callable = E.value.input_event_callback;
- if (callable.is_null()) {
- continue;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
}
- callable.call((const Variant **)&evp, 1, ret, ce);
}
}
}
@@ -3136,6 +3170,108 @@ void DisplayServerX11::_check_pending_events(LocalVector &r_events) {
}
}
+DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const {
+ const List::Element *E = popup_list.back();
+ if (E) {
+ return E->get();
+ } else {
+ return INVALID_WINDOW_ID;
+ }
+}
+
+void DisplayServerX11::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.parent_safe_rect = p_rect;
+}
+
+Rect2i DisplayServerX11::window_get_popup_safe_rect(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());
+ const WindowData &wd = windows[p_window];
+ return wd.parent_safe_rect;
+}
+
+void DisplayServerX11::popup_open(WindowID p_window) {
+ WindowData &wd = windows[p_window];
+ if (wd.is_popup) {
+ // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ List::Element *E = popup_list.back();
+ while (E) {
+ if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
+ _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ } else {
+ break;
+ }
+ }
+
+ time_since_popup = OS::get_singleton()->get_ticks_msec();
+ popup_list.push_back(p_window);
+ }
+}
+
+void DisplayServerX11::popup_close(WindowID p_window) {
+ List::Element *E = popup_list.find(p_window);
+ while (E) {
+ _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->next();
+ popup_list.erase(E);
+ E = F;
+ }
+}
+
+void DisplayServerX11::mouse_process_popups() {
+ if (popup_list.is_empty()) {
+ return;
+ }
+
+ uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
+ if (delta < 250) {
+ return;
+ }
+
+ int number_of_screens = XScreenCount(x11_display);
+ for (int i = 0; i < number_of_screens; i++) {
+ Window root, child;
+ int root_x, root_y, win_x, win_y;
+ unsigned int mask;
+ if (XQueryPointer(x11_display, XRootWindow(x11_display, i), &root, &child, &root_x, &root_y, &win_x, &win_y, &mask)) {
+ XWindowAttributes root_attrs;
+ XGetWindowAttributes(x11_display, root, &root_attrs);
+ Vector2i pos = Vector2i(root_attrs.x + root_x, root_attrs.y + root_y);
+ if ((pos != last_mouse_monitor_pos) || (mask != last_mouse_monitor_mask)) {
+ if (((mask & Button1Mask) || (mask & Button2Mask) || (mask & Button3Mask) || (mask & Button4Mask) || (mask & Button5Mask))) {
+ List::Element *E = popup_list.back();
+ while (E) {
+ // Popup window area.
+ Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
+ // Area of the parent window, which responsible for opening sub-menu.
+ Rect2i safe_rect = window_get_popup_safe_rect(E->get());
+ if (win_rect.has_point(pos)) {
+ break;
+ } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
+ break;
+ } else {
+ _send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ }
+ }
+ }
+ }
+ last_mouse_monitor_mask = mask;
+ last_mouse_monitor_pos = pos;
+ }
+ }
+}
+
void DisplayServerX11::process_events() {
_THREAD_SAFE_METHOD_
@@ -3144,6 +3280,8 @@ void DisplayServerX11::process_events() {
++frame;
#endif
+ mouse_process_popups();
+
if (app_focused) {
//verify that one of the windows has focus, else send focus out notification
bool focus_found = false;
@@ -3374,7 +3512,7 @@ void DisplayServerX11::process_events() {
// Set focus when menu window is started.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if (wd.menu_type && !wd.no_focus) {
+ if (!wd.no_focus && !wd.is_popup) {
XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
}
} break;
@@ -3520,7 +3658,7 @@ void DisplayServerX11::process_events() {
// Set focus when menu window is re-used.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if (wd.menu_type && !wd.no_focus) {
+ if (!wd.no_focus && !wd.is_popup) {
XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
}
@@ -3561,7 +3699,7 @@ void DisplayServerX11::process_events() {
// Ensure window focus on click.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if (!wd.no_focus) {
+ if (!wd.no_focus && !wd.is_popup) {
XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
}
@@ -4121,21 +4259,20 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
WindowID id = window_id_counter++;
WindowData &wd = windows[id];
- if ((id != MAIN_WINDOW_ID) && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) {
- wd.menu_type = true;
- }
-
if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
- wd.menu_type = true;
wd.no_focus = true;
}
+ if (p_flags & WINDOW_FLAG_POPUP_BIT) {
+ wd.is_popup = true;
+ }
+
// Setup for menu subwindows:
// - override_redirect forces the WM not to interfere with the window, to avoid delays due to
// handling decorations and placement.
// On the other hand, focus changes need to be handled manually when this is set.
// - save_under is a hint for the WM to keep the content of windows behind to avoid repaint.
- if (wd.menu_type) {
+ if (wd.is_popup || wd.no_focus) {
windowAttributes.override_redirect = True;
windowAttributes.save_under = True;
valuemask |= CWOverrideRedirect | CWSaveUnder;
@@ -4146,7 +4283,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
// Enable receiving notification when the window is initialized (MapNotify)
// so the focus can be set at the right time.
- if (wd.menu_type && !wd.no_focus) {
+ if (!wd.no_focus && !wd.is_popup) {
XSelectInput(x11_display, wd.x11_window, StructureNotifyMask);
}
@@ -4243,7 +4380,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
}
}
- if (wd.menu_type) {
+ if (wd.is_popup || wd.no_focus) {
// Set Utility type to disable fade animations.
Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False);
Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False);
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
index 2d07361deb49..63d32d939d3b 100644
--- a/platform/linuxbsd/display_server_x11.h
+++ b/platform/linuxbsd/display_server_x11.h
@@ -133,7 +133,6 @@ class DisplayServerX11 : public DisplayServer {
ObjectID instance_id;
- bool menu_type = false;
bool no_focus = false;
//better to guess on the fly, given WM can change it
@@ -145,12 +144,21 @@ class DisplayServerX11 : public DisplayServer {
Vector2i last_position_before_fs;
bool focused = true;
bool minimized = false;
+ bool is_popup = false;
+
+ Rect2i parent_safe_rect;
unsigned int focus_order = 0;
};
Map windows;
+ unsigned int last_mouse_monitor_mask = 0;
+ Vector2i last_mouse_monitor_pos;
+ uint64_t time_since_popup = 0;
+
+ List popup_list;
+
WindowID last_focused_window = INVALID_WINDOW_ID;
WindowID window_id_counter = MAIN_WINDOW_ID;
@@ -283,6 +291,10 @@ class DisplayServerX11 : public DisplayServer {
void _window_changed(XEvent *event);
public:
+ void mouse_process_popups();
+ void popup_open(WindowID p_window);
+ void popup_close(WindowID p_window);
+
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
@@ -317,6 +329,10 @@ class DisplayServerX11 : public DisplayServer {
virtual void show_window(WindowID p_id) override;
virtual void delete_sub_window(WindowID p_id) override;
+ virtual WindowID window_get_active_popup() const override;
+ virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override;
+ virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override;
+
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h
index 2b57983ca7da..036e74c47c48 100644
--- a/platform/osx/display_server_osx.h
+++ b/platform/osx/display_server_osx.h
@@ -104,8 +104,14 @@ class DisplayServerOSX : public DisplayServer {
bool borderless = false;
bool resize_disabled = false;
bool no_focus = false;
+ bool is_popup = false;
+
+ Rect2i parent_safe_rect;
};
+ List popup_list;
+ uint64_t time_since_popup = 0;
+
private:
#if defined(GLES3_ENABLED)
GLManager_OSX *gl_manager = nullptr;
@@ -197,6 +203,9 @@ class DisplayServerOSX : public DisplayServer {
void push_to_key_event_buffer(const KeyEvent &p_event);
void update_im_text(const Point2i &p_selection, const String &p_text);
void set_last_focused_window(WindowID p_window);
+ void mouse_process_popups(bool p_close = false);
+ void popup_open(WindowID p_window);
+ void popup_close(WindowID p_window);
void window_update(WindowID p_window);
void window_destroy(WindowID p_window);
@@ -259,6 +268,10 @@ class DisplayServerOSX : public DisplayServer {
virtual void show_window(WindowID p_id) override;
virtual void delete_sub_window(WindowID p_id) override;
+ virtual WindowID window_get_active_popup() const override;
+ virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override;
+ virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override;
+
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index b2201eabbc3a..23f37a8e18a4 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -324,27 +324,39 @@
Variant ret;
Callable::CallError ce;
+ {
+ List::Element *E = popup_list.front();
+ if (E && Object::cast_to(*p_event)) {
+ // Redirect keyboard input to active popup.
+ if (windows.has(E->get())) {
+ Callable callable = windows[E->get()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+ in_dispatch_input_event = false;
+ return;
+ }
+ }
+
Ref event_from_window = p_event;
if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
// Send to a window.
if (windows.has(event_from_window->get_window_id())) {
Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
- if (callable.is_null()) {
- return;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
}
- callable.call((const Variant **)&evp, 1, ret, ce);
}
} else {
// Send to all windows.
- for (Map::Element *E = windows.front(); E; E = E->next()) {
- Callable callable = E->get().input_event_callback;
- if (callable.is_null()) {
- continue;
+ for (KeyValue &E : windows) {
+ Callable callable = E.value.input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
}
- callable.call((const Variant **)&evp, 1, ret, ce);
}
}
-
in_dispatch_input_event = false;
}
}
@@ -513,6 +525,9 @@
}
void DisplayServerOSX::send_event(NSEvent *p_event) {
+ if ([p_event type] == NSEventTypeLeftMouseDown || [p_event type] == NSEventTypeRightMouseDown || [p_event type] == NSEventTypeOtherMouseDown) {
+ mouse_process_popups();
+ }
// Special case handling of command-period, which is traditionally a special
// shortcut in macOS and doesn't arrive at our regular keyDown handler.
if ([p_event type] == NSEventTypeKeyDown) {
@@ -1366,7 +1381,8 @@
void DisplayServerOSX::show_window(WindowID p_id) {
WindowData &wd = windows[p_id];
- if (wd.no_focus) {
+ popup_open(p_id);
+ if (wd.no_focus || wd.is_popup) {
[wd.window_object orderFront:nil];
} else {
[wd.window_object makeKeyAndOrderFront:nil];
@@ -1809,7 +1825,7 @@
}
_update_window_style(wd);
if ([wd.window_object isVisible]) {
- if (wd.no_focus) {
+ if (wd.no_focus || wd.is_popup) {
[wd.window_object orderFront:nil];
} else {
[wd.window_object makeKeyAndOrderFront:nil];
@@ -1838,6 +1854,11 @@
case WINDOW_FLAG_NO_FOCUS: {
wd.no_focus = p_enabled;
} break;
+ case WINDOW_FLAG_POPUP: {
+ ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
+ ERR_FAIL_COND_MSG([wd.window_object isVisible] && (wd.is_popup != p_enabled), "Pupup flag can't changed while window is opened.");
+ wd.is_popup = p_enabled;
+ } break;
default: {
}
}
@@ -1869,6 +1890,9 @@
case WINDOW_FLAG_NO_FOCUS: {
return wd.no_focus;
} break;
+ case WINDOW_FLAG_POPUP: {
+ return wd.is_popup;
+ } break;
default: {
}
}
@@ -1888,7 +1912,7 @@
const WindowData &wd = windows[p_window];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
- if (wd.no_focus) {
+ if (wd.no_focus || wd.is_popup) {
[wd.window_object orderFront:nil];
} else {
[wd.window_object makeKeyAndOrderFront:nil];
@@ -2446,6 +2470,120 @@
register_create_function("osx", create_func, get_rendering_drivers_func);
}
+DisplayServer::WindowID DisplayServerOSX::window_get_active_popup() const {
+ const List::Element *E = popup_list.back();
+ if (E) {
+ return E->get();
+ } else {
+ return INVALID_WINDOW_ID;
+ }
+}
+
+void DisplayServerOSX::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.parent_safe_rect = p_rect;
+}
+
+Rect2i DisplayServerOSX::window_get_popup_safe_rect(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());
+ const WindowData &wd = windows[p_window];
+ return wd.parent_safe_rect;
+}
+
+void DisplayServerOSX::popup_open(WindowID p_window) {
+ WindowData &wd = windows[p_window];
+ if (wd.is_popup) {
+ bool was_empty = popup_list.is_empty();
+ // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ List::Element *E = popup_list.back();
+ while (E) {
+ if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
+ send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ } else {
+ break;
+ }
+ }
+
+ if (was_empty && popup_list.is_empty()) {
+ // Inform OS that popup was opened, to close other native popups.
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.beginMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
+ }
+ time_since_popup = OS::get_singleton()->get_ticks_msec();
+ popup_list.push_back(p_window);
+ }
+}
+
+void DisplayServerOSX::popup_close(WindowID p_window) {
+ bool was_empty = popup_list.is_empty();
+ List::Element *E = popup_list.find(p_window);
+ while (E) {
+ send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->next();
+ popup_list.erase(E);
+ E = F;
+ }
+ if (!was_empty && popup_list.is_empty()) {
+ // Inform OS that all popups are closed.
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
+ }
+}
+
+void DisplayServerOSX::mouse_process_popups(bool p_close) {
+ _THREAD_SAFE_METHOD_
+
+ bool was_empty = popup_list.is_empty();
+ if (p_close) {
+ // Close all popups.
+ List::Element *E = popup_list.front();
+ while (E) {
+ send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->next();
+ popup_list.erase(E);
+ E = F;
+ }
+ if (!was_empty) {
+ // Inform OS that all popups are closed.
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
+ }
+ } else {
+ uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
+ if (delta < 250) {
+ return;
+ }
+
+ Point2i pos = mouse_get_position();
+ List::Element *E = popup_list.back();
+ while (E) {
+ // Popup window area.
+ Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
+ // Area of the parent window, which responsible for opening sub-menu.
+ Rect2i safe_rect = window_get_popup_safe_rect(E->get());
+ if (win_rect.has_point(pos)) {
+ break;
+ } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
+ break;
+ } else {
+ send_window_event(windows[E->get()], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ }
+ }
+ if (!was_empty && popup_list.is_empty()) {
+ // Inform OS that all popups are closed.
+ [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.apple.HIToolbox.endMenuTrackingNotification" object:@"org.godotengine.godot.popup_window"];
+ }
+ }
+}
+
DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
diff --git a/platform/osx/godot_application_delegate.mm b/platform/osx/godot_application_delegate.mm
index be284ba543e5..dc82075c44e8 100644
--- a/platform/osx/godot_application_delegate.mm
+++ b/platform/osx/godot_application_delegate.mm
@@ -68,6 +68,10 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notice {
}
- (void)applicationDidResignActive:(NSNotification *)notification {
+ DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton();
+ if (ds) {
+ ds->mouse_process_popups(true);
+ }
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
}
diff --git a/platform/osx/godot_content_view.mm b/platform/osx/godot_content_view.mm
index 76d9cfb081c5..e96f0a809826 100644
--- a/platform/osx/godot_content_view.mm
+++ b/platform/osx/godot_content_view.mm
@@ -281,7 +281,7 @@ - (BOOL)canBecomeKeyView {
}
DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
- return !wd.no_focus;
+ return !wd.no_focus && !wd.is_popup;
}
- (BOOL)acceptsFirstResponder {
diff --git a/platform/osx/godot_window.mm b/platform/osx/godot_window.mm
index 772a2ddb9f9c..d43853a94b27 100644
--- a/platform/osx/godot_window.mm
+++ b/platform/osx/godot_window.mm
@@ -52,7 +52,7 @@ - (BOOL)canBecomeKeyWindow {
}
DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
- return !wd.no_focus;
+ return !wd.no_focus && !wd.is_popup;
}
- (BOOL)canBecomeMainWindow {
@@ -63,7 +63,7 @@ - (BOOL)canBecomeMainWindow {
}
DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
- return !wd.no_focus;
+ return !wd.no_focus && !wd.is_popup;
}
@end
diff --git a/platform/osx/godot_window_delegate.mm b/platform/osx/godot_window_delegate.mm
index 1742be987d75..dbc244650e1b 100644
--- a/platform/osx/godot_window_delegate.mm
+++ b/platform/osx/godot_window_delegate.mm
@@ -54,6 +54,8 @@ - (void)windowWillClose:(NSNotification *)notification {
return;
}
+ ds->popup_close(window_id);
+
DisplayServerOSX::WindowData &wd = ds->get_window(window_id);
while (wd.transient_children.size()) {
ds->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID);
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 6b4b342389fc..163f5c350b75 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -546,6 +546,9 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
wd.no_focus = true;
}
+ if (p_flags & WINDOW_FLAG_POPUP_BIT) {
+ wd.is_popup = true;
+ }
// Inherit icons from MAIN_WINDOW for all sub windows.
HICON mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_SMALL, 0);
@@ -563,13 +566,14 @@ void DisplayServerWindows::show_window(WindowID p_id) {
ERR_FAIL_COND(!windows.has(p_id));
WindowData &wd = windows[p_id];
+ popup_open(p_id);
if (p_id != MAIN_WINDOW_ID) {
_update_window_style(p_id);
}
- ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window.
- if (!wd.no_focus) {
+ ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window.
+ if (!wd.no_focus && !wd.is_popup) {
SetForegroundWindow(wd.hWnd); // Slightly higher priority.
SetFocus(wd.hWnd); // Set keyboard focus.
}
@@ -581,6 +585,8 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window cannot be deleted.");
+ popup_close(p_window);
+
WindowData &wd = windows[p_window];
while (wd.transient_children.size()) {
@@ -1019,6 +1025,7 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
r_style_ex = WS_EX_WINDOWEDGE;
if (p_main_window) {
r_style_ex |= WS_EX_APPWINDOW;
+ r_style |= WS_VISIBLE;
}
if (p_fullscreen || p_borderless) {
@@ -1037,11 +1044,15 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
r_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
}
}
- r_style |= WS_VISIBLE;
if (p_no_activate_focus) {
r_style_ex |= WS_EX_TOPMOST | WS_EX_NOACTIVATE;
}
+
+ if (!p_borderless && !p_no_activate_focus) {
+ r_style |= WS_VISIBLE;
+ }
+
r_style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
r_style_ex |= WS_EX_ACCEPTFILES;
}
@@ -1055,12 +1066,12 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain
DWORD style = 0;
DWORD style_ex = 0;
- _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex);
+ _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus || wd.is_popup, style, style_ex);
SetWindowLongPtr(wd.hWnd, GWL_STYLE, style);
SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex);
- SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | (wd.no_focus ? SWP_NOACTIVATE : 0));
+ SetWindowPos(wd.hWnd, wd.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | ((wd.no_focus || wd.is_popup) ? SWP_NOACTIVATE : 0));
if (p_repaint) {
RECT rect;
@@ -1211,6 +1222,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
wd.borderless = p_enabled;
_update_window_style(p_window);
_update_window_mouse_passthrough(p_window);
+ ShowWindow(wd.hWnd, (wd.no_focus || wd.is_popup) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show the window.
} break;
case WINDOW_FLAG_ALWAYS_ON_TOP: {
ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top");
@@ -1224,6 +1236,11 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W
wd.no_focus = p_enabled;
_update_window_style(p_window);
} break;
+ case WINDOW_FLAG_POPUP: {
+ ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup.");
+ ERR_FAIL_COND_MSG(IsWindowVisible(wd.hWnd) && (wd.is_popup != p_enabled), "Pupup flag can't changed while window is opened.");
+ wd.is_popup = p_enabled;
+ } break;
case WINDOW_FLAG_MAX:
break;
}
@@ -1250,6 +1267,9 @@ bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window
case WINDOW_FLAG_NO_FOCUS: {
return wd.no_focus;
} break;
+ case WINDOW_FLAG_POPUP: {
+ return wd.is_popup;
+ } break;
case WINDOW_FLAG_MAX:
break;
}
@@ -1278,7 +1298,9 @@ void DisplayServerWindows::window_move_to_foreground(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
- SetForegroundWindow(wd.hWnd);
+ if (!wd.no_focus && !wd.is_popup) {
+ SetForegroundWindow(wd.hWnd);
+ }
}
bool DisplayServerWindows::window_can_draw(WindowID p_window) const {
@@ -1989,33 +2011,145 @@ void DisplayServerWindows::_dispatch_input_event(const Ref &p_event)
Variant ret;
Callable::CallError ce;
+ {
+ List::Element *E = popup_list.front();
+ if (E && Object::cast_to(*p_event)) {
+ // Redirect keyboard input to active popup.
+ if (windows.has(E->get())) {
+ Callable callable = windows[E->get()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
+ }
+ in_dispatch_input_event = false;
+ return;
+ }
+ }
+
Ref event_from_window = p_event;
if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) {
// Send to a single window.
- if (!windows.has(event_from_window->get_window_id())) {
- in_dispatch_input_event = false;
- ERR_FAIL_MSG("DisplayServerWindows: Invalid window id in input event.");
- }
- Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
- if (callable.is_null()) {
- in_dispatch_input_event = false;
- return;
+ if (windows.has(event_from_window->get_window_id())) {
+ Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
+ }
}
- callable.call((const Variant **)&evp, 1, ret, ce);
} else {
// Send to all windows.
for (const KeyValue &E : windows) {
const Callable callable = E.value.input_event_callback;
- if (callable.is_null()) {
- continue;
+ if (callable.is_valid()) {
+ callable.call((const Variant **)&evp, 1, ret, ce);
}
- callable.call((const Variant **)&evp, 1, ret, ce);
}
}
in_dispatch_input_event = false;
}
+LRESULT CALLBACK MouseProc(int code, WPARAM wParam, LPARAM lParam) {
+ DisplayServerWindows *ds_win = static_cast(DisplayServer::get_singleton());
+ if (ds_win) {
+ return ds_win->MouseProc(code, wParam, lParam);
+ } else {
+ return ::CallNextHookEx(nullptr, code, wParam, lParam);
+ }
+}
+
+DisplayServer::WindowID DisplayServerWindows::window_get_active_popup() const {
+ const List::Element *E = popup_list.back();
+ if (E) {
+ return E->get();
+ } else {
+ return INVALID_WINDOW_ID;
+ }
+}
+
+void DisplayServerWindows::window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND(!windows.has(p_window));
+ WindowData &wd = windows[p_window];
+ wd.parent_safe_rect = p_rect;
+}
+
+Rect2i DisplayServerWindows::window_get_popup_safe_rect(WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ ERR_FAIL_COND_V(!windows.has(p_window), Rect2i());
+ const WindowData &wd = windows[p_window];
+ return wd.parent_safe_rect;
+}
+
+void DisplayServerWindows::popup_open(WindowID p_window) {
+ WindowData &wd = windows[p_window];
+ if (wd.is_popup) {
+ // Close all popups, up to current popup parent, or every popup if new window is not transient.
+ List::Element *E = popup_list.back();
+ while (E) {
+ if (wd.transient_parent != E->get() || wd.transient_parent == INVALID_WINDOW_ID) {
+ _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ } else {
+ break;
+ }
+ }
+
+ time_since_popup = OS::get_singleton()->get_ticks_msec();
+ popup_list.push_back(p_window);
+ }
+}
+
+void DisplayServerWindows::popup_close(WindowID p_window) {
+ List::Element *E = popup_list.find(p_window);
+ while (E) {
+ _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->next();
+ popup_list.erase(E);
+ E = F;
+ }
+}
+
+LRESULT DisplayServerWindows::MouseProc(int code, WPARAM wParam, LPARAM lParam) {
+ _THREAD_SAFE_METHOD_
+ uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_popup;
+ if (delta > 250) {
+ switch (wParam) {
+ case WM_NCLBUTTONDOWN:
+ case WM_NCRBUTTONDOWN:
+ case WM_NCMBUTTONDOWN:
+ case WM_LBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ case WM_MBUTTONDOWN: {
+ MOUSEHOOKSTRUCT *ms = (MOUSEHOOKSTRUCT *)lParam;
+ Point2i pos = Point2i(ms->pt.x, ms->pt.y);
+ List::Element *E = popup_list.back();
+ while (E) {
+ // Popup window area.
+ Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get()));
+ // Area of the parent window, which responsible for opening sub-menu.
+ Rect2i safe_rect = window_get_popup_safe_rect(E->get());
+ if (win_rect.has_point(pos)) {
+ break;
+ } else if (safe_rect != Rect2i() && safe_rect.has_point(pos)) {
+ break;
+ } else {
+ _send_window_event(windows[E->get()], DisplayServerWindows::WINDOW_EVENT_CLOSE_REQUEST);
+ List::Element *F = E->prev();
+ popup_list.erase(E);
+ E = F;
+ }
+ }
+
+ } break;
+ }
+ }
+ return ::CallNextHookEx(mouse_monitor, code, wParam, lParam);
+}
+
// Our default window procedure to handle processing of window-related system messages/events.
// Also known as DefProc or DefWindowProc.
// See: https://docs.microsoft.com/en-us/windows/win32/winmsg/window-procedures
@@ -2048,6 +2182,13 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
// Process window messages.
switch (uMsg) {
+ case WM_MOUSEACTIVATE: {
+ if (windows[window_id].no_focus) {
+ return MA_NOACTIVATEANDEAT; // Do not activate, and discard mouse messages.
+ } else if (windows[window_id].is_popup) {
+ return MA_NOACTIVATE; // Do not activate, but process mouse messages.
+ }
+ } break;
case WM_SETFOCUS: {
windows[window_id].window_has_focus = true;
last_focused_window = window_id;
@@ -2309,7 +2450,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
old_x = mm->get_position().x;
old_y = mm->get_position().y;
- if (windows[window_id].window_has_focus) {
+ if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
}
}
@@ -2451,7 +2592,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
old_x = mm->get_position().x;
old_y = mm->get_position().y;
- if (windows[window_id].window_has_focus) {
+ if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
}
@@ -2551,7 +2692,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y)));
old_x = mm->get_position().x;
old_y = mm->get_position().y;
- if (windows[window_id].window_has_focus) {
+ if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) {
Input::get_singleton()->parse_input_event(mm);
}
@@ -3462,6 +3603,8 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
#endif
+ HHOOK mouse_monitor = SetWindowsHookEx(WH_MOUSE, ::MouseProc, nullptr, GetCurrentThreadId());
+
Point2i window_position(
(screen_get_size(0).width - p_resolution.width) / 2,
(screen_get_size(0).height - p_resolution.height) / 2);
@@ -3545,6 +3688,10 @@ DisplayServerWindows::~DisplayServerWindows() {
cursors_cache.clear();
+ if (mouse_monitor) {
+ UnhookWindowsHookEx(mouse_monitor);
+ }
+
if (user_proc) {
SetWindowLongPtr(windows[MAIN_WINDOW_ID].hWnd, GWLP_WNDPROC, (LONG_PTR)user_proc);
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 7561f9bb7708..a56a2b83ac11 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -387,9 +387,15 @@ class DisplayServerWindows : public DisplayServer {
WindowID transient_parent = INVALID_WINDOW_ID;
Set transient_children;
+
+ bool is_popup = false;
+ Rect2i parent_safe_rect;
};
JoypadWindows *joypad;
+ HHOOK mouse_monitor = nullptr;
+ List popup_list;
+ uint64_t time_since_popup = 0;
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect);
WindowID window_id_counter = MAIN_WINDOW_ID;
@@ -440,6 +446,10 @@ class DisplayServerWindows : public DisplayServer {
public:
LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+ LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam);
+
+ void popup_open(WindowID p_window);
+ void popup_close(WindowID p_window);
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
@@ -474,6 +484,10 @@ class DisplayServerWindows : public DisplayServer {
virtual void show_window(WindowID p_window) override;
virtual void delete_sub_window(WindowID p_window) override;
+ virtual WindowID window_get_active_popup() const override;
+ virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect) override;
+ virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const override;
+
virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
index 4a5dc57e3668..24b91cd16a53 100644
--- a/scene/gui/popup.cpp
+++ b/scene/gui/popup.cpp
@@ -42,26 +42,30 @@ void Popup::_input_from_window(const Ref &p_event) {
}
void Popup::_initialize_visible_parents() {
- visible_parents.clear();
-
- Window *parent_window = this;
- while (parent_window) {
- parent_window = parent_window->get_parent_visible_window();
- if (parent_window) {
- visible_parents.push_back(parent_window);
- parent_window->connect("focus_entered", callable_mp(this, &Popup::_parent_focused));
- parent_window->connect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents));
+ if (is_embedded()) {
+ visible_parents.clear();
+
+ Window *parent_window = this;
+ while (parent_window) {
+ parent_window = parent_window->get_parent_visible_window();
+ if (parent_window) {
+ visible_parents.push_back(parent_window);
+ parent_window->connect("focus_entered", callable_mp(this, &Popup::_parent_focused));
+ parent_window->connect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents));
+ }
}
}
}
void Popup::_deinitialize_visible_parents() {
- for (uint32_t i = 0; i < visible_parents.size(); ++i) {
- visible_parents[i]->disconnect("focus_entered", callable_mp(this, &Popup::_parent_focused));
- visible_parents[i]->disconnect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents));
- }
+ if (is_embedded()) {
+ for (uint32_t i = 0; i < visible_parents.size(); ++i) {
+ visible_parents[i]->disconnect("focus_entered", callable_mp(this, &Popup::_parent_focused));
+ visible_parents[i]->disconnect("tree_exited", callable_mp(this, &Popup::_deinitialize_visible_parents));
+ }
- visible_parents.clear();
+ visible_parents.clear();
+ }
}
void Popup::_notification(int p_what) {
@@ -94,7 +98,7 @@ void Popup::_notification(int p_what) {
}
void Popup::_parent_focused() {
- if (popped_up && close_on_parent_focus) {
+ if (popped_up && get_flag(FLAG_POPUP)) {
_close_pressed();
}
}
@@ -111,19 +115,7 @@ void Popup::set_as_minsize() {
set_size(get_contents_minimum_size());
}
-void Popup::set_close_on_parent_focus(bool p_close) {
- close_on_parent_focus = p_close;
-}
-
-bool Popup::get_close_on_parent_focus() {
- return close_on_parent_focus;
-}
-
void Popup::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_close_on_parent_focus", "close"), &Popup::set_close_on_parent_focus);
- ClassDB::bind_method(D_METHOD("get_close_on_parent_focus"), &Popup::get_close_on_parent_focus);
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "close_on_parent_focus"), "set_close_on_parent_focus", "get_close_on_parent_focus");
-
ADD_SIGNAL(MethodInfo("popup_hide"));
}
@@ -184,6 +176,7 @@ Popup::Popup() {
set_transient(true);
set_flag(FLAG_BORDERLESS, true);
set_flag(FLAG_RESIZE_DISABLED, true);
+ set_flag(FLAG_POPUP, true);
connect("window_input", callable_mp(this, &Popup::_input_from_window));
}
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
index 5678043b23c4..a3c56c9ff1ff 100644
--- a/scene/gui/popup.h
+++ b/scene/gui/popup.h
@@ -42,7 +42,6 @@ class Popup : public Window {
LocalVector visible_parents;
bool popped_up = false;
- bool close_on_parent_focus = true;
void _input_from_window(const Ref &p_event);
@@ -61,9 +60,6 @@ class Popup : public Window {
public:
void set_as_minsize();
- void set_close_on_parent_focus(bool p_close);
- bool get_close_on_parent_focus();
-
Popup();
~Popup();
};
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index deca1451ee26..af2edfa09038 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -205,7 +205,6 @@ void PopupMenu::_activate_submenu(int p_over) {
submenu_pos.x = this_pos.x - submenu_size.width;
}
- submenu_popup->set_close_on_parent_focus(false);
submenu_popup->set_position(submenu_pos);
PopupMenu *submenu_pum = Object::cast_to(submenu_popup);
@@ -223,6 +222,11 @@ void PopupMenu::_activate_submenu(int p_over) {
// Set autohide areas.
+ Rect2 safe_area = this_rect;
+ safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2;
+ safe_area.size.y = items[p_over]._height_cache;
+ DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area);
+
// Make the position of the parent popup relative to submenu popup.
this_rect.position = this_rect.position - submenu_pum->get_position();
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index ca817b17bcf2..de6aa2b1394d 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1239,6 +1239,7 @@ void Viewport::_gui_show_tooltip() {
panel->set_transient(true);
panel->set_flag(Window::FLAG_NO_FOCUS, true);
+ panel->set_flag(Window::FLAG_POPUP, false);
panel->set_wrap_controls(true);
panel->add_child(base_tooltip);
@@ -1268,7 +1269,10 @@ void Viewport::_gui_show_tooltip() {
gui.tooltip_popup->set_position(r.position);
gui.tooltip_popup->set_size(r.size);
- gui.tooltip_popup->show();
+ DisplayServer::WindowID active_popup = DisplayServer::get_singleton()->window_get_active_popup();
+ if (active_popup == DisplayServer::INVALID_WINDOW_ID || active_popup == window->get_window_id()) {
+ gui.tooltip_popup->show();
+ }
gui.tooltip_popup->child_controls_changed();
}
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 0ce556d36c6c..6eccb4da9a70 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -951,7 +951,7 @@ bool Window::_can_consume_input_events() const {
void Window::_window_input(const Ref &p_ev) {
if (EngineDebugger::is_active()) {
- //quit from game window using F8
+ // Quit from game window using F8.
Ref k = p_ev;
if (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::F8) {
EngineDebugger::get_singleton()->send_message("request_quit", Array());
@@ -959,15 +959,7 @@ void Window::_window_input(const Ref &p_ev) {
}
if (exclusive_child != nullptr) {
- /*
- Window *focus_target = exclusive_child;
- focus_target->grab_focus();
- while (focus_target->exclusive_child != nullptr) {
- focus_target = focus_target->exclusive_child;
- focus_target->grab_focus();
- }*/
-
- if (!is_embedding_subwindows()) { //not embedding, no need for event
+ if (!is_embedding_subwindows()) { // Not embedding, no need for event.
return;
}
}
@@ -1587,6 +1579,7 @@ void Window::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "always_on_top"), "set_flag", "get_flag", FLAG_ALWAYS_ON_TOP);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "transparent"), "set_flag", "get_flag", FLAG_TRANSPARENT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "unfocusable"), "set_flag", "get_flag", FLAG_NO_FOCUS);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "popup_window"), "set_flag", "get_flag", FLAG_POPUP);
ADD_GROUP("Limits", "");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "min_size"), "set_min_size", "get_min_size");
@@ -1630,6 +1623,7 @@ void Window::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_ALWAYS_ON_TOP);
BIND_ENUM_CONSTANT(FLAG_TRANSPARENT);
BIND_ENUM_CONSTANT(FLAG_NO_FOCUS);
+ BIND_ENUM_CONSTANT(FLAG_POPUP);
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(CONTENT_SCALE_MODE_DISABLED);
diff --git a/scene/main/window.h b/scene/main/window.h
index f37689f9056b..3d8e337b4a15 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -55,6 +55,7 @@ class Window : public Viewport {
FLAG_ALWAYS_ON_TOP = DisplayServer::WINDOW_FLAG_ALWAYS_ON_TOP,
FLAG_TRANSPARENT = DisplayServer::WINDOW_FLAG_TRANSPARENT,
FLAG_NO_FOCUS = DisplayServer::WINDOW_FLAG_NO_FOCUS,
+ FLAG_POPUP = DisplayServer::WINDOW_FLAG_POPUP,
FLAG_MAX = DisplayServer::WINDOW_FLAG_MAX,
};
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 58a51e3aea12..819c1510872b 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -399,6 +399,9 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("delete_sub_window", "window_id"), &DisplayServer::delete_sub_window);
ClassDB::bind_method(D_METHOD("window_get_native_handle", "handle_type", "window_id"), &DisplayServer::window_get_native_handle, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_get_active_popup"), &DisplayServer::window_get_active_popup);
+ ClassDB::bind_method(D_METHOD("window_set_popup_safe_rect", "window", "rect"), &DisplayServer::window_set_popup_safe_rect);
+ ClassDB::bind_method(D_METHOD("window_get_popup_safe_rect", "window"), &DisplayServer::window_get_popup_safe_rect);
ClassDB::bind_method(D_METHOD("window_set_title", "title", "window_id"), &DisplayServer::window_set_title, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_set_mouse_passthrough", "region", "window_id"), &DisplayServer::window_set_mouse_passthrough, DEFVAL(MAIN_WINDOW_ID));
@@ -552,6 +555,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_FLAG_ALWAYS_ON_TOP);
BIND_ENUM_CONSTANT(WINDOW_FLAG_TRANSPARENT);
BIND_ENUM_CONSTANT(WINDOW_FLAG_NO_FOCUS);
+ BIND_ENUM_CONSTANT(WINDOW_FLAG_POPUP);
BIND_ENUM_CONSTANT(WINDOW_FLAG_MAX);
BIND_ENUM_CONSTANT(WINDOW_EVENT_MOUSE_ENTER);
diff --git a/servers/display_server.h b/servers/display_server.h
index 81ac551f5723..67dbab09248e 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -226,6 +226,7 @@ class DisplayServer : public Object {
WINDOW_FLAG_ALWAYS_ON_TOP,
WINDOW_FLAG_TRANSPARENT,
WINDOW_FLAG_NO_FOCUS,
+ WINDOW_FLAG_POPUP,
WINDOW_FLAG_MAX,
};
@@ -235,13 +236,18 @@ class DisplayServer : public Object {
WINDOW_FLAG_BORDERLESS_BIT = (1 << WINDOW_FLAG_BORDERLESS),
WINDOW_FLAG_ALWAYS_ON_TOP_BIT = (1 << WINDOW_FLAG_ALWAYS_ON_TOP),
WINDOW_FLAG_TRANSPARENT_BIT = (1 << WINDOW_FLAG_TRANSPARENT),
- WINDOW_FLAG_NO_FOCUS_BIT = (1 << WINDOW_FLAG_NO_FOCUS)
+ WINDOW_FLAG_NO_FOCUS_BIT = (1 << WINDOW_FLAG_NO_FOCUS),
+ WINDOW_FLAG_POPUP_BIT = (1 << WINDOW_FLAG_POPUP),
};
virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i());
virtual void show_window(WindowID p_id);
virtual void delete_sub_window(WindowID p_id);
+ virtual WindowID window_get_active_popup() const { return INVALID_WINDOW_ID; };
+ virtual void window_set_popup_safe_rect(WindowID p_window, const Rect2i &p_rect){};
+ virtual Rect2i window_get_popup_safe_rect(WindowID p_window) const { return Rect2i(); };
+
virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const;
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const = 0;