From 34b34b104cd25c0e36b09ff2c666c3e64719f262 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Sun, 15 Jan 2023 12:05:25 +0200 Subject: [PATCH] Add `WINDOW_FLAG_MOUSE_PASSTHROUGH` flag and enabled it for tooltips. Expose `window_set_mouse_passthrough` to `Window`. --- doc/classes/DisplayServer.xml | 5 ++- doc/classes/Window.xml | 37 +++++++++++++++++++- platform/linuxbsd/x11/display_server_x11.cpp | 33 ++++++++++++----- platform/linuxbsd/x11/display_server_x11.h | 4 +++ platform/macos/display_server_macos.h | 1 + platform/macos/display_server_macos.mm | 12 ++++++- platform/windows/display_server_windows.cpp | 17 ++++++++- platform/windows/display_server_windows.h | 1 + scene/main/viewport.cpp | 1 + scene/main/window.cpp | 20 +++++++++++ scene/main/window.h | 5 +++ servers/display_server.cpp | 1 + servers/display_server.h | 2 ++ 13 files changed, 127 insertions(+), 12 deletions(-) diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index 5da6cf81020..1dc4dd3ff07 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1694,7 +1694,10 @@ Use [method window_get_safe_title_margins] to determine area under the title bar that is not covered by decorations. [b]Note:[/b] This flag is implemented on macOS. - + + All mouse event as passed to the underlying window of the same application. + + Max value of the [enum WindowFlags]. diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 4cd6cf41daa..dd145c016c1 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -527,6 +527,39 @@ Set's the window's current mode. [b]Note:[/b] Fullscreen mode is not exclusive full screen on Windows and Linux. + + If [code]true[/code], all mouse event as passed to the underlying window of the same application. See also [member mouse_passthrough_polygon]. + [b]Note:[/b] This property is implemented on Linux (X11), macOS and Windows. + + + Sets a polygonal region of the window which accepts mouse events. Mouse events outside the region will be passed through. + Passing an empty array will disable passthrough support (all mouse events will be intercepted by the window, which is the default behavior). + [codeblocks] + [gdscript] + # Set region, using Path2D node. + $Window.mouse_passthrough_polygon = $Path2D.curve.get_baked_points() + + # Set region, using Polygon2D node. + $Window.mouse_passthrough_polygon = $Polygon2D.polygon + + # Reset region to default. + $Window.mouse_passthrough_polygon = [] + [/gdscript] + [csharp] + // Set region, using Path2D node. + GetNode<Window>("Window").MousePassthrough = GetNode<Path2D>("Path2D").Curve.GetBakedPoints(); + + // Set region, using Polygon2D node. + GetNode<Window>("Window").MousePassthrough = GetNode<Polygon2D>("Polygon2D").Polygon; + + // Reset region to default. + GetNode<Window>("Window").MousePassthrough = new Vector2[] {}; + [/csharp] + [/codeblocks] + [b]Note:[/b] This property is ignored if [member mouse_passthrough] is set to [code]true[/code]. + [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]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). @@ -695,7 +728,9 @@ Window content is expanded to the full size of the window. Unlike borderless window, the frame is left intact and can be used to resize the window, title bar is transparent, but have minimize/maximize/close buttons. Set with [member extend_to_title]. [b]Note:[/b] This flag is implemented on macOS. - + + + Max value of the [enum Flags]. diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 10fd9972e36..a2c34bb874b 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -1420,13 +1420,22 @@ void DisplayServerX11::window_set_mouse_passthrough(const Vector &p_reg _THREAD_SAFE_METHOD_ ERR_FAIL_COND(!windows.has(p_window)); - const WindowData &wd = windows[p_window]; + windows[p_window].mpath = p_region; + _update_window_mouse_passthrough(p_window); +} + +void DisplayServerX11::_update_window_mouse_passthrough(WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + + const Vector region_path = windows[p_window].mpath; int event_base, error_base; const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base); if (ext_okay) { Region region; - if (p_region.size() == 0) { + if (windows[p_window].mpass) { + region = XCreateRegion(); + } else if (region_path.size() == 0) { region = XCreateRegion(); XRectangle rect; rect.x = 0; @@ -1435,15 +1444,15 @@ void DisplayServerX11::window_set_mouse_passthrough(const Vector &p_reg rect.height = window_get_size_with_decorations(p_window).y; XUnionRectWithRegion(&rect, region, region); } else { - XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * p_region.size()); - for (int i = 0; i < p_region.size(); i++) { - points[i].x = p_region[i].x; - points[i].y = p_region[i].y; + XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * region_path.size()); + for (int i = 0; i < region_path.size(); i++) { + points[i].x = region_path[i].x; + points[i].y = region_path[i].y; } - region = XPolygonRegion(points, p_region.size(), EvenOddRule); + region = XPolygonRegion(points, region_path.size(), EvenOddRule); memfree(points); } - XShapeCombineRegion(x11_display, wd.x11_window, ShapeInput, 0, 0, region, ShapeSet); + XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet); XDestroyRegion(region); } } @@ -2312,6 +2321,7 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo window_set_size(window_get_size(p_window), p_window); wd.borderless = p_enabled; + _update_window_mouse_passthrough(p_window); } break; case WINDOW_FLAG_ALWAYS_ON_TOP: { ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID, "Can't make a window transient if the 'on top' flag is active."); @@ -2345,6 +2355,10 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo case WINDOW_FLAG_NO_FOCUS: { wd.no_focus = p_enabled; } break; + case WINDOW_FLAG_MOUSE_PASSTHROUGH: { + wd.mpass = p_enabled; + _update_window_mouse_passthrough(p_window); + } break; case WINDOW_FLAG_POPUP: { XWindowAttributes xwa; XSync(x11_display, False); @@ -2398,6 +2412,9 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co case WINDOW_FLAG_NO_FOCUS: { return wd.no_focus; } break; + case WINDOW_FLAG_MOUSE_PASSTHROUGH: { + return wd.mpass; + } break; case WINDOW_FLAG_POPUP: { return wd.is_popup; } break; diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 43a66f95b27..437766d9537 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -149,6 +149,8 @@ class DisplayServerX11 : public DisplayServer { Callable input_text_callback; Callable drop_files_callback; + Vector mpath; + WindowID transient_parent = INVALID_WINDOW_ID; HashSet transient_children; @@ -169,6 +171,7 @@ class DisplayServerX11 : public DisplayServer { bool maximized = false; bool is_popup = false; bool layered_window = false; + bool mpass = false; Rect2i parent_safe_rect; @@ -245,6 +248,7 @@ class DisplayServerX11 : public DisplayServer { Atom _process_selection_request_target(Atom p_target, Window p_requestor, Atom p_property, Atom p_selection) const; void _handle_selection_request_event(XSelectionRequestEvent *p_event) const; + void _update_window_mouse_passthrough(WindowID p_window); String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; String _clipboard_get(Atom p_source, Window x11_window) const; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 7f57644a4cf..4873db4b642 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -114,6 +114,7 @@ public: bool resize_disabled = false; bool no_focus = false; bool is_popup = false; + bool mpass = false; Rect2i parent_safe_rect; }; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 865b8d9711a..63280c78877 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2974,6 +2974,9 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win case WINDOW_FLAG_NO_FOCUS: { wd.no_focus = p_enabled; } break; + case WINDOW_FLAG_MOUSE_PASSTHROUGH: { + wd.mpass = 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), "Popup flag can't changed while window is opened."); @@ -3013,6 +3016,9 @@ bool DisplayServerMacOS::window_get_flag(WindowFlags p_flag, WindowID p_window) case WINDOW_FLAG_NO_FOCUS: { return wd.no_focus; } break; + case WINDOW_FLAG_MOUSE_PASSTHROUGH: { + return wd.mpass; + } break; case WINDOW_FLAG_POPUP: { return wd.is_popup; } break; @@ -3487,7 +3493,11 @@ void DisplayServerMacOS::process_events() { for (KeyValue &E : windows) { WindowData &wd = E.value; - if (wd.mpath.size() > 0) { + if (wd.mpass) { + if (![wd.window_object ignoresMouseEvents]) { + [wd.window_object setIgnoresMouseEvents:YES]; + } + } else if (wd.mpath.size() > 0) { update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); if (Geometry2D::is_point_in_polygon(wd.mouse_pos, wd.mpath)) { if ([wd.window_object ignoresMouseEvents]) { diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 969bcc38412..914cd07b505 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -735,6 +735,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_MOUSE_PASSTHROUGH_BIT) { + wd.mpass = true; + } if (p_flags & WINDOW_FLAG_POPUP_BIT) { wd.is_popup = true; } @@ -918,7 +921,7 @@ void DisplayServerWindows::window_set_mouse_passthrough(const Vector &p void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); - if (windows[p_window].mpath.size() == 0) { + if (windows[p_window].mpass || windows[p_window].mpath.size() == 0) { SetWindowRgn(windows[p_window].hWnd, nullptr, TRUE); } else { POINT *points = (POINT *)memalloc(sizeof(POINT) * windows[p_window].mpath.size()); @@ -1499,6 +1502,10 @@ 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_MOUSE_PASSTHROUGH: { + wd.mpass = p_enabled; + _update_window_mouse_passthrough(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), "Popup flag can't changed while window is opened."); @@ -1530,6 +1537,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_MOUSE_PASSTHROUGH: { + return wd.mpass; + } break; case WINDOW_FLAG_POPUP: { return wd.is_popup; } break; @@ -2464,6 +2474,11 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Process window messages. switch (uMsg) { + case WM_NCHITTEST: { + if (windows[window_id].mpass) { + return HTTRANSPARENT; + } + } break; case WM_MOUSEACTIVATE: { if (windows[window_id].no_focus) { return MA_NOACTIVATEANDEAT; // Do not activate, and discard mouse messages. diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 84f2dee35e4..ce4b94af591 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -370,6 +370,7 @@ class DisplayServerWindows : public DisplayServer { bool window_has_focus = false; bool exclusive = false; bool context_created = false; + bool mpass = false; // Used to transfer data between events using timer. WPARAM saved_wparam; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 7ca1f9d3ec7..9c7e04c433a 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1256,6 +1256,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_flag(Window::FLAG_MOUSE_PASSTHROUGH, true); panel->set_wrap_controls(true); panel->add_child(base_tooltip); panel->gui_parent = this; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index c5dbfffd7bf..4baedc8c14d 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -489,6 +489,7 @@ void Window::_make_window() { ERR_FAIL_COND(window_id == DisplayServer::INVALID_WINDOW_ID); DisplayServer::get_singleton()->window_set_max_size(Size2i(), window_id); DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id); + DisplayServer::get_singleton()->window_set_mouse_passthrough(mpath, window_id); String tr_title = atr(title); #ifdef DEBUG_ENABLED if (window_id == DisplayServer::MAIN_WINDOW_ID) { @@ -1235,6 +1236,18 @@ DisplayServer::WindowID Window::get_window_id() const { return window_id; } +void Window::set_mouse_passthrough_polygon(const Vector &p_region) { + mpath = p_region; + if (window_id == DisplayServer::INVALID_WINDOW_ID) { + return; + } + DisplayServer::get_singleton()->window_set_mouse_passthrough(mpath, window_id); +} + +Vector Window::get_mouse_passthrough_polygon() const { + return mpath; +} + void Window::set_wrap_controls(bool p_enable) { wrap_controls = p_enable; if (wrap_controls) { @@ -2163,6 +2176,9 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_font_oversampling", "enable"), &Window::set_use_font_oversampling); ClassDB::bind_method(D_METHOD("is_using_font_oversampling"), &Window::is_using_font_oversampling); + ClassDB::bind_method(D_METHOD("set_mouse_passthrough_polygon", "polygon"), &Window::set_mouse_passthrough_polygon); + ClassDB::bind_method(D_METHOD("get_mouse_passthrough_polygon"), &Window::get_mouse_passthrough_polygon); + ClassDB::bind_method(D_METHOD("set_wrap_controls", "enable"), &Window::set_wrap_controls); ClassDB::bind_method(D_METHOD("is_wrapping_controls"), &Window::is_wrapping_controls); ClassDB::bind_method(D_METHOD("child_controls_changed"), &Window::child_controls_changed); @@ -2243,6 +2259,8 @@ void Window::_bind_methods() { } ADD_PROPERTY(PropertyInfo(Variant::INT, "current_screen", PROPERTY_HINT_ENUM, screen_hints), "set_current_screen", "get_current_screen"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "mouse_passthrough_polygon"), "set_mouse_passthrough_polygon", "get_mouse_passthrough_polygon"); + ADD_GROUP("Flags", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wrap_controls"), "set_wrap_controls", "is_wrapping_controls"); @@ -2255,6 +2273,7 @@ void Window::_bind_methods() { 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_PROPERTYI(PropertyInfo(Variant::BOOL, "extend_to_title"), "set_flag", "get_flag", FLAG_EXTEND_TO_TITLE); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "mouse_passthrough"), "set_flag", "get_flag", FLAG_MOUSE_PASSTHROUGH); ADD_GROUP("Limits", ""); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "min_size", PROPERTY_HINT_NONE, "suffix:px"), "set_min_size", "get_min_size"); @@ -2302,6 +2321,7 @@ void Window::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_NO_FOCUS); BIND_ENUM_CONSTANT(FLAG_POPUP); BIND_ENUM_CONSTANT(FLAG_EXTEND_TO_TITLE); + BIND_ENUM_CONSTANT(FLAG_MOUSE_PASSTHROUGH); 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 b4887437a18..d819ca7c606 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -59,6 +59,7 @@ public: FLAG_NO_FOCUS = DisplayServer::WINDOW_FLAG_NO_FOCUS, FLAG_POPUP = DisplayServer::WINDOW_FLAG_POPUP, FLAG_EXTEND_TO_TITLE = DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, + FLAG_MOUSE_PASSTHROUGH = DisplayServer::WINDOW_FLAG_MOUSE_PASSTHROUGH, FLAG_MAX = DisplayServer::WINDOW_FLAG_MAX, }; @@ -101,6 +102,7 @@ private: mutable Size2i size = Size2i(DEFAULT_WINDOW_SIZE, DEFAULT_WINDOW_SIZE); mutable Size2i min_size; mutable Size2i max_size; + mutable Vector mpath; mutable Mode mode = MODE_WINDOWED; mutable bool flags[FLAG_MAX] = {}; bool visible = true; @@ -281,6 +283,9 @@ public: void set_use_font_oversampling(bool p_oversampling); bool is_using_font_oversampling() const; + void set_mouse_passthrough_polygon(const Vector &p_region); + Vector get_mouse_passthrough_polygon() const; + void set_wrap_controls(bool p_enable); bool is_wrapping_controls() const; void child_controls_changed(); diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 82343e04020..39f14439f4d 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -848,6 +848,7 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(WINDOW_FLAG_NO_FOCUS); BIND_ENUM_CONSTANT(WINDOW_FLAG_POPUP); BIND_ENUM_CONSTANT(WINDOW_FLAG_EXTEND_TO_TITLE); + BIND_ENUM_CONSTANT(WINDOW_FLAG_MOUSE_PASSTHROUGH); 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 f8ade60aca0..aa30b25b654 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -311,6 +311,7 @@ public: WINDOW_FLAG_NO_FOCUS, WINDOW_FLAG_POPUP, WINDOW_FLAG_EXTEND_TO_TITLE, + WINDOW_FLAG_MOUSE_PASSTHROUGH, WINDOW_FLAG_MAX, }; @@ -323,6 +324,7 @@ public: WINDOW_FLAG_NO_FOCUS_BIT = (1 << WINDOW_FLAG_NO_FOCUS), WINDOW_FLAG_POPUP_BIT = (1 << WINDOW_FLAG_POPUP), WINDOW_FLAG_EXTEND_TO_TITLE_BIT = (1 << WINDOW_FLAG_EXTEND_TO_TITLE), + WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT = (1 << WINDOW_FLAG_MOUSE_PASSTHROUGH), }; virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i());