Skip to content

Commit

Permalink
Add mouse passthrough rects and polygons
Browse files Browse the repository at this point in the history
  • Loading branch information
arlo-phoenix committed Jun 2, 2024
1 parent 705b7a0 commit b01b308
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 65 deletions.
11 changes: 9 additions & 2 deletions doc/classes/DisplayServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1657,9 +1657,9 @@
[b]Note:[/b] Setting the window to full screen forcibly sets the borderless flag to [code]true[/code], so make sure to set it back to [code]false[/code] when not wanted.
</description>
</method>
<method name="window_set_mouse_passthrough">
<method name="window_set_mouse_passthrough_polygons">
<return type="void" />
<param index="0" name="region" type="PackedVector2Array" />
<param index="0" name="regions" type="PackedVector2Array[]" />
<param index="1" name="window_id" type="int" default="0" />
<description>
Sets a polygonal region of the window which accepts mouse events. Mouse events outside the region will be passed through.
Expand Down Expand Up @@ -1690,6 +1690,13 @@
[b]Note:[/b] This method is implemented on Linux (X11), macOS and Windows.
</description>
</method>
<method name="window_set_mouse_passthrough_rects">
<return type="void" />
<param index="0" name="rectangles" type="Rect2i[]" />
<param index="1" name="window_id" type="int" default="0" />
<description>
</description>
</method>
<method name="window_set_popup_safe_rect">
<return type="void" />
<param index="0" name="window" type="int" />
Expand Down
19 changes: 16 additions & 3 deletions doc/classes/Window.xml
Original file line number Diff line number Diff line change
Expand Up @@ -619,9 +619,9 @@
[b]Note:[/b] This property is implemented on Linux (X11), macOS and Windows.
[b]Note:[/b] This property only works with native windows.
</member>
<member name="mouse_passthrough_polygon" type="PackedVector2Array" setter="set_mouse_passthrough_polygon" getter="get_mouse_passthrough_polygon" default="PackedVector2Array()">
<member name="mouse_passthrough_polygons" type="PackedVector2Array[]" setter="set_mouse_passthrough_polygons" getter="get_mouse_passthrough_polygons" default="[]">
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).
Passing empty arrays to this and [member mouse_passthrough_rects] 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.
Expand All @@ -646,7 +646,20 @@
[/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.
[b]Note:[/b] This property is implemented on Linux (X11), macOS and Windows. For wider support use [member mouse_passthrough_rects].
</member>
<member name="mouse_passthrough_rects" type="Rect2i[]" setter="set_mouse_passthrough_rects" getter="get_mouse_passthrough_rects" default="[]">
Sets an input region out of rectangles for the window which accept mouse events. Mouse events outside the input region will be passed through.
Passing empty arrays to this and [member mouse_passthrough_polygon] will disable passthrough support (all mouse events will be intercepted by the window, which is the default behavior).
[codeblocks]
[gdscript]
# Set region, using control node bounds.
$Window.mouse_passthrough_rects = [Rect2i($Control.get_rect())]
[/gdscript]
[/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, Wayland), macOS and Windows.
</member>
<member name="popup_window" type="bool" setter="set_flag" getter="get_flag" default="false">
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).
Expand Down
10 changes: 10 additions & 0 deletions misc/extension_api_validation/4.2-stable.expected
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,13 @@ GH-92322
Validate extension JSON: Error: Field 'classes/EditorInspectorPlugin/methods/add_property_editor/arguments': size changed value in new API, from 3 to 4.

Optional arguments added. Compatibility methods registered.


GH-88558
--------
Validate extension JSON: API was removed: classes/DisplayServer/methods/window_set_mouse_passthrough
Validate extension JSON: API was removed: classes/Window/methods/get_mouse_passthrough_polygon
Validate extension JSON: API was removed: classes/Window/methods/set_mouse_passthrough_polygon
Validate extension JSON: API was removed: classes/Window/properties/mouse_passthrough_polygon

