You've already forked godot
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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user