-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Make it easier to improve UI event metadata. #16931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
44b6055
to
b5763fa
Compare
lib/matplotlib/backend_bases.py
Outdated
def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None): | ||
LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) | ||
self.key = key | ||
|
||
def process(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (or the init) should update the location based on the last-known mouse location if the user does not supply one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did that occur before? One of the points of this PR is also to stop trying to fill in this "bad" metadata, which can easily be wrong. (If it did occur, sure, I can put that back for now, but I don't think it did?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is my understanding of
matplotlib/lib/matplotlib/backend_bases.py
Lines 1691 to 1692 in e9e1c87
event = KeyEvent( | |
s, self, key, self._lastx, self._lasty, guiEvent=guiEvent) |
matplotlib/lib/matplotlib/backend_bases.py
Lines 1700 to 1701 in e9e1c87
event = KeyEvent( | |
s, self, key, self._lastx, self._lasty, guiEvent=guiEvent) |
I'm mixed on this, but leaning 👍. On the pro side, this makes it slightly more explicit what is going on (as you create the Event object rather than calling a function that creates it for you). I have always found the method name ( On the con-side having the process method push it's self through the callback registry seems a bit odd. This also moves management of (private) state on the canvas out of canvas methods and into the The If we are going to do this the on-init (!?) processing chain that happen in |
Especially, these methods are (effectively) backend-dependent, so you can't write backend-independent code using them (well, unless you're careful to always call the base class implementation FigureCanvasBase.foo_event instead of canvas.foo_event...).
I'm not super-wedded to the API; initially I had the somewhat more obvious(?)
I agree, but really I think these things should be just plain callbacks always connected at canvas setup (e.g. adding a callback that draws on resize).
mplcairo doesn't need to change anything because it fully relies on Matplotlib's base classes for UI integration (that's basically why I wrote the foocairo backends for Matplotlib -- making sure that GUI and rendering were orthogonal); IOW it only overrides paintEvent (Qt), on_draw_event (GTK3), etc. A quick look suggests that yes, Kivy and ipympl will need updates, but they can at least use the no-helper API (
|
But then they lose the axes enter/leave state management. They are going to have to carry some version gating code for a while. I guess we could pull all of the "extra" logic back into the canvas and add a "pre-process" step to the call back registry?
Why are you trying to write code against these methods? My sense of the design is that the |
Just a quick thought (not an expert in the event system): Aren‘t events sort of data classes (if that had existed back then)? It feels a bit strange to add process functionality there. |
Fair enough, I guess we can have that code both in LocationEvent.process (like I did in this PR) and in each of the FigureCanvasBase.location_event(). (In any case, that seems better than having it in the LocationEvent constructor -- big side effects in a class constructor seem just... awkward.)
I'm not actually trying to write code against these methods; my point here was just that end-users can't rely on it (as you say they're basically internal). The original motivation was to improve event metadata in the case of MouseEvents (#6159). Basically, right now the GUI backends don't fill in the .key attribute of MouseEvents -- because indeed Qt/GTK/Tk/wx expose no API to tell what keys are pressed when a mouse event occurs. Instead, the key attribute is filled based on the last The situation is similar wrt.
As noted above, initially I had a Also I don't think that dataclasses means "no methods". |
Seems like this still needs some discussion? |
Likely, yes. (I'll wait for the rebase until we can agree on whether we want this or not.) |
On a similar note, the approach here would also help with reporting of pressed buttons in the case of motion_notify_event: currently, we just report the button corresponding to the last button_press_event that occurred over the canvas, but all GUI frameworks can actually report all of the buttons that are being pressed while a cursor motion occurs (which may not even include the last button_press_event, if the mouse motion started out of the canvas). ... and rebased. |
d2ceeef
to
684663c
Compare
So was this ever on a call? |
A few times, but last time was a while ago. |
difficult to improve event metadata. | ||
|
||
In order to trigger an event on a canvas, directly construct an `.Event` object | ||
of the correct class and call ``canvas.callbacks.process(event.name, event)``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include a note about _process
existing?
My attempt to summarize:
I'ma little wary of documenting to use I am happy with the motion_notify subscription, doing that work in Over all 👍🏻 and think we should get this in for 3.6. |
Thanks for the review. I moved all special-casing in the various _process methods into plain event handler callbacks (in separate commits, which should be hopefully self-contained enough for review), which seems cleaner. How does that look to you? I can still make _process public if you prefer and/or revert back to putting the special-casing into _process overrides. As for event ordering, I believe user callbacks registered for the axes_enter_event will still be called before user callbacks registered for the motion_notify_event, because the order is now 1) we process the motion_notify_event, and call the the first callback registered to it, which is _axes_enter_leave_emitter 2) this synthesizes and processes the axes_enter/leave_event, and calls all user callbacks registered to it, and finally 3) we return to prcessing the motion_notify_event, calling all remaining callbacks, which are the user callbacks. |
84f69e0
to
151de3c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to work in the backends I tested; TkAgg, QtAgg, Gtk3Agg.
Currently, UI events (MouseEvent, KeyEvent, etc.) are generated by letting the GUI-specific backends massage the native event objects into a list of args/kwargs and then call `FigureCanvasBase.motion_notify_event`/`.key_press_event`/etc. This makes it a bit tricky to improve the metadata on the events, because one needs to change the signature on both the `FigureCanvasBase` method and the event class. Moreover, the `motion_notify_event`/etc. methods are directly bound as event handlers in the gtk3 and tk backends, and thus have incompatible signatures there. Instead, the native GUI handlers can directly construct the relevant event objects and trigger the events themselves; a new `Event._process` helper method makes this even shorter (and allows to keep factoring some common functionality e.g. for tracking the last pressed button or key). As an example, this PR also updates figure_leave_event to always correctly set the event location based on the *current* cursor position, instead of the last triggered location event (which may be outdated); this can now easily be done on a backend-by-backend basis, instead of coordinating the change with FigureCanvasBase.figure_leave_event. This also exposed another (minor) issue, in that resize events often trigger *two* calls to draw_idle -- one in the GUI-specific handler, and one in FigureCanvasBase.draw_idle (now moved to ResizeEvent._process, but should perhaps instead be a callback autoconnected to "resize_event") -- could probably be fixed later.
These were only needed back when axes_enter_events were generated in the LocationEvent constructor, but this is now done by a standard callback.
CallbackRegistry already replaces exceptions by printed tracebacks, which seems better than fully suppressing everything.
Backends can call draw_idle themselves. Note that 1) this was already done by the gtk backends, and 2) this may actually be unneeded, as figure.set_size_inches (which is always called a bit earlier by the various resize handlers) also marks the figure as stale, which should trigger a redraw too. Still, let's add the draw_idle calls to be safe, they shouldn't be costly as both draws should get compressed together; we can always investigate removing them later.
I did the rebase, anyone (including @anntzer ) can merge this when green. |
PR Summary
Currently, UI events (MouseEvent, KeyEvent, etc.) are generated by
letting the GUI-specific backends massage the native event objects into
a list of args/kwargs and then call
FigureCanvasBase.motion_notify_event
/.key_press_event
/etc. Thismakes it a bit tricky to improve the metadata on the events, because one
needs to change the signature on both the
FigureCanvasBase
method andthe event class. Moreover, the
motion_notify_event
/etc. methods aredirectly bound as event handlers in the gtk3 and tk backends, and thus
have incompatible signatures there.
Instead, the native GUI handlers can directly construct the relevant
event objects and trigger the events themselves; a new
Event.process
helper method makes this even shorter (and allows to keep factoring some
common functionality e.g. for tracking the last pressed button or key).
As an example, this PR also updates figure_leave_event to always
correctly set the event location based on the current cursor position,
instead of the last triggered location event (which may be outdated);
this can now easily be done on a backend-by-backend basis, instead of
coordinating the change with FigureCanvasBase.figure_leave_event.
This also exposed another (minor) issue, in that resize events often
trigger two calls to draw_idle -- one in the GUI-specific handler, and
one in FigureCanvasBase.draw_idle (now moved to ResizeEvent.process, but
should perhaps instead be a callback autoconnected to "resize_event") --
could probably be fixed later.
As an example, this strategy would have made the change at #9814 easier (by just passing the correct additional arguments to LocationEvent).
This would also make #6159 easier (right now #6159 only fixes modifiers for button_press_event, but it would also nice to have it for motion_notify_event and button_release_event).
Also closes #8715 (by deprecating the relevant handlers).
Another small commit updates .gitattributes so that diffs in _macosx.m get the correct context when displayed locally.
PR Checklist