[Development] Pointer handlers: transparent or block? was Re: QEvent::accept() vs. the newer event delivery algorithms in Qt Quick; remaining API issues; etc.

Shawn Rutledge Shawn.Rutledge at qt.io
Tue Mar 16 10:30:54 CET 2021

On 2020 Oct 8, at 13:26, Shawn Rutledge <Shawn.Rutledge at qt.io<mailto:Shawn.Rutledge at qt.io>> wrote:

If you subclass QQuickItem and start handling events, it becomes clear that QEvent::accept() has always meant two things in legacy Qt Quick: 1) stop propagation and 2) grab the mouse.

…etc… and there weren’t a lot of replies to that thread.  At the time, bigger changes were possible; now, Qt 6.0 is released (and then some), and we always end up making only incremental changes anyway, with old behavior held in place by autotests and tradition and shortage of time.

The next increment seems to be this: I think all pointer handlers (TapHandler, DragHandler, HoverHandler, WheelHandler, PointHandler) should share a common boolean flag to tell whether they accept or reject events.  What we have so far is that TapHandler has gesturePolicy, which was meant to be intuitive for designers: describe how it behaves in an observable way, despite the fact that under the covers it affects the accept flag, which everyone with Qt experience knows about, but designers can’t be expected to know.  Based on bug reports we’ve received, programmers still want to control the state of the accept flag to control whether further propagation is allowed.  And I want that to be declarative, not requiring you to write a JS if statement to decide.

What should we call the flag then?  I think our best candidates so far are “blocking”, “transparent” and “propagates".  “Blocking” I think is pretty clear (as long as users don’t confuse it with the idea of blocking execution, as modal dialogs do); “transparent” maybe has a good meaning if you understand that it concerns only event delivery, not appearance, and it goes with things like Qt::WindowTransparentForInput; on the other hand, it does not mean that the handler fails to react at all, but that it lets the event keep propagating.  And speaking of propagating: I’ve never liked the meaning we give to that word, because it makes me think of propagating plants: i.e. copying.  In fact we try to avoid copying events where possible; and even if we do it, it’s not the handler’s job to copy the event and send it to another target; so maybe let’s not imply that… even though traditional Qt programmers already know what event propagation is.

So what do you think? do you have any preference there?

More background, how this came up again: Richard has been refactoring the delivery of hover events in Qt Quick.  It’s something I hadn’t gotten around to yet: I’ve been torn for a while on whether we should continue with frame-synchronous hover events or come up with something else.  From one perspective, the reason we have hover events is to inform all the items and handlers that care, that the mouse has moved; so the naive implementation in early versions of Qt 5 was just sending the actual mouse moves in the form of QHoverEvents, which made me wonder why do we have those: why isn’t a QMouseEvent good enough?  But then multiple users wrote bugs complaining about the case when the mouse does not move, but items are being animated, and they happen to go underneath the current cursor position.  (Why is it so hard for items to just observe the mouse?  The trouble is the sheer number of them that could want to do that, in general.  So we use events.  Could we possibly invent something better?)  Robin fixed this set of bugs by sending an artificial hover event once per frame, before the scene graph gets updated prior to rendering.  It sounds expensive, but we also have a flag QQuickItemPrivate::subtreeHoverEnabled, which is false unless the item or one of its children actually needs hover events, which it expresses by setting hoverEnabled to true.  So if you are worried about the cost of these events, make sure your UI design allows us to rule out most or all of the item subtrees while we are delivering the hover events, so that it can stop short, and doesn’t need to visit every item and handler.  MouseArea has hoverEnabled: false by default.  I didn’t want pointer handlers to have restrictions by default; so the result of an empty HoverHandler { } declaration inside an item is that the item will have its private hoverEnabled flag true, and thus its parents all need subtreeHoverEnabled true too.  I wanted to solve the long-standing problem when an item and its parent (or grandparent) need to show visual hover feedback simultaneously.  MouseArea will only allow that in the case that one MouseArea is a direct child of another (not the usual way you design components!) and they both have hoverEnabled: true.  HoverHandler works regardless of the hierarchy.  So, that implies the hover event needs to be rejected by default, so that it can keep propagating to all other hover handlers; and that means more work for the delivery code.  Now, Richard wants to enable you to optimize it again by having some flag that you can set to tell that when one HoverHandler has reacted, it’s enough: nothing else in the scene needs to know.  A HoverHandler ought to reject events by default, because it cannot know in general whether other items and handlers are interested; but you should be able to tell it to leave them accepted so that propagation stops, if you know that no others will be interested.

I’m working on Qt Quick 3D lately: when you have an interactive 2D Qt Quick scene mapped onto a 3D object, the pointing devices and the keyboard have to keep working normally.  That’s the first step.  The next step might be to allow declaring pointer handlers directly inside 3D objects, without a 2D subscene: that’s just an experiment so far.  (See https://github.com/ec1oud/qtquick3d-input-demo for a demo, but it depends on features that aren’t yet integrated.)  Anyway… when you hover a 3D object, we need to do picking (create a mathematical abstraction of a ray pointing down into the scene, calculate which faces of which 3D objects it intersects and at what exact positions, then visit each 2D scene that is mapped onto them to see what items and handers are impacted), so the expense of doing that at 60FPS (or more) is higher than it was in Qt Quick itself.  (Granted, we can probably do it less often: every 1/10 of a second might be often enough.  So maybe we can just skip some frames until that much time has elapsed.)  I think application developers need ways to avoid that overhead in case hover is not needed in most of the scene.

WheelHandler behaves in a way that looks to be “blocking” by default, but I think we need the flag there too: you should be able to react to the mouse wheel in more than one place in the z stack, if you need to.  (Show feedback in one layer and actually move something in another layer, for example.). Last year I was going to call it acceptsEvents, just in WheelHandler, but that patch sat there in gerrit, unapproved for a year, and I already was thinking we need to solve this problem in general, not just for WheelHandler.  If we are to have one unified flag, the default setting will be different in different handlers, and I think that’s OK: we'll just document it.  The interaction between the new flag and TapHandler.gesturePolicy is tricky though.  One will have to override the other.  I suppose maybe blocking should override gesturePolicy because blocking is the geekier flag, you probably know what you’re doing if you use it?  But I didn’t try yet; maybe something would go wrong with that.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/development/attachments/20210316/b5871d0d/attachment-0001.html>

More information about the Development mailing list