Qt-输入事件模型
Qt Quick中鼠標和觸摸事件的傳遞非常復雜,幾年前我們就清楚了我們需要重構事件繼承層次結構,為各種事件類型提供一些通用的API,以便可以將更多傳遞代碼共享。在Qt 5.8中,我們添加了QQuickPointerEvent和關聯(lián)的類型,作為對可能的樣子進行原型制作的一種方法。它們是QObjects。從那時起,QQuickWindow一直在交付這些包裝器事件,這些包裝器事件將原始事件承載在內部。現(xiàn)在終于在Qt 6中,我們已經(jīng)能夠完成QEvent重構,因此QQuickWindow不再需要包裝器。除此之外,我們還能夠添加一些功能并修復了一些錯誤。Qt 5中似乎很難解決的許多其他錯誤至少應在以后修復。
向QPointerEvent和QEventPoint問好
現(xiàn)在,繼承層次結構如下所示:
QPointerEvent是來自指針設備(鼠標,觸摸屏,平板電腦手寫筆)的所有事件的新抽象類型。它具有通用API,能夠以與設備無關的方式處理所有這些問題。由于QTouchEvent可以在一個事件中攜帶多個接觸點,因此我們對這一概念進行了標準化:每個QPointerEvent都可能代表QEventPoint實例的集群(即使大多數(shù)事件僅攜帶一個點),因此具有適當?shù)腁PI:points(),point(i)和pointCount()。
每個QInputEvent(包括QPointerEvent)都帶有指向它來自的QInputDevice的指針。即使處理合成鼠標事件,事件處理代碼也可以以設備特定的方式響應。
每個QEventPoint都有速度。Qt Quick在Qt 5中使用的卡爾曼濾波器已移至QtGui,因此無論事件傳遞到何處,都可以使用最近幾次移動的平均速度。這樣,無論速度來自哪個設備,都可以實現(xiàn)對速度敏感的行為(例如,將快速拖動與快速拖動區(qū)別開來,或響應特定的運動方向)。對于這樣的目的,瞬時速度通常太不穩(wěn)定,但是如果需要,可以將其計算為(globalPosition() - globalLastPosition()) / (timestamp() - lastTimestamp())。
QSinglePointEvent是另一種抽象類型,用于標準化位置訪問器,該位置訪問器以前在QMouseEvent,QTabletEvent,QHoverEvent,QWheelEvent等中單獨實現(xiàn)且不一致。它們現(xiàn)在都是浮點數(shù)。position()替換pos()和posF(),scenePosition()替換windowPos(),而globalPosition()替換screenPos()。舊的訪問器現(xiàn)在仍然存在,但是已被棄用:例如,僅由于處理QMouseEvent,Qt 5應用程序就不會遇到SC中斷。QEventPoint代替了QTouchEvent :: TouchPoint,但是出于源兼容性的考慮,有一個“使用中”的聲明。
我已經(jīng)分手了,并添加了新的qevent-accessors檢查,這可能為您省去一些麻煩:它可以自動應用“ fixits”來擺脫由于事件訪問器重命名而引起的棄用警告。(是的,我們應該在這一變化的上游。)
C ++中與設備無關的事件處理
現(xiàn)在,在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()以這種方式工作。這產(chǎn)生了一個有趣的結果:
可滑動的手柄現(xiàn)在觸摸!
Qt 5的Flickable只能處理實際的鼠標事件和合成的鼠標事件,這涉及到許多開放的錯誤。Qt只支持一只鼠標,一個鼠標位置,一個光標(到目前為止,但是我們正在努力...),因此您無法用兩根手指輕拂兩個Flickable。如果您在Flickable中觸摸了能夠處理觸摸事件的某個組件,但隨后在允許的方向上在Flickable上拖動手指,則它將使用childMouseEventFilter()來竊取該組件的抓取;但是這涉及從實際觸摸事件切換到合成鼠標事件,并且還記得將以下更新作為合成鼠標事件傳遞給Flickable。各種事情出了問題。好吧...那些日子已經(jīng)過去了,因為Flickable :: childMouseEventFilter()不再關心QPointerEvents來自哪個設備。如果設置了pressDelay,則可以保留實際的觸摸按鍵,然后在計時器到期時將其重播到其中的項目。是的,您現(xiàn)在也可以用多個手指拖動多個Flickable。
但是,多點觸摸仍不能與其余的僅鼠標項(如MouseArea)一起使用,因為它們仍依賴于合成鼠標事件。但這是可以避免的。通常,請嘗試使用事件處理程序而不是MouseArea,因為(顧名思義)它實際上不打算支持除鼠標交互以外的任何功能。
QTabletEvents(來自Wacom手寫筆,Samsung S-pen,Apple Pencil等)也是具有更多屬性的指針事件,并且可以由任何處理鼠標和觸摸事件的與設備無關的代碼來處理。但是,我們將繼續(xù)努力改善這些體驗。我們沒有在QQuickItem中為其添加任何新的虛擬函數(shù),但是它們很快就會被傳遞到QQuickItem :: event()。
我們仍在努力的另一件事是使Flickable在筆記本電腦觸摸板上的性能更好。一個修復程序即將推出。
留給自己的設備
平臺插件現(xiàn)在有責任發(fā)現(xiàn)和注冊輸入設備,以兌現(xiàn)每個QInputEvent告訴您確切來源的承諾。其中一些工作已經(jīng)在某些平臺上完成。(事實證明,這是有問題的。)您可以從QInputDevice :: devices()獲取所有已注冊設備的列表。qtdiag實用程序還會顯示此列表。
QInputDevice是一個QObject,并且如果存在自然的層次結構,則它的parent()可以是另一個設備:例如,X11具有主設備和從設備,而數(shù)位板手寫筆“屬于”特定的數(shù)位板設備。在其他情況下,父對象只是平臺插件中的一個對象,該對象擁有該設備以用于內存管理。
在未完成設備發(fā)現(xiàn)工作的平臺上,QInputEvent :: device()永遠不會為null,而可以是取自QInputDevice :: primaryKeyboard()或QPointingDevice :: primaryMouse()的通用實例。觸摸屏設備是獨一無二的。我們已經(jīng)在Qt 5中做到了這一點。
QInputDevice :: seatName()對應于“座位”的Wayland概念:一個用戶正在使用的一組設備。到目前為止,幾乎沒有多座位支持,但是隨著時間的推移它將得到改善。如果配置多指針X服務器,則可以在不同設備上看到不同的席位名稱,但是這些名稱是根據(jù)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系統(tǒng)控件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” 操作系統(tǒng)上的Qt 6.0.0(x86_64-little_endian-lp64共享(動態(tài))調試版本;由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()旨在告訴您該設備可以訪問虛擬桌面的哪個區(qū)域。例如,您可能正在使用帶有外部顯示器的觸摸屏筆記本電腦:那么觸摸屏的QPointingDevice :: availableVirtualGeometry()應該與屏幕的QScreen :: geometry()相同。Wacom數(shù)位板可以映射到小于整個屏幕的區(qū)域,以提高繪圖精度(使用xinput或特定于操作系統(tǒng)的控制面板)。但是同樣,這項工作尚未在所有受支持的平臺上完成。
合成鼠標事件
合成鼠標事件仍然存在,即使我們現(xiàn)在嘗試減少對它們的依賴。
QEvent :: spontaneous()是我們必須區(qū)分OS生成的事件和合成事件的最古老的方法,但是它不適用于區(qū)分合成鼠標事件。在Qt 5中,添加了QMouseEvent :: source()來幫助您區(qū)分由其他設備,操作系統(tǒng),Qt或應用程序與其他設備合成的鼠標事件。但隨后我們發(fā)現(xiàn),假設此類事件是從接觸點合成的,這既誘人又是錯誤的。(例如,它可以從QTabletEvent合成。)因此,我們建議現(xiàn)在使用event->device()->type()和/或pointerDevice()->pointerType()區(qū)分它們。當QMouseEvent由其他類型的事件合成時,設備實例保持不變,因此您可以知道它的真正來源。
分類日志
自從幾年前添加分類日志記錄以來,我們一直在Qt中添加越來越多的內部日志記錄。(git grep Q_LOGGING_CATEGORY在qtbase和qtdeclarative中將找到許多用于調試Qt Quick應用程序的有用的工具。)解決鼠標和觸摸交互問題的最有用的類別是qt.pointer.grab,因為抓取轉換是這些問題的征兆或原因。但是還有更多:在交付項目和/或處理程序期間,您可以在QPA級別,QtGui級別,QQuickWindow中記錄事件。您可以在各種項目和處理程序中記錄交互的各個方面。
總結
- 上一篇: Dubbo + RestEasy 实现文
- 下一篇: 线程中发送消息阻塞问题解决