1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-13 13:31:48 +00:00

Improve popup window handling.

Add window FLAG_POPUP and a platform specific routines to control popup auto-hiding and event forwarding.
This commit is contained in:
bruvzg
2022-02-24 11:21:23 +02:00
parent 80baa1386a
commit 74ff5921d6
22 changed files with 617 additions and 113 deletions

View File

@@ -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<InputEvent> &p_event) {
Variant ret;
Callable::CallError ce;
Ref<InputEventFromWindow> 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()) {
{
List<WindowID>::Element *E = popup_list.front();
if (E && Object::cast_to<InputEventKey>(*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;
}
callable.call((const Variant **)&evp, 1, ret, ce);
}
Ref<InputEventFromWindow> 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())) {
Callable callable = windows[event_from_window->get_window_id()].input_event_callback;
if (callable.is_valid()) {
callable.call((const Variant **)&evp, 1, ret, ce);
}
}
} else {
//send to all windows
// Send to all windows.
for (KeyValue<WindowID, WindowData> &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<XEvent> &r_events) {
}
}
DisplayServer::WindowID DisplayServerX11::window_get_active_popup() const {
const List<WindowID>::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<WindowID>::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<WindowID>::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<WindowID>::Element *E = popup_list.find(p_window);
while (E) {
_send_window_event(windows[E->get()], DisplayServerX11::WINDOW_EVENT_CLOSE_REQUEST);
List<WindowID>::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<WindowID>::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<WindowID>::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,13 +4259,12 @@ 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.no_focus = 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:
@@ -4135,7 +4272,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
// 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);