アニメーションループを止めずに Mouse を操作したときだけ反応するプログラム

HgEvent() 関数を使ってマウスのイベントを取ると、イベントが発生するまで(たとえばマウスのボタンを押すまで)プログラムが停止してしまいます。 そうではなく、例えばマウスを操作していなければ何もせず、操作したとき(たとえばマウスのボタンを押したとき)だけある種の処理をさせることが可能です。 そうした処理のことを一般にノン・ブロッキングな処理と呼びます。 HandyGraphic ではそのための HgEventNonBlocking() 関数が用意されています。

Mouse ボタンを押したときだけ反応する

以下にマウスのボタンを押したときだけ反応する方法を示します。

    HgSetEventMask(HG_MOUSE_DOWN); // MOUSE_DOWN を待つよう設定
    
    while(1) { // アニメーションのループ
        // 画面消去など様々な処理
           ・・・・・(途中略)・・・・・・・
        // === ここから位置取得の処理
        event = HgEventNonBlocking(); // 随時イベントがあれば採る
        if( event != NULL ) {
            mouseX=event->x;
            mouseY=event->y;
            printf("x=%5.2f, y=%5.2f\n", mouseX, mouseY);
            HgBox(mouseX, mouseY, 5.0, 5.0);
        }
        // === ここまで位置取得の処理
           ・・・・・(途中略)・・・・・・・
    }

以下にマウスだけでなくキーにも反応させる方法を示します。
このプログラムは x あるいは y キーを押すことで、縦横の進行方向を反転させます。

    HgSetEventMask(HG_MOUSE_DOWN | HG_KEY_DOWN); // 待ち受けるイベントを設定
    
    while(1) { // アニメーションのループ
        // 画面消去など様々な処理
           ・・・・・(途中略)・・・・・・・
        // === ここから位置取得の処理
        if( event != NULL ) {
            if( event->type == HG_MOUSE_DOWN ) {
                mouseX=event->x;
                mouseY=event->y;
                printf("x=%5.2f, y=%5.2f\n", mouseX, mouseY);
                HgBox(mouseX, mouseY, 5.0, 5.0);
            } else if( event->type == HG_KEY_DOWN ) {
                switch (event->ch) {
                    case 'x': dx*=(-1.0); break;
                    case 'y': dy*=(-1.0); break;
                }
            }
        }
        // === ここまで位置取得の処理
           ・・・・・(途中略)・・・・・・・
    }

サンプルコード

上記の内容を含めたサンプルコードを以下に置いておきます。

Mouse がいまどこにあるか調べたい

ところで HandyGraphic では「(クリックなどの操作をしていない状態で)マウスがいまどこに居るか」を直接的に得る関数は用意されていません。 ここでは HgEventNonBlocking() 関数を用いてこれをある程度実現する方法を示します。

Mouse の位置を取得する呼び出し方

以下のコードを自分のアニメーション処理に挟んでみると、マウスを動かしたときだけ mouseX, mouseY 情報が更新され、その位置に小さな四角が描画されることがわかるでしょう。

    while(1) { // アニメーションのループ
        // 画面消去など様々な処理
           ・・・・・(途中略)・・・・・・・
        // ========== ここからMoveイベント取得
        lastEventType = HG_NO_EVENT; // イベントなしで初期化
        while(1) { // たまっているイベントを掘り出して処理
            event = HgEventNonBlocking(); // イベントを一つ取り出す
            if( event == NULL ) break; // 取り出せなかった=たまっているイベントがなくなったので終了
            // DOWNしか待たない設定したのでDOWNとして処理すれば良い
            lastEventType = HG_MOUSE_MOVE; // MOUSE_MOVE を処理したことを記録
            mouseX = event->x; // その位置を記録
            mouseY = event->y;
        }
        // ========== 続いて取得したイベントに反応する処理
        if(lastEventType == HG_MOUSE_MOVE) { // MOVEイベントを受け取った場合に処理
            HgBox(mouseX, mouseY, 5.0, 5.0);
            printf("x=%5.2f, y=%5.2f\n", mouseX, mouseY);
        }
           ・・・・・(途中略)・・・・・・・
  }

