| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
| LocationEvent.__init__(self, name, canvas, x, y, guiEvent=guiEvent) | ||
| self.key = key | ||
|
|
||
| def process(self): |
There was a problem hiding this comment.
This (or the init) should update the location based on the last-known mouse location if the user does not supply one.
Sorry, something went wrong.
There was a problem hiding this comment.
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?)
Sorry, something went wrong.
There was a problem hiding this comment.
That is my understanding of
matplotlib/lib/matplotlib/backend_bases.py
Lines 1691 to 1692 in e9e1c87
matplotlib/lib/matplotlib/backend_bases.py
Lines 1700 to 1701 in e9e1c87
Sorry, something went wrong.
|
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 (pick_event) to be confusing so I'm not particularly sad to see them go and I hope that these are not being widely used by non-backend authors. 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 EventSubclass.process which is also a bit odd. I think this will require changes to atleast 3 extrernal packages (mplcairo, ipympl, the kivy backend). The process for figure_leave_event should clear LocationEevent.lastevent If we are going to do this the on-init (!?) processing chain that happen in LocationEvent.__init__ (via _update_enter_leave should be moved to process. |
Sorry, something went wrong.
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(?) def FigureCanvasBase.process_event(event): self.callbacks.process(event.name, event), which is a bit more verbose but works too. Or we can just not add any helper and force backend implementers to call everywhere event = FooEvent(...); canvas.callbacks.process(event.name, event) which is even more verbose, but again works too. On the other hand, having a helper is also useful for e.g. testing code which wants to trigger arbitrary events (e.g., mplcursors).
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 (event = FooEvent(...); canvas.callbacks.process(event.name, event)) which should be safe in all cases.
I need to look into it, but again I'd rather not have this lastevent at all and just have the GUI backends query the cursor position when the event is triggered and fill in that info correctly. |
Sorry, something went wrong.
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 canvas.foo_event were "internal" and should only be called by the backends (which is why the API drifted between the backends) and that users should be writing code against the other side of the callback registry. |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
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 key_{press,release}_event. But that's actually quite brittle, because key presses/releases can also occur while the canvas doesn't have the focus, and these won't be recorded (this isn't an abstract problem, but one I actually encountered, and quite puzzling in fact, because most of the time .key will be correct... but fail in a few times, then you repress the key a few times and "weirdly" it works again, and you wonder whether your keyboard is dying). However, Qt/... expose an API to tell which modifier keys (Ctrl/Alt/Shift) are pressed when a mouse event occurs, so MouseEvent could gain a .modifiers attribute recording that info (and then we may choose later to deprecate .key and tell the users to listen to key_{press,release}_event themselves if they want, but that's another story). With the approach of this PR, one would just need to add a .modifiers parameter/attribute to MouseEvent, and then the backend could just set it and call process() on it. In fact a third-party backend could even choose to implement that independently of Matplotlib by creating its own MouseEvent subclass and then push it to the callback registry. But with the current API this is slightly more annoying, because it needs to go through button_{press,release}_event/etc., which all need to have their signature extended. And then is it a bug if a backend exposes a button_press_event which has no modifiers parameter? (On the other hand it's not really a problem because these methods already have incompatible signatures anyways.) The situation is similar wrt. figure_leave_event, which currently fills its location based on the last LocationEvent, but really the GUI backends could/should just query the cursor position when the LocationEvent is triggered and fill in that info themselves.
As noted above, initially I had a def FigureCanvasBase.process_event(event): self.canvas.process(event.name, event). One of the issues, though, is that with that approach you need to put all the event-specific process() logic into a big switchyard in that single method, rather than in each separate process() implementation. Also I don't think that dataclasses means "no methods". |
Sorry, something went wrong.
|
Seems like this still needs some discussion? |
Sorry, something went wrong.
|
Likely, yes. (I'll wait for the rebase until we can agree on whether we want this or not.) |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
|
So was this ever on a call? |
Sorry, something went wrong.
|
A few times, but last time was a while ago. |
Sorry, something went wrong.
| 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.
Include a note about _process existing?
Sorry, something went wrong.
|
My attempt to summarize:
I'ma little wary of documenting to use canvas.callbacks.process(event.name, event) when that is not what we do internally (because the dead reckoning book keeping is done in Event._process (in the sub-classes implementation or because we actually emit some events 2x but with different names for the enter/leave events (it was like this before). Although in #16931 (comment) it is noted the consensus was to make _process private, I'm leaning the other way now and think we should make it public. It might also make sense to have a single proccess_ui_event(name, evt) on the canvas base-class that merges all the special casing into one place? I am happy with the motion_notify subscription, doing that work in __init__ (maybe triggering other callbacks!) was not great. There might be some subtle differences in the order of events (I think that you used to get the enter/leave event then the location event, now you will get the location event and then the enter / leave event). I really hope no one is relying on that ordering and do not think it is worth more effort to restore the old behavior (but we should note it). Over all 👍🏻 and think we should get this in for 3.6. |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
There was a problem hiding this comment.
This seems to work in the backends I tested; TkAgg, QtAgg, Gtk3Agg.
Sorry, something went wrong.
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. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
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. 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.
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