Qt Quick中鼠标和触摸事件的传递非常复杂,几年前我们就清楚了我们需要重构事件继承层次结构,为各种事件类型提供一些通用的API,以便可以将更多传递代码共享。在Qt 5.8中,我们添加了QQuickPointerEvent和关联的类型,作为对可能的样子进行原型制作的一种方法。它们是QObjects。从那时起,QQuickWindow一直在交付这些包装器事件,这些包装器事件将原始事件承载在内部。现在终于在Qt 6中,我们已经能够完成QEvent重构,因此QQuickWindow不再需要包装器。除此之外,我们还能够添加一些功能并修复了一些错误。Qt 5中似乎很难解决的许多其他错误至少应在以后修复。
向QPointerEvent和QEventPoint问好
现在,继承层次结构如下所示:
QPointerEvent是来自指针设备(鼠标,触摸屏,平板电脑手写笔)的所有事件的新抽象类型。它具有通用API,能够以与设备无关的方式处理所有这些问题。由于QTouchEvent可以在一个事件中携带多个接触点,因此我们对这一概念进行了标准化:每个QPointerEvent都可能代表QEventPoint实例的集群(即使大多数事件仅携带一个点),因此具有适当的API:points(),point(i)和pointCount()。
每个QInputEvent(包括QPointerEvent)都带有指向它来自的QInputDevice的指针。即使处理合成鼠标事件,事件处理代码也可以以设备特定的方式响应。
每个QEventPoint都有速度。Qt Quick在Qt 5中使用的卡尔曼滤波器已移至QtGui,因此无论事件传递到何处,都可以使用最近几次移动的平均速度。这样,无论速度来自哪个设备,都可以实现对速度敏感的行为(例如,将快速拖动与快速拖动区别开来,或响应特定的运动方向)。对于这样的目的,瞬时速度通常太不稳定,但是如果需要,可以将其计算为(globalPosition() - globalLastPosition()) / (timestamp() - lastTimestamp())
。
QSinglePointEvent是另一种抽象类型,用于标准化位置访问器,该位置访问器以前在QMouseEvent,QTabletEvent,QHoverEvent,QWheelEvent等中单独实现且不一致。它们现在都是浮点数。position()替换pos()和posF(),scenePosition()替换windowPos(),而globalPosition()替换screenPos()。旧的访问器现在仍然存在,但是已被弃用:例如,仅由于处理QMouseEvent,Qt 5应用程序就不会遇到SC中断。QEventPoint代替了QTouchEvent :: TouchPoint,但是出于源兼容性的考虑,有一个“使用中”的声明。
我已经分手了,并添加了新的qevent-accessors检查,这可能为您省去一些麻烦:它可以自动应用“ fixits”来摆脱由于事件访问器重命名而引起的弃用警告。(是的,我们应该在这一变化的上游。)
C ++中与设备无关的事件处理
现在,在Qt的各个地方,我们可以通过迭代QEventPoints或仅响应第一点来响应鼠标,触摸和平板电脑事件(例如检测单击或拖动)。这是一个QQuickItem子类如何做到这一点的人为示例:
bool MyItem :: event(QEvent * ev)覆盖{ 如果(ev-> isPointerEvent()){ QPointerEvent * pev = static_cast <QPointerEvent *>(ev); for(QEventPoint&point:pev-> points()){ 开关(point.state()){ case QEventPoint :: State :: Pressed: if(reactToPress(point.position())) pev-> setExclusiveGrabber(point,this ); 打破; case QEventPoint :: State :: Updated: ... } } 返回true; } return QQuickItem :: event(ev); }
例如,QQuickFlickable :: childMouseEventFilter()以这种方式工作。这产生了一个有趣的结果:
可滑动的手柄现在触摸!
Qt 5的Flickable只能处理实际的鼠标事件和合成的鼠标事件,这涉及到许多开放的错误。Qt只支持一只鼠标,一个鼠标位置,一个光标(到目前为止,但是我们正在努力...),因此您无法用两根手指轻拂两个Flickable。如果您在Flickable中触摸了能够处理触摸事件的某个组件,但随后在允许的方向上在Flickable上拖动手指,则它将使用childMouseEventFilter()来窃取该组件的抓取;但是这涉及从实际触摸事件切换到合成鼠标事件,并且还记得将以下更新作为合成鼠标事件传递给Flickable。各种事情出了问题。好吧...那些日子已经过去了,因为Flickable :: childMouseEventFilter()不再关心QPointerEvents来自哪个设备。如果设置了pressDelay,则可以保留实际的触摸按键,然后在计时器到期时将其重播到其中的项目。是的,您现在也可以用多个手指拖动多个Flickable。
但是,多点触摸仍不能与其余的仅鼠标项(如MouseArea)一起使用,因为它们仍依赖于合成鼠标事件。但这是可以避免的。通常,请尝试使用事件处理程序而不是MouseArea,因为(顾名思义)它实际上不打算支持除鼠标交互以外的任何功能。
QTabletEvents(来自Wacom手写笔,Samsung S-pen,Apple Pencil等)也是具有更多属性的指针事件,并且可以由任何处理鼠标和触摸事件的与设备无关的代码来处理。但是,我们将继续努力改善这些体验。我们没有在QQuickItem中为其添加任何新的虚拟函数,但是它们很快就会被传递到QQuickItem :: event()。
我们仍在努力的另一件事是使Flickable在笔记本电脑触摸板上的性能更好。一个修复程序即将推出。
留给自己的设备
平台插件现在有责任发现和注册输入设备,以兑现每个QInputEvent告诉您确切来源的承诺。其中一些工作已经在某些平台上完成。(事实证明,这是有问题的。)您可以从QInputDevice :: devices()获取所有已注册设备的列表。qtdiag实用程序还会显示此列表。
QInputDevice是一个QObject,并且如果存在自然的层次结构,则它的parent()可以是另一个设备:例如,X11具有主设备和从设备,而数位板手写笔“属于”特定的数位板设备。在其他情况下,父对象只是平台插件中的一个对象,该对象拥有该设备以用于内存管理。
在未完成设备发现工作的平台上,QInputEvent :: device()永远不会为null,而可以是取自QInputDevice :: primaryKeyboard()或QPointingDevice :: primaryMouse()的通用实例。触摸屏设备是独一无二的。我们已经在Qt 5中做到了这一点。
QInputDevice :: seatName()对应于“座位”的Wayland概念:一个用户正在使用的一组设备。到目前为止,几乎没有多座位支持,但是随着时间的推移它将得到改善。如果配置多指针X服务器,则可以在不同设备上看到不同的席位名称,但是这些名称是根据xcb插件中的xinput ID自动生成的。在Wayway合成器(如Sway)上,可以给座椅任意命名;我们计划Qt最终将与之合作。
$ xinput list 虚拟核心指针id = 2 [主指针(3)] 虚拟核心XTEST指针id = 4 [从属指针(2)] ZSA Technology Labs ErgoDox EZ鼠标id = 11 [从属指针(2)] ZSA Technology Labs ErgoDox EZ用户控制id = 13 [从属指针(2)] Logitech MX Master 2S id = 15 [从属指针(2)] 虚拟核心键盘id = 3 [主键盘(2)] 虚拟核心XTEST键盘id = 5 [从属键盘(3) 电源按钮id = 6 [从属键盘(3)] 电源按钮id = 7 [从属键盘(3)] 睡眠按钮id = 8 [从属键盘(3)] UVC摄像机(046d:0992)id = 9 [从属键盘(3)] ZSA Technology Labs ErgoDox EZ id = 10 [从属键盘(3)] ZSA Technology Labs ErgoDox EZ系统控件id = 12 [从属键盘(3)] ZSA Technology Labs ErgoDox EZ键盘id = 14 [从属键盘(3)] ZSA Technology Labs ErgoDox EZ Consumer Control id = 16 [从属键盘(3)] Logitech MX Master 2S id = 17 [从属键盘(3)] 辅助指针id = 22 [主指针(23)] Microsoft Microsoft光学鼠标,由Starck id = 19 [从属指针(22)] 辅助XTEST指针id = 24 [从属指针(22)] 辅助键盘id = 23 [主键盘(22)] Apple,Inc.苹果键盘id = 20 [从属键盘(23)] 苹果公司Inc.苹果键盘id = 21 [从属键盘(23)] aux XTEST键盘id = 25 [从属键盘(23)] $ qtdiag 在“ xcb” 操作系统上的Qt 6.0.0(x86_64-little_endian-lp64共享(动态)调试版本;由GCC 10.2.0构建):Arch Linux [linux版本5.9.11-arch2-1] ... 输入设备:23 QInputDevice :: DeviceType :: Mouse“虚拟核心指针”,座位:“ 30002”功能:位置滚动悬停 QInputDevice :: DeviceType :: Keyboard“虚拟核心键盘”,座位:“ 30002”功能: QInputDevice :: DeviceType :: Mouse“辅助指针”,座:“ 170016”功能:位置滚动悬停 QInputDevice :: DeviceType :: Keyboard“辅助键盘”,座:“ 170016”功能: QInputDevice :: DeviceType :: Mouse“虚拟核心XTEST指针”,座: 30002“功能:位置滚动悬停 QInputDevice :: DeviceType :: Keyboard“虚拟核心XTEST键盘”,座位:“ 30002”功能: QInputDevice :: DeviceType ::键盘“电源按钮”,座位:“ 30002”功能: QInputDevice :: DeviceType ::键盘“电源按钮”,座位:“ 30002”功能: QInputDevice :: DeviceType ::键盘“睡眠按钮”,席位:“ 30002”功能: QInputDevice :: DeviceType ::键盘“ UVC摄像头(046d:0992)”,席位:“ 30002”功能: QInputDevice :: DeviceType ::键盘“ ZSA Technology Labs ErgoDox EZ”,席位:“ 30002功能: QInputDevice :: DeviceType :: Mouse“ ZSA Technology Labs ErgoDox EZ鼠标”,座椅:“ 30002”功能:位置滚动悬停 QInputDevice :: DeviceType :: Keyboard“ ZSA Technology Labs ErgoDox EZ System Control”,座椅:“ 30002 ”功能: QInputDevice :: DeviceType :: Mouse“ ZSA Technology Labs ErgoDox EZ消费者控制”,座位:“ 30002”功能:位置滚动悬停 QInputDevice :: DeviceType :: Keyboard“ ZSA Technology Labs ErgoDox EZ键盘”,座位:“ 30002”功能: QInputDevice :: DeviceType ::键盘“ ZSA Technology Labs ErgoDox EZ消费者控制”,位置:“ 30002”功能: QInputDevice :: DeviceType ::鼠标“ Logitech MX Master 2S”,位置:“ 30002”功能:位置滚动悬停 QInputDevice: :设备类型::键盘“ Logitech MX Master 2S”,位置:“ 30002”功能: QInputDevice ::设备类型::鼠标“ Starck的Microsoft Microsoft光学鼠标”,位置:“ 170016”功能:位置滚动悬停 QInputDevice :: DeviceType :: Keyboard“ Apple,Inc苹果键盘”,座位:“ 170016”功能: QInputDevice :: DeviceType ::键盘“ Apple,Inc苹果键盘”,座:“ 170016”功能: QInputDevice :: DeviceType ::鼠标“ aux XTEST指针”,座:“ 170016”功能:位置滚动悬停 QInputDevice :: DeviceType: :键盘“ aux XTEST键盘”,座位:“ 170016”功能:
QInputDevice :: availableVirtualGeometry()旨在告诉您该设备可以访问虚拟桌面的哪个区域。例如,您可能正在使用带有外部显示器的触摸屏笔记本电脑:那么触摸屏的QPointingDevice :: availableVirtualGeometry()应该与屏幕的QScreen :: geometry()相同。Wacom数位板可以映射到小于整个屏幕的区域,以提高绘图精度(使用xinput或特定于操作系统的控制面板)。但是同样,这项工作尚未在所有受支持的平台上完成。
合成鼠标事件
合成鼠标事件仍然存在,即使我们现在尝试减少对它们的依赖。
QEvent :: spontaneous()是我们必须区分OS生成的事件和合成事件的最古老的方法,但是它不适用于区分合成鼠标事件。在Qt 5中,添加了QMouseEvent :: source()来帮助您区分由其他设备,操作系统,Qt或应用程序与其他设备合成的鼠标事件。但随后我们发现,假设此类事件是从接触点合成的,这既诱人又是错误的。(例如,它可以从QTabletEvent合成。)因此,我们建议现在使用event->device()->type()
和/或pointerDevice()->pointerType()
区分它们。当QMouseEvent由其他类型的事件合成时,设备实例保持不变,因此您可以知道它的真正来源。
分类日志
自从几年前添加分类日志记录以来,我们一直在Qt中添加越来越多的内部日志记录。(git grep Q_LOGGING_CATEGORY
在qtbase和qtdeclarative中将找到许多用于调试Qt Quick应用程序的有用的工具。)解决鼠标和触摸交互问题的最有用的类别是qt.pointer.grab,因为抓取转换是这些问题的征兆或原因。但是还有更多:在交付项目和/或处理程序期间,您可以在QPA级别,QtGui级别,QQuickWindow中记录事件。您可以在各种项目和处理程序中记录交互的各个方面。