このページでは

C

カスタム入力デバイスの処理

タッチやキー入力イベント以外にも、アプリケーションで処理しなければならない他の種類の入力がシステムにある場合があります。例えば、温度センサーや、システムへの入力を可能にするロータリーノブなどです。以下のセクションでは、これらの種類の入力を処理する方法について説明します。

温度センサーからの入力

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

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

定義されたインターフェースが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 シグナルを監視しています。temperatureUnitText 項目のunitプロパティは、TemperatureInputシングルトンのunitQul::Property インスタンスにバインドされています。これにより、QMLとC++の両方からこのプロパティを変更できます。最後に、画面をタッチするとsetUnit 関数を通じて単位が変更され、センサーからの次の読み取りを待つことなく、現在の温度値を即座に変換できるようになります。

ロータリーノブコントロールの入力

この例では、ロータリーノブ制御入力の処理方法を示します。ロータリーノブ制御は、例えば自動車のユーザーインターフェースを操作するために見られるような、コントロールノブのようなものです。 この例では、ロータリーノブは下、上、左、右の4方向に微調整できます。押し込むことも、左右に回転させることもでき、戻るボタンも備わっています。簡略化のため、回転にはダイナミックレンジがなく、360°フル回転することはできません。

ここで、カスタム型RotaryKnobInputEvent を導入します。これは、QMLでこの型を使用できるようにQul::Object を継承した単純な構造体です。この型では、ユーザーがロータリーノブで行えるさまざまな操作を表すために、Action 列挙型を導入しています。また、開始状態と停止状態、および繰り返し操作のためのrepeat状態を用いて操作のライフサイクルを追跡するために、ActionState も導入しています。 このカスタム型のコンストラクタは、action および state パラメータを受け取ります。最後に、イベントデータを格納するための action および state メンバーが用意されています。

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++からQMLへのインターフェースを導入します。これは、Qul::EventQueue を持つQul::Singleton です。このインターフェースは、ロータリーノブのaction およびstate をアプリケーションに送信するための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"
               ]
    }
}

これで、ロータリーノブのイベントをアプリケーションに送信できるようになりました。以下は、ロータリーノブ入力デバイスのISRとして機能する、ダミーのrotaryKnobISR() 関数です。この関数は、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"
    }
}

特定のQtライセンスの下で利用可能です。
詳細はこちらをご覧ください。