Skip to content

Commit 8663485

Browse files
authored
Add pointer_enter and pointer_leave events (#80)
1 parent e6d3232 commit 8663485

File tree

4 files changed

+83
-8
lines changed

4 files changed

+83
-8
lines changed

rendercanvas/_events.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ class EventType(BaseEnum):
2222
resize = None #: The canvas has changed size. Has 'width' and 'height' in logical pixels, 'pixel_ratio'.
2323
close = None #: The canvas is closed. No additional fields.
2424
pointer_down = None #: The pointing device is pressed down. Has 'x', 'y', 'button', 'butons', 'modifiers', 'ntouches', 'touches'.
25-
pointer_up = None #: The pointing device is released. Same fields as pointer_down.
26-
pointer_move = None #: The pointing device is moved. Same fields as pointer_down.
25+
pointer_up = None #: The pointing device is released. Same fields as pointer_down. Can occur outside of the canvas.
26+
pointer_move = None #: The pointing device is moved. Same fields as pointer_down. Can occur outside of the canvas if the pointer is currently down.
27+
pointer_enter = None #: The pointing device is moved into the canvas.
28+
pointer_leave = None #: The pointing device is moved outside of the canvas (regardless of a button currently being pressed).
2729
double_click = None #: A double-click / long-tap. This event looks like a pointer event, but without the touches.
2830
wheel = None #: The mouse-wheel is used (scrolling), or the touchpad/touchscreen is scrolled/pinched. Has 'dx', 'dy', 'x', 'y', 'modifiers'.
2931
key_down = None #: A key is pressed down. Has 'key', 'modifiers'.

rendercanvas/glfw.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,12 @@ def __init__(self, *args, present_method=None, **kwargs):
231231
self._key_modifiers = ()
232232
self._pointer_buttons = ()
233233
self._pointer_pos = 0, 0
234+
self._pointer_inside = None
235+
self._pointer_lock = False
234236
self._double_click_state = {"clicks": 0}
235237
glfw.set_mouse_button_callback(self._window, weakbind(self._on_mouse_button))
236238
glfw.set_cursor_pos_callback(self._window, weakbind(self._on_cursor_pos))
239+
glfw.set_cursor_enter_callback(self._window, weakbind(self._on_cursor_enter))
237240
glfw.set_scroll_callback(self._window, weakbind(self._on_scroll))
238241
glfw.set_key_callback(self._window, weakbind(self._on_key))
239242
glfw.set_char_callback(self._window, weakbind(self._on_char))
@@ -441,6 +444,16 @@ def _on_mouse_button(self, window, but, action, mods):
441444
}
442445
button = button_map.get(but, 0)
443446

447+
# Handler pointer locking
448+
if self._pointer_lock:
449+
if action == glfw.RELEASE:
450+
self._pointer_lock = False
451+
return
452+
elif not self._pointer_inside:
453+
# This press is to select the window (regaining focus)
454+
self._pointer_lock = True
455+
return
456+
444457
if action == glfw.PRESS:
445458
event_type = "pointer_down"
446459
buttons = set(self._pointer_buttons)
@@ -475,6 +488,9 @@ def _follow_double_click(self, action, button):
475488
# If a sequence of down-up-down-up is made in nearly the same
476489
# spot, and within a short time, we emit the double-click event.
477490

491+
if self._pointer_lock:
492+
return
493+
478494
x, y = self._pointer_pos[0], self._pointer_pos[1]
479495
state = self._double_click_state
480496

@@ -521,6 +537,18 @@ def _follow_double_click(self, action, button):
521537
self.submit_event(ev)
522538

523539
def _on_cursor_pos(self, window, x, y):
540+
if self._pointer_lock:
541+
return
542+
543+
# Maybe trigger initial enter
544+
if self._pointer_inside is None:
545+
if glfw.get_window_attrib(window, glfw.HOVERED):
546+
self._on_cursor_enter(window, True)
547+
548+
# Only process move events if inside or if drag-tracking
549+
if not (self._pointer_inside or self._pointer_buttons):
550+
return
551+
524552
# Store pointer position in logical coordinates
525553
if self._screen_size_is_logical:
526554
self._pointer_pos = x, y
@@ -540,6 +568,11 @@ def _on_cursor_pos(self, window, x, y):
540568

541569
self.submit_event(ev)
542570