HG_MOUSE_MOVE イベントも他のイベント同様、取得は HgEventNonBlocking() 関数で行えます。 ただ、上のプログラムではこれを一回のアニメーションループ内で、二つめの while() ループで繰り返し実行している点に注意して下さい。 これはHG_MOUSE_MOVE についてはかなり高速にイベントが発生するため、アニメーションループが一周するあいだに複数のイベントが HandyGraphic のシステムに蓄積されます。 これを次のアニメーションループに持ち越さないよう、つまり「一週遅れて古い位置情報」を取得するのを防ぐため、一度蓄積されているすべてのイベントを取り出しているわけです。

他のイベントとの共存:移動・ボタン押し下げ・キー入力

上の方法では HG_MOUSE_MOVE イベントの取得のためだけに HgEventNonBlocking() 関数を使っていますが、HG_MOUSE_DOWN イベントなども同時に取ろうとするともう少し工夫が要ります。 以下に、マウスの移動、ボタンの押し下げ、キー入力をそれぞれ処理するコードの例を示します。

        // ========== ここからMoveイベント取得
        lastEventType = HG_NO_EVENT; // イベントなしで初期化
        while(1) { // たまっているイベントを掘り出して処理
            event = HgEventNonBlocking(); // イベントを一つ取り出す
            if( event == NULL ) break; // 取り出せなかった=たまっているイベントがなくなったので終了
            if( event->type == HG_KEY_DOWN ) { // KEY_DOWN は無条件に処理
                keyCh = event->ch; // そのキーを記録
                lastEventType = event->type; // 処理したイベントの種類(KEY_DOWN)を保存
            } else if(( event->type == HG_MOUSE_DOWN && lastEventType != HG_KEY_DOWN ) ||
                      ( event->type == HG_MOUSE_MOVE && lastEventType == HG_NO_EVENT ) ) {
                // MOUSE_DOWN は KEY_DOWN より優先度低い(まだKEY_DOWNが残っていない時だけ処理)
                // MOUSE_DOWN は他のどのイベントより優先度低い(NO_EVENTの時だけ処理)
                mouseX = event->x; // マウスの位置を記録
                mouseY = event->y;
                lastEventType = event->type; // 処理したイベントの種類(MOUSE_DOWN or MOUSE_MOVE)を保存
            }
        }
        // ========== 続いて取得したイベントに反応する処理
        switch(lastEventType) { // 最終的に優先度をつけて残したイベントの種類に応じた処理を実行
            case HG_KEY_DOWN:
                printf("ch=%c KEY\n", keyCh);
                switch (keyCh) {
                    case 'x': dx*=(-1.0); break; // x を押すと移動方向を左右反転する
                    case 'y': dy*=(-1.0); break; // y なら上下反転
                }
                break;
            case HG_MOUSE_DOWN:
                HgBox(mouseX, mouseY, 15.0, 15.0);
                printf("x=%5.2f, y=%5.2f DOWN\n", mouseX, mouseY);
                break;
            case HG_MOUSE_MOVE:
                HgBox(mouseX, mouseY, 5.0, 5.0);
                printf("x=%5.2f, y=%5.2f\n", mouseX, mouseY);
                break;
        }

かなりややこしい書き方になってしまいました。 分かりにくいと思いますが、そのまま使ってくれれば良いと思います。

サンプルコード

上記の内容を含めたサンプルコードを以下に置いておきます。

mouseMove.c

mouseMoveDownKey.c

更に分かりにくくなったコード

上に示したサンプルコードは、これでもこのクラスの受講生にとって分かりやすいように書いています。 そのため、あまり普通はこうした冗長、あるいは場当たり的なロジックでは書かないと思います。 そのあたりをもう少し(このクラスの受講生らには内容が分からなくなるのを気にせずに)書くと、つまり一般的な C プログラムとしてはこんなふうにも書けるんだ、という参考として以下にコードを出しておきます。

mouseMoveDownKeyFunc.c


Yutaka Yasuda (yasuda@bakkers.gr.jp)