C

处理输入

本主题解释了如何将系统输入集成到 Qt Quick Ultralite 中。

概述

Qt Quick Ultralite 默认支持触摸事件和按键事件。这些事件由以下头文件定义的函数处理:platforminterface/platforminterface.h

注意:直接调用以上任何函数,例如从中断服务例程(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;
};

现在您准备好发送温度事件时,通过单例实例发布事件。这些事件由函数发布,该函数是针对温度传感器的特定平台的中断处理函数。注意这里是如何使用Qul::Property来按应用程序请求的单位发送读数的。如果传感器可以提供多个单位的温度读数以避免额外的转换,这样做很方便。

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)
            }
        }
    }
}

请注意,应用程序的用户界面很简单,使用作为根项,并为温度值和单位添加了几个项。项允许应用程序通过触摸切换温度单位。它还跟踪来自单例的信号。temperatureUnit Text项目的unit属性绑定到TemperatureInput单例的unit Qul::Property实例。这允许您从QML和C++中更改属性。最后,触摸屏幕通过函数切换单元,以便在不等待传感器下一次读取的情况下立即转换当前温度值。

旋转按键控制输入

以下示例演示如何处理旋转按键控制输入。旋转按键控制类似于您在汽车上可以找到的控制旋钮,例如用于控制汽车的用户界面。在我们的示例中,旋转旋钮可以调整四个方向,向下、向上、向左和向右。它可以被按下、向左或向右旋转,并且有一个返回按钮。为了简化问题,旋转没有动态范围,也不能旋转360°。

现在,引入自定义的RotaryKnobInputEvent类型。这是一个简单的结构体,继承自Qul::Object,以便您可以在QML中使用此类型。它引入了Action枚举来表示用户可以使用旋钮执行的各种操作。它还引入了ActionState,用于跟踪操作的生命周期,包括开始和停止状态以及重复操作状态。这个自定义类型的构造函数接受动作和状态参数。最后,它有为存储事件数据的动作和状态成员。包含RotaryKnobInputEvent的类型头必须通过QmlProject文件中使用InterfaceFiles.files引入QML。

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接口。Qul::Singleton带有Qul::EventQueue。它引入了一个Qul::Signal,用于将旋转旋钮的动作和状态发送到应用程序。它还覆盖了Qul::EventQueue的onEvent()函数,以接收来自平台的事件并通过rotaryKnobEvent信号将它们转发到应用程序。作为一个Qul::Singleton,它阻止了外部构造和复制。包含RotaryKnobInput接口的类型头必须通过InterfaceFiles.files引入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许可证下可用。
了解更多。