C
处理输入
本主题解释了如何将系统输入集成到 Qt Quick Ultralite 中。
概述
Qt Quick Ultralite 默认支持触摸事件和按键事件。这些事件由以下头文件定义的函数处理:platforminterface/platforminterface.h。
- Qul::PlatformInterface::handleTouchEvent() - 将触摸事件发送到 Qt Quick Ultralite 引擎。
- Qul::PlatformInterface::handleTouchCancelEvent() - 取消触摸事件。
- Qul::PlatformInterface::handleKeyEvent() - 将按键事件发送到 Qt Quick Ultralite 引擎。
注意:直接调用以上任何函数,例如从中断服务例程(ISR)中调用,可能会破坏 Qt Quick Ultralite 应用程序状态。相反,应该使用 Qul::EventQueue。您还可以使用自己的队列实现,但在此情况下,对上述任何函数的调用必须在 Qt Quick Ultralite 上下文中进行,这意味着它可能应该在 PlatformContext::exec 中调用。
对于单点触摸系统,请使用Qul::Platform::SinglePointTouchEventDispatcher 辅助类。它提供了一个更方便的方法来将触摸事件发送到 Qt Quick Ultralite 引擎,而不是使用 Qul::PlatformInterface::handleTouchEvent。后者需要您自己处理多点触摸方面。
除了默认的触摸和按键事件之外,还可以使用 Qul::EventQueue 类来发送自定义事件。此操作需要在应用程序中实现自定义 C++ 到 QML 接口以接收自定义事件。
实现输入处理
通常,系统通过中断接收触摸和按键事件。要将系统输入与 Qt Quick Ultralite 集成,请设置 ISR 以处理从系统输入设备接收到的中断。ISR 然后解释输入数据,将其转换为适当的格式,并将其发送到 Qt Quick Ultralite 引擎进行进一步处理。
使用调度器发送触摸事件
下面是一个使用上面提到的 Qul::Platform::SinglePointTouchEventDispatcher 辅助类的单点触摸系统的示例。为了清晰起见,这里没有显示特定平台的中断配置和获取触摸事件数据的函数。
首先,创建一个具有Qul::EventQueue的类,用于处理单点触摸事件,并将Qul::Platform::SinglePointTouchEventDispatcher作为私有成员。事件通过重写的Qul::EventQueue::onEvent()分发。同时,还创建了一个事件队列的静态实例。
class SinglePointTouchEventQueue : public Qul::EventQueue<Qul::Platform::SinglePointTouchEvent> { public: void onEvent(const Qul::Platform::SinglePointTouchEvent &event) QUL_DECL_OVERRIDE { touchEventDispatcher.dispatch(event); } void onQueueOverrun() QUL_DECL_OVERRIDE { clearOverrun(); PlatformInterface::log("Touch event discarded/overwritten. Consider increasing the queue size.\n"); } private: Qul::Platform::SinglePointTouchEventDispatcher touchEventDispatcher; }; static SinglePointTouchEventQueue touchEventQueue;
接下来,实现getTouchEvent()
辅助函数,将平台特定的触摸数据转换为Qul::Platform::SinglePointTouchEvent。
Qul::Platform::SinglePointTouchEvent getTouchEvent() { static Qul::Platform::SinglePointTouchEvent event; int x = 0, y = 0; bool pressed = false; // Here would be platform specific code to fetch touch data and store it to above x, y and // pressed variables or directly to the event variable. event.x = x; event.y = y; event.pressed = pressed; event.timestamp = Qul::Platform::getPlatformInstance()->currentTimestamp(); return event; }
注意:使用PlatformContext::currentTimestamp确保输入事件的时间戳与其他Qt Quick Ultralite引擎的时钟相同。
最后,实现touchISR()
函数来处理触摸控制器的中断并向队列中投递事件。
void touchISR() { touchEventQueue.postEventFromInterrupt(getTouchEvent()); }
使用事件队列发送触摸事件
可以使用Qul::PlatformInterface::handleTouchEvent()与Qul::EventQueue将触摸事件发送到Qt Quick Ultralite引擎。
以下是一个示例。
class TouchPointEventQueue : public Qul::EventQueue<Qul::PlatformInterface::TouchPoint> { public: void onEvent(const Qul::PlatformInterface::TouchPoint &point) QUL_DECL_OVERRIDE { Qul::PlatformInterface::handleTouchEvent(NULL, /* Primary screen will be used */ Qul::Platform::getPlatformInstance()->currentTimestamp(), &point, 1); /* only one touch point */ } }; static TouchPointEventQueue touchPointQueue; void touchEventISR() { static Qul::PlatformInterface::TouchPoint point; // Here would be code to fetch platform specific touch data and store it into point variable. // To keep the example simple we just send constant data. point.id = 0; point.state = Qul::PlatformInterface::TouchPoint::Stationary; point.positionX = 100; point.positionY = 100; point.areaX = 1.f; point.areaY = 1.f; point.pressure = 0.1f; point.rotation = 1.f; touchPointQueue.postEventFromInterrupt(point); }
发送多屏幕触摸事件
尽管Qt Quick Ultralite目前尚不支持多点和多个屏幕,以下示例仍展示了如何实现。
// These are just dummy handles for the example Qul::PlatformInterface::Screen primaryScreenHandle; Qul::PlatformInterface::Screen secondaryScreenHandle; // multi-touch with 2 touch points const int numTouchPoints = 2; struct TouchPoints { Qul::PlatformInterface::TouchPoint points[numTouchPoints]; }; class PlatformTouchEventQueue : public Qul::EventQueue<TouchPoints> { public: PlatformTouchEventQueue(Qul::PlatformInterface::Screen *s) : screen(s) {} void onEvent(const TouchPoints &p) QUL_DECL_OVERRIDE { Qul::PlatformInterface::handleTouchEvent(screen, Qul::Platform::getPlatformInstance()->currentTimestamp(), p.points, numTouchPoints); } private: Qul::PlatformInterface::Screen *screen; }; static PlatformTouchEventQueue primaryScreenTouchEventQueue(&primaryScreenHandle); static PlatformTouchEventQueue secondaryScreenTouchEventQueue(&secondaryScreenHandle); void primaryTouchISR() { static TouchPoints p; // Here would be platform specific code to fetch touch data for primary screen. To keep the // example simple we just send constant data. for (int i = 0; i < numTouchPoints; i++) { p.points[i].id = i; p.points[i].state = Qul::PlatformInterface::TouchPoint::Stationary; p.points[i].positionX = 100; p.points[i].positionY = 100; p.points[i].areaX = 1.f; p.points[i].areaY = 1.f; p.points[i].pressure = 0.1f; p.points[i].rotation = 1.f; } primaryScreenTouchEventQueue.postEventFromInterrupt(p); } void secondaryTouchISR() { static TouchPoints p; // Here would be platform specific code to fetch touch data for secondary screen. To keep the // example simple we just send constant data. for (int i = 0; i < numTouchPoints; i++) { p.points[i].id = i; p.points[i].state = Qul::PlatformInterface::TouchPoint::Stationary; p.points[i].positionX = 150; p.points[i].positionY = 150; p.points[i].areaX = 0.5f; p.points[i].areaY = 0.5f; p.points[i].pressure = 0.5f; p.points[i].rotation = 2.f; } secondaryScreenTouchEventQueue.postEventFromInterrupt(p); }
处理键盘输入
如果系统有键盘,您可以使用Qul::PlatformInterface::handleKeyEvent()与Qul::EventQueue将键盘事件发送给应用程序。Qt Quick Ultralite引擎会将键盘事件传递给应用程序进行进一步处理,而不进行解释。任何应用程序中的视觉原语都可以接收键盘事件,有关更多信息,请参阅Keys QML类型。
以下是一个发送键盘按键事件的示例
struct KeyboardEvent { uint64_t timestamp; Qul::PlatformInterface::KeyEventType type; int key; unsigned int nativeScanCode; unsigned int modifiers; uint32_t ucs4; bool autorepeat; }; class PlatformKeyboardEventQueue : public Qul::EventQueue<KeyboardEvent> { void onEvent(const KeyboardEvent &event) QUL_DECL_OVERRIDE { Qul::PlatformInterface::handleKeyEvent(event.timestamp, event.type, event.key, event.nativeScanCode, event.modifiers, NULL, event.autorepeat, event.ucs4); } }; static PlatformKeyboardEventQueue keyboardEventQueue; void keyboardISR() { // Here would be platform specific code to fetch data from the keyboard. static KeyboardEvent event; event.timestamp = Qul::Platform::getPlatformInstance()->currentTimestamp(); event.type = Qul::PlatformInterface::KeyPressEvent; event.key = Qul::PlatformInterface::Key_A; event.nativeScanCode = 0; event.modifiers = Qul::PlatformInterface::NoKeyboardModifier; event.ucs4 = 0x61; event.autorepeat = false; keyboardEventQueue.postEventFromInterrupt(event); }
注意:Qt Quick Ultralite仅将按键类型、键码和原生扫描码传送给应用程序。之前示例中显示的附加参数不会被转发。
自定义输入处理
除了触摸和键盘输入事件外,系统可能还有其他类型的事件需要由应用程序处理。例如,一个温度传感器或旋钮,允许您向系统提供输入。以下部分展示了如何处理这些类型的事件。
温度传感器输入
首先,通过C++到QML接口使用Qul::EventQueue将平台上的温度读数发送到应用程序。该接口是一个Qul::Singleton,可以通过instance()
函数访问。此外,定义以下属性和方法:
temperatureChanged
- 接收温度读数的Qul::Signal。Unit
- 不同温度单位的枚举。- Qul::Property - 跟踪单位变化。
setUnit()
- 设置新温度单位并转换当前值的方法。onEvent()
- 重写的Qul::EventQueue::onEvent()方法,使用temperatureChanged
信号通知温度读数。
由于接口定义为Qul::Singleton,因此防止了外部构造和复制。包含C++到QML接口类的头文件必须通过QmlProject文件中的InterfaceFiles.files引入到QML。
struct TemperatureInput : public Qul::Singleton<TemperatureInput>, public Qul::EventQueue<double> { Qul::Signal<void(double value)> temperatureChanged; enum Unit { Celsius, Fahrenheit, Kelvin }; Qul::Property<TemperatureInput::Unit> unit; void setUnit(TemperatureInput::Unit newUnit) { switch (unit.value()) { case Celsius: if (newUnit == Celsius) return; newUnit == Kelvin ? cachedValue += 273.15 : cachedValue = cachedValue * 1.8 + 32.0; break; case Fahrenheit: if (newUnit == Fahrenheit) return; newUnit == Kelvin ? cachedValue = (cachedValue - 32.0) / 1.8 + 273.15 : cachedValue = (cachedValue - 32.0) / 1.8; break; case Kelvin: if (newUnit == Kelvin) return; newUnit == Celsius ? cachedValue -= 273.15 : cachedValue = (cachedValue - 273.15) * 1.8 + 32.0; break; } unit.setValue(newUnit); temperatureChanged(cachedValue); } void onEvent(const double &temperature) QUL_DECL_OVERRIDE { temperatureChanged(temperature); cachedValue = temperature; } private: // Create friendship for a base class to be able to access private constructor friend struct Qul::Singleton<TemperatureInput>; TemperatureInput() {} TemperatureInput(const TemperatureInput &); TemperatureInput &operator=(const TemperatureInput &); double cachedValue; };
现在您准备好发送温度事件时,通过
void temperatureSensorISR() { // Here would be platform specific code to read temperature sensor value. switch (TemperatureInput::instance().unit.value()) { case TemperatureInput::Celsius: TemperatureInput::instance().postEventFromInterrupt(30.0); break; case TemperatureInput::Fahrenheit: TemperatureInput::instance().postEventFromInterrupt(86.0); break; case TemperatureInput::Kelvin: TemperatureInput::instance().postEventFromInterrupt(303.15); } }
下面是一个简单的温度读取QML应用的示例看起来可能是什么样的。
import QtQuick 2.15 Rectangle { color: "#41CD52" Text { id: temperature anchors.centerIn: parent font.pixelSize: 45 text: "0" Text { id: temperatureUnit property TemperatureInput.Unit unit: TemperatureInput.unit anchors.left: temperature.right anchors.top: temperature.top font.pixelSize: 20 onUnitChanged: { if(unit == TemperatureInput.Celsius) { text = "°C" } else if(unit == TemperatureInput.Fahrenheit) { text = "°F" } else if(unit == TemperatureInput.Kelvin) { text = "K" } } Component.onCompleted: { TemperatureInput.unit = TemperatureInput.Celsius } } TemperatureInput.onTemperatureChanged: { temperature.text = value } } MouseArea { anchors.fill: parent onReleased: { if(temperatureUnit.unit == TemperatureInput.Celsius) { TemperatureInput.setUnit(TemperatureInput.Fahrenheit) } else if(temperatureUnit.unit == TemperatureInput.Fahrenheit) { TemperatureInput.setUnit(TemperatureInput.Kelvin) } else if(temperatureUnit.unit == TemperatureInput.Kelvin) { TemperatureInput.setUnit(TemperatureInput.Celsius) } } } }
请注意,应用程序的用户界面很简单,使用
旋转按键控制输入
以下示例演示如何处理旋转按键控制输入。旋转按键控制类似于您在汽车上可以找到的控制旋钮,例如用于控制汽车的用户界面。在我们的示例中,旋转旋钮可以调整四个方向,向下、向上、向左和向右。它可以被按下、向左或向右旋转,并且有一个返回按钮。为了简化问题,旋转没有动态范围,也不能旋转360°。
现在,引入自定义的
struct RotaryKnobInputEvent : public Qul::Object { enum Action { NudgeDown, NudgeUp, NudgeLeft, NudgeRight, Push, Back, RotateLeft, RotateRight }; enum ActionState { ActionStart, ActionRepeat, ActionStop }; RotaryKnobInputEvent(Action action, ActionState state) : action(action) , actionState(state) {} RotaryKnobInputEvent() : action(NudgeDown) , actionState(ActionStart) {} Action action; ActionState actionState; };
接下来,引入旋钮输入的C++到QML接口。
struct RotaryKnobInput : public Qul::Singleton<RotaryKnobInput>, public Qul::EventQueue<RotaryKnobInputEvent> { Qul::Signal<void(int action, int state)> rotaryKnobEvent; void onEvent(const RotaryKnobInputEvent &event) { rotaryKnobEvent(event.action, event.actionState); } private: // Create friendship for a base class to be able to access private constructor friend struct Qul::Singleton<RotaryKnobInput>; RotaryKnobInput() {} RotaryKnobInput(const RotaryKnobInput &); RotaryKnobInput &operator=(const RotaryKnobInput &); };
现在,您可以向应用程序发送旋转拨盘事件。这里有一个模拟的 rotaryKnobISR()
函数,它作为旋转拨盘输入设备的ISR。它使用 RotaryKnobInput
单例实例来转发事件。为了测试目的,它旋转拨盘状态和操作。如果您没有实际的旋转拨盘设备,可以使用按钮连接来试玩。
void rotaryKnobISR() { // Here would be platform specific code to read state from rotary knob device. static int action = RotaryKnobInputEvent::ActionStart; static int state = RotaryKnobInputEvent::NudgeDown; RotaryKnobInput::instance().postEventFromInterrupt( RotaryKnobInputEvent(RotaryKnobInputEvent::Action(action), RotaryKnobInputEvent::ActionState(state))); if (++state > RotaryKnobInputEvent::ActionStop) { state = RotaryKnobInputEvent::ActionStart; if (++action > RotaryKnobInputEvent::RotateRight) action = RotaryKnobInputEvent::NudgeDown; } }
最后,这是一个简单的测试应用程序,它接收旋转拨盘事件并在一个 Text 项中显示接收的事件。
import QtQuick 2.15 Rectangle { color: "#41CD52" Text { id: knobState anchors.centerIn: parent font.pixelSize: 30 text: "Waiting for rotary knob..." } RotaryKnobInput.onRotaryKnobEvent: { knobState.text = knobStateToString(state) + ": " + knobActionToString(action) } function knobActionToString(action : int) : string { if(action == RotaryKnobInputEvent.NudgeDown) return "NudgeDown" else if(action == RotaryKnobInputEvent.NudgeUp) return "NudgeUp" else if(action == RotaryKnobInputEvent.NudgeLeft) return "NudgeLeft" else if(action == RotaryKnobInputEvent.NudgeRight) return "NudgeRight" else if(action == RotaryKnobInputEvent.Push) return "Push" else if(action == RotaryKnobInputEvent.Back) return "Back" else if(action == RotaryKnobInputEvent.RotateLeft) return "RotateLeft" else if(action == RotaryKnobInputEvent.RotateRight) return "RotateRight" } function knobStateToString(state : int) : string { if(state == RotaryKnobInputEvent.ActionStart) return "ActionStart" else if(state == RotaryKnobInputEvent.ActionRepeat) return "ActionRepeat" else if(state == RotaryKnobInputEvent.ActionStop) return "ActionStop" } }
在特定Qt许可证下可用。
了解更多。