571+
def _on_cursor_enter(self, window, entered):
572+
self._pointer_inside = bool(entered)
573+
ev = {"event_type": "pointer_enter" if entered else "pointer_leave"}
574+
self.submit_event(ev)
575+
543576
def _on_scroll(self, window, dx, dy):
544577
# wheel is 1 or -1 in glfw, in jupyter_rfb this is ~100
545578
ev = {

rendercanvas/qt.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,14 @@ def mouseMoveEvent(self, event): # noqa: N802
542542
def mouseReleaseEvent(self, event): # noqa: N802
543543
self._mouse_event("pointer_up", event)
544544

545+
def enterEvent(self, event): # noqa: N802
546+
ev = {"event_type": "pointer_enter"}
547+
self.submit_event(ev)
548+
549+
def leaveEvent(self, event): # noqa: N802
550+
ev = {"event_type": "pointer_leave"}
551+
self.submit_event(ev)
552+
545553
def mouseDoubleClickEvent(self, event): # noqa: N802
546554
super().mouseDoubleClickEvent(event)
547555
self._mouse_event("double_click", event, touches=False)

rendercanvas/wx.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def _rc_add_task(self, async_func, name):
179179
return super()._rc_add_task(async_func, name)
180180

181181
def _rc_call_later(self, delay, callback):
182-
wx.CallLater(int(delay * 1000), callback)
182+
wx.CallLater(max(int(delay * 1000), 1), callback)
183183

184184
def process_wx_events(self):
185185
old_loop = wx.GUIEventLoop.GetActive()
@@ -223,6 +223,8 @@ def __init__(self, *args, present_method=None, **kwargs):
223223
raise ValueError(f"Invalid present_method {present_method}")
224224

225225
self._is_closed = False
226+
self._pointer_inside = None
227+
self._is_pointer_inside_according_to_wx = False
226228

227229
# We keep a timer to prevent draws during a resize. This prevents
228230
# issues with mismatching present sizes during resizing (on Linux).
@@ -238,6 +240,10 @@ def __init__(self, *args, present_method=None, **kwargs):
238240

239241
self.Bind(wx.EVT_MOUSE_EVENTS, self._on_mouse_events)
240242
self.Bind(wx.EVT_MOTION, self._on_mouse_move)
243+
self.Bind(wx.EVT_ENTER_WINDOW, self._on_window_enter)
244+
self.Bind(wx.EVT_LEAVE_WINDOW, self._on_window_enter)
245+
self.Bind(wx.EVT_SET_FOCUS, self._on_focus)
246+
self.Bind(wx.EVT_KILL_FOCUS, self._on_focus)
241247

242248
self.Show()
243249
self._final_canvas_init()
@@ -496,11 +502,7 @@ def _mouse_event(self, event_type: str, event: wx.MouseEvent, touches: bool = Tr
496502

497503
ev.update({"dx": -dx, "dy": -dy})
498504

499-
self.submit_event(ev)
500-
elif event_type == "pointer_move":
501-
self.submit_event(ev)
502-
else:
503-
self.submit_event(ev)
505+
self.submit_event(ev)
504506

505507
def _on_mouse_events(self, event: wx.MouseEvent):
506508
event_type = event.GetEventType()
@@ -512,8 +514,38 @@ def _on_mouse_events(self, event: wx.MouseEvent):
512514
self._mouse_event(event_type_name, event)
513515

514516
def _on_mouse_move(self, event: wx.MouseEvent):
517+
# On MacOS this event does not happen unless a button is pressed (i.e. dragging)
515518
self._mouse_event("pointer_move", event)
516519

520+
def _on_window_enter(self, event: wx.MouseEvent):
521+
if event.GetEventType() == wx.wxEVT_ENTER_WINDOW:
522+
pointer_inside = True
523+
ev = {"event_type": "pointer_enter"}
524+
else: # event.GetEventType() == wx.wxEVT_LEAVE_WINDOW
525+
pointer_inside = False
526+
ev = {"event_type": "pointer_leave"}
527+
528+
# Track the state of wx
529+
self._is_pointer_inside_according_to_wx = pointer_inside
530+
531+
# Update our state only if we have focus
532+
if self.HasFocus() and pointer_inside != self._pointer_inside:
533+
self._pointer_inside = pointer_inside
534+
self.submit_event(ev)
535+
536+
def _on_focus(self, event: wx.FocusEvent):
537+
if event.GetEventType() == wx.wxEVT_SET_FOCUS:
538+
if self._is_pointer_inside_according_to_wx:
539+
if not self._pointer_inside:
540+
ev = {"event_type": "pointer_enter"}
541+
self._pointer_inside = True
542+
self.submit_event(ev)
543+
else: # event.GetEventType() == wx.wxEVT_KILL_FOCUS
544+
if self._pointer_inside:
545+
ev = {"event_type": "pointer_leave"}
546+
self._pointer_inside = False
547+
self.submit_event(ev)
548+
517549

518550
class WxRenderCanvas(WrapperRenderCanvas, wx.Frame):
519551
"""A toplevel wx Frame providing a render canvas."""

0 commit comments

Comments
 (0)