このページでは

C

カスタム入力デバイスの取り扱い

タッチや キーの入力イベントの他に、システムにはアプリケーションによって処理されなければならない他のタイプの入力があるかもしれません。例えば、温度センサーや回転ノブなどです。以下のセクションでは、これらのタイプの入力を処理する方法を示します。

温度センサー入力

まず、Qul::EventQueue を使って、プラットフォームからアプリケーショ ンに温度測定値を送信するためのC++ to QML インタフェースを紹介します。このインターフェースはQul::Singleton であり、instance() 関数を使ってアクセスすることができます。また、以下のプロパティとメソッドを定義します:

  • temperatureChanged -Qul::Signal 温度測定値を受信する。
  • Unit - さまざまな温度単位の列挙
  • Qul::Property - 単位の変更を追跡するための enumeration。
  • setUnit() - 新しい温度単位を設定し、現在の値を新しい単位に変換するセッター・メソッド。
  • onEvent() -temperatureChanged シグナルを使用して温度測定値を通知するためのQul::EventQueue::onEvent() メソッドの再実装。

定義されているインターフェイスはQul::Singleton であるため、外部からの構築やコピーはできません。

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

温度イベントを送信する準備ができたので、TemperatureInput シングルトンインスタンス経由でイベントをポストする。イベントはtemperatureSensorISR() 関数からポストされます。この関数は温度センサー用のプラットフォーム固有の割り込みハンドラーです。unit 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);
    }
}

C++ から QML へのインターフェースクラスを含むヘッダは、QmlProject ファイルのInterfaceFiles.filesを使って QML に導入する必要があります:

Project {
    ...
    InterfaceFiles {
        files: ["temperatureinput.h"]
    }
}

以下に簡単な温度リーダーQMLアプリケーションを示します:

import QtQuick 2.15

Rectangle {
    Text {
        id: temperature
        anchors.centerIn: parent
        text: "0"

        Text {
            id: temperatureUnit
            property TemperatureInput.Unit unit: TemperatureInput.unit
            anchors.left: temperature.right
            anchors.top: temperature.top

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

このアプリケーションのUIはシンプルで、Rectangle をルート項目とし、Text 温度値と単位を表す項目がいくつかあります。MouseArea 、タッチ操作で温度単位を切り替えることができます。また、TemperatureInput シングルトンからのtemperatureChanged シグナルを追跡します。temperatureUnit Text アイテムの unit プロパティは、TemperatureInput シングルトンの unitQul::Property インスタンスにバインドされています。これにより、QMLとC++の両方からプロパティを変更することができます。最後に、画面をタッチすると、setUnit 関数を介して単位が変更され、センサーからの次の読み取りを待つことなく、現在の温度値を即座に変換できるようになります。

ロータリーノブの制御入力

この例では、回転ノブ制御入力をどのように扱うかを示します。ロータリ・ノブ・コントロールとは、例えば自動車のユーザー・インターフェイスをコントロールするためのコントロール・ノブのようなものです。この例では、回転ノブは下、上、左、右の4方向に動かすことができる。押すこともできるし、左右に回転させることもできる。シンプルにするため、回転にはダイナミックレンジがなく、360°フル回転させることはできない。

次に、RotaryKnobInputEvent のカスタム・タイプを紹介しよう。これはQul::Object を継承した単純な構造体で、QMLでこの型を使えるようにするためのものです。Action 列挙型を導入し、ユーザーが回転ノブでできるさまざまなアクションを表現します。また、アクションのライフタイムを追跡するために、ActionState 、開始状態と停止状態、そして繰り返されるアクションのための繰り返し状態が導入されています。このカスタム型のコンストラクタは、アクションとステートのパラメータを受け取る。最後に、イベント・データを格納するアクション・メンバとステート・メンバを持つ。

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

RotaryKnobInputEvent を含むヘッダは、QmlProject ファイル中のInterfaceFiles.filesを使って QML に導入する必要があります:

Project {
    ...
    InterfaceFiles {
        files: ["rotaryknobinputevent.h"]
    }
}

次に、ノブ入力用のC++ to QML インタフェースを紹介します。これはQul::EventQueue を持つQul::Singleton です。アプリケーションに回転ノブactionstate を送るためにQul::Signal を1つ導入します。また、Qul::EventQueueonEvent() 関数をオーバーライドし、プラットフォームからイベントを受信し、rotaryKnobEvent シグナルを介してアプリケーションに転送します。このインターフェースはQul::Singleton であり、外部からの構築やコピーは禁止されている。

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

RotaryKnobInput インタフェースを含むヘッダは、InterfaceFiles.files を介して QML に導入する必要があります:

Project {
    ...
    InterfaceFiles {
        files: ["rotaryknobinputevent.h",
                "rotaryknobinput.h"
               ]
    }
}

これで、回転ノブのイベントをアプリケーションに送ることができます。以下はダミーの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 {
    Text {
        id: knobState
        anchors.centerIn: parent
        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"
    }
}


詳細はこちらをご覧ください。