Replaced by mouse_passthrough_polygons.
49 changes: 40 additions & 9 deletions platform/linuxbsd/wayland/display_server_wayland.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -669,9 +669,37 @@ void DisplayServerWayland::window_set_title(const String &p_title, DisplayServer
wayland_thread.window_set_title(MAIN_WINDOW_ID, wd.title);
}

void DisplayServerWayland::window_set_mouse_passthrough(const Vector<Vector2> &p_region, DisplayServer::WindowID p_window_id) {
// TODO
DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_mouse_passthrough region %s", p_region));
void DisplayServerWayland::window_set_mouse_passthrough_polygons(const TypedArray<Vector<Vector2>> &p_regions, DisplayServer::WindowID p_window_id) {
ERR_FAIL_MSG("Mouse polygon passthrough not supported by wayland.");
}

void DisplayServerWayland::window_set_mouse_passthrough_rects(const TypedArray<Rect2i> &p_rectangles, WindowID p_window) {
MutexLock mutex_lock(wayland_thread.mutex);
main_window.passthrough_rectangles = p_rectangles;
_update_window_mouse_passthrough(p_window);
}

void DisplayServerWayland::_update_window_mouse_passthrough(WindowID p_window) {
wl_surface *surface = wayland_thread.window_get_wl_surface(p_window);
WaylandThread::WindowState *window_state = wayland_thread.wl_surface_get_window_state(surface);

if (main_window.flags & WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT) {
wl_region *region = wl_compositor_create_region(window_state->registry->wl_compositor);
wl_surface_set_input_region(surface, region);
wl_region_destroy(region);
} else if (main_window.passthrough_rectangles.is_empty()) {
wl_surface_set_input_region(surface, nullptr);
} else {
wl_region *region = wl_compositor_create_region(window_state->registry->wl_compositor);
double scale_factor = 1.0 / wayland_thread.window_state_get_scale_factor(window_state);
for (int i = 0; i < main_window.passthrough_rectangles.size(); i++) {
const Rect2i &rect = main_window.passthrough_rectangles[i];
wl_region_add(region, scale_factor * rect.position.x, scale_factor * rect.position.y, scale_factor * rect.size.width, scale_factor * rect.size.height);
}

wl_surface_set_input_region(surface, region);
wl_region_destroy(region);
}
}

void DisplayServerWayland::window_set_rect_changed_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
Expand Down Expand Up @@ -856,20 +884,23 @@ void DisplayServerWayland::window_set_flag(WindowFlags p_flag, bool p_enabled, D

DEBUG_LOG_WAYLAND(vformat("Window set flag %d", p_flag));

if (p_enabled) {
wd.flags |= 1 << p_flag;
} else {
wd.flags &= ~(1 << p_flag);
}

switch (p_flag) {
case WINDOW_FLAG_BORDERLESS: {
wayland_thread.window_set_borderless(MAIN_WINDOW_ID, p_enabled);
} break;
case WINDOW_FLAG_MOUSE_PASSTHROUGH: {
_update_window_mouse_passthrough(p_window_id);
} break;

default: {
}
}

if (p_enabled) {
wd.flags |= 1 << p_flag;
} else {
wd.flags &= ~(1 << p_flag);
}
}

bool DisplayServerWayland::window_get_flag(WindowFlags p_flag, DisplayServer::WindowID p_window_id) const {
Expand Down
7 changes: 6 additions & 1 deletion platform/linuxbsd/wayland/display_server_wayland.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class DisplayServerWayland : public DisplayServer {

String title;
ObjectID instance_id;

TypedArray<Rect2i> passthrough_rectangles;
};

struct CustomCursor {
Expand Down Expand Up @@ -151,6 +153,8 @@ class DisplayServerWayland : public DisplayServer {

virtual void _show_window();

void _update_window_mouse_passthrough(WindowID p_window_id);

public:
virtual bool has_feature(Feature p_feature) const override;

Expand Down Expand Up @@ -211,7 +215,8 @@ class DisplayServerWayland : public DisplayServer {
virtual ObjectID window_get_attached_instance_id(WindowID p_window_id = MAIN_WINDOW_ID) const override;

virtual void window_set_title(const String &p_title, WindowID p_window_id = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window_id = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough_polygons(const TypedArray<Vector<Vector2>> &p_regions, WindowID p_window_id = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough_rects(const TypedArray<Rect2i> &p_rects, WindowID p_window = MAIN_WINDOW_ID) override;

virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
Expand Down
53 changes: 44 additions & 9 deletions platform/linuxbsd/x11/display_server_x11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1868,40 +1868,75 @@ void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window
}
}

void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
void DisplayServerX11::window_set_mouse_passthrough_polygons(const TypedArray<Vector<Vector2>> &p_regions, WindowID p_window) {
_THREAD_SAFE_METHOD_

ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].mpath = p_region;
windows[p_window].mregions = p_regions;
_update_window_mouse_passthrough(p_window);
}

void DisplayServerX11::window_set_mouse_passthrough_rects(const TypedArray<Rect2i> &p_rects, WindowID p_window) {
_THREAD_SAFE_METHOD_

ERR_FAIL_COND(!windows.has(p_window));
windows[p_window].mrects = p_rects;
_update_window_mouse_passthrough(p_window);
}

void DisplayServerX11::_update_window_mouse_passthrough(WindowID p_window) {
ERR_FAIL_COND(!windows.has(p_window));
ERR_FAIL_COND(!xshaped_ext_ok);

const Vector<Vector2> 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) {
if (windows[p_window].mpass) {
Region region = XCreateRegion();
XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);
XDestroyRegion(region);
} else if (region_path.size() == 0) {
XShapeCombineMask(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, None, ShapeSet);
} else {
_update_window_input_region(p_window);
}
}
}

void DisplayServerX11::_update_window_input_region(WindowID p_window) {
const TypedArray<Vector<Vector2>> &region_paths = windows[p_window].mregions;
const TypedArray<Rect2i> &region_rectangles = windows[p_window].mrects;

if (region_paths.is_empty() && region_rectangles.is_empty()) {
XShapeCombineMask(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, None, ShapeSet);
} else {
Region input_region = XCreateRegion();

// apply polygons to input region
for (int region_idx = 0; region_idx < region_paths.size(); region_idx++) {
const Vector<Vector2> &region_path = region_paths[region_idx];
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 region = XPolygonRegion(points, region_path.size(), EvenOddRule);
Region path_input_region = XPolygonRegion(points, region_path.size(), EvenOddRule);
XUnionRegion(input_region, path_input_region, input_region);
memfree(points);
XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, region, ShapeSet);
XDestroyRegion(region);
}

// add rectangles to input region
XRectangle xrect;
for (int i = 0; i < region_rectangles.size(); i++) {
const Rect2i &rect = region_rectangles[i];
xrect.x = rect.position.x;
xrect.y = rect.position.y;
xrect.width = rect.size.x;
xrect.height = rect.size.y;

XUnionRectWithRegion(&xrect, input_region, input_region);
}

XShapeCombineRegion(x11_display, windows[p_window].x11_window, ShapeInput, 0, 0, input_region, ShapeSet);
XDestroyRegion(input_region);
}
}

Expand Down
7 changes: 5 additions & 2 deletions platform/linuxbsd/x11/display_server_x11.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ class DisplayServerX11 : public DisplayServer {
Callable input_text_callback;
Callable drop_files_callback;

Vector<Vector2> mpath;
TypedArray<Vector<Vector2>> mregions;
TypedArray<Rect2i> mrects;

WindowID transient_parent = INVALID_WINDOW_ID;
HashSet<WindowID> transient_children;
Expand Down Expand Up @@ -303,6 +304,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_input_region(WindowID p_window);
void _update_window_mouse_passthrough(WindowID p_window);

String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const;
Expand Down Expand Up @@ -455,7 +457,8 @@ class DisplayServerX11 : public DisplayServer {
virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;

virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough_polygons(const TypedArray<Vector<Vector2>> &p_regions, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough_rects(const TypedArray<Rect2i> &p_rects, WindowID p_window = MAIN_WINDOW_ID) 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;
Expand Down
7 changes: 5 additions & 2 deletions platform/macos/display_server_macos.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ class DisplayServerMacOS : public DisplayServer {
id window_view;
id window_button_view;

Vector<Vector2> mpath;
TypedArray<Vector<Vector2>> mregions;
TypedArray<Rect2i> mrects;

Point2i mouse_pos;

Expand Down Expand Up @@ -225,6 +226,7 @@ class DisplayServerMacOS : public DisplayServer {
void _process_key_events();
void _update_keyboard_layouts();
static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info);
bool _mouse_intersects_input_region(const WindowData &p_wd) const;

static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);

Expand Down Expand Up @@ -344,7 +346,8 @@ class DisplayServerMacOS : public DisplayServer {

virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
virtual Size2i window_get_title_size(const String &p_title, WindowID p_window) const override;
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough_polygons(const TypedArray<Vector<Vector2>> &p_regions, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_mouse_passthrough_rects(const TypedArray<Rect2i> &p_rects, WindowID p_window = MAIN_WINDOW_ID) override;

virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
Expand Down
35 changes: 31 additions & 4 deletions platform/macos/display_server_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,24 @@
return nsimg;
}

bool DisplayServerMacOS::_mouse_intersects_input_region(const WindowData &p_wd) const {
const Point2i &mouse_pos = p_wd.mouse_pos;
for (int i = 0; i < p_wd.mregions.size(); i++) {
if (Geometry2D::is_point_in_polygon(mouse_pos, p_wd.mregions[i])) {
return true;
}
}

for (int i = 0; i < p_wd.mrects.size(); i++) {
const Rect2i &rect = p_wd.mrects[i];
if (rect.has_point(mouse_pos)) {
return true;
}
}

return false;
}

NSCursor *DisplayServerMacOS::_cursor_from_selector(SEL p_selector, SEL p_fallback) {
if ([NSCursor respondsToSelector:p_selector]) {
id object = [NSCursor performSelector:p_selector];
Expand Down Expand Up @@ -1846,13 +1864,22 @@
return size * scale;
}

void DisplayServerMacOS::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
void DisplayServerMacOS::window_set_mouse_passthrough_polygons(const TypedArray<Vector<Vector2>> &p_regions, WindowID p_window) {
_THREAD_SAFE_METHOD_

ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];

wd.mregions = p_regions;
}

void DisplayServerMacOS::window_set_mouse_passthrough_rects(const TypedArray<Rect2i> &p_rects, WindowID p_window) {
_THREAD_SAFE_METHOD_

ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];

wd.mpath = p_region;
wd.mrects = p_rects;
}

int DisplayServerMacOS::window_get_current_screen(WindowID p_window) const {
Expand Down Expand Up @@ -3034,9 +3061,9 @@
if (![wd.window_object ignoresMouseEvents]) {
[wd.window_object setIgnoresMouseEvents:YES];
}
} else if (wd.mpath.size() > 0) {
} else if (wd.mregions.size() > 0 || wd.mrects.size() > 0) {
update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]);
if (Geometry2D::is_point_in_polygon(wd.mouse_pos, wd.mpath)) {
if (_mouse_intersects_input_region(wd)) {
if ([wd.window_object ignoresMouseEvents]) {
[wd.window_object setIgnoresMouseEvents:NO];
}
Expand Down
Loading

0 comments on commit b01b308

Please sign in to comment.