読者です 読者をやめる 読者になる 読者になる

すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

【openFrameworks】トグルボタンをつくる【ofEvent】

openFrameworks

こんばんは.1000chです.

前回 ofEventの基礎的なところを学んだので,今回はタッチイベントに応じたボタンをつくってみます.

デモ

つくるのはこんな感じ.

画面上をドラッグすると,背景色が変わります.oFビギナーが一番始めにつくるであろうやつですね. ただし今回は,右上のボタンを押して,ロック状態になっている時は色変化無しとします. このボタン関連の部分をofEventを使って実装していきます.

実装はoF0.8.3 for iOSで行ってますが,for macでもだいたい同じだと思います.

画面の色変える機能をつくる

#ofApp.h 
# ofAppクラスに追加

ofColor bg_color;

# ofApp.cpp
//--------------------------------------------------------------
void ofApp::setup(){
    ofBackground(0);
}

//--------------------------------------------------------------
void ofApp::update(){
    ofBackground(bg_color);
}

//--------------------------------------------------------------
void ofApp::touchMoved(ofTouchEventArgs & touch){
    bg_color = ofColor(255 * touch.y / ofGetHeight());
}

まーなんてこたないですね.

ボタンのベースをつくる

まずはイベントはおいといて,表示するボタンのベースをつくります. ボタンサイズ,表示画像のパス指定などですね.

# ToggleButton.h

#include "ofMain.h"

class ToggleButton{

public:
    
    float width;
    float height;
    ofVec2f offset;
    ofImage img_on;
    ofImage img_off;

    bool is_on;
    
    void setup(string path_on_img, string path_off_img);
    void update();
    void draw();
};

# ToggleButton.cpp

#include "ToggleButton.h"

void ToggleButton::setup(string path_on_img, string path_off_img){
    img_on.loadImage(path_on_img);
    img_off.loadImage(path_off_img);
    width = 100;
    height = 100;
    offset = ofVec2f(0,0);

    is_on = true;
}

void ToggleButton::draw(){
    ofPushMatrix();
    ofTranslate(0,0,1);
    if(is_on){
        img_on.draw(offset, width, height);
    } else {
        img_off.draw(offset, width, height);
    }
    ofPopMatrix();
}

is_onフラグで描画する画像を切り替える.ただそれだけです. 現状はロジックが無いので常にon状態の画像が表示されます.

タッチイベントを監視する

では本題行きましょう.

作ったボタンクラスがタッチイベントに反応する為には,ofAppクラスに投げられるタッチイベントをListenする必要がありますね. 前回使ったofAddListenerを使っても良いんですが,クリックやタッチといったインタラクティブな要素に関しては,簡単にイベントを監視出来る便利関数が用意されています.それがofRegisterTouchEvents.素直にこれを使いましょう.(ofRegisterTouchEventsはofAddListenerをラップしてるだけ)

ofRegisterTouchEventsを利用してタッチイベントを監視すると.独自クラスでも以下の関数がイベントに応じて呼び出されるようになります.

void touchDown(ofTouchEventArgs & touch);
void touchMoved(ofTouchEventArgs & touch);
void touchUp(ofTouchEventArgs & touch);
void touchDoubleTap(ofTouchEventArgs & touch);
void touchCancelled(ofTouchEventArgs & touch);

ofAppクラスでいじるやつと全く同じですね.実際全く同じように使えます. ヘッダでこれらの関数を定義,実装ファイルに記述していきましょう. 内容無しでも,ヘッダ,実装ファイルに書いておかないとofAddLitenerがエラーを起こすので注意してください. なお,touchArgsで使えるのはx,y,idのみっぽいです.詳細は次回(?

# ToggleButton.h 追記

    void touchDown(ofTouchEventArgs & touch);
    void touchMoved(ofTouchEventArgs & touch);
    void touchUp(ofTouchEventArgs & touch);
    void touchDoubleTap(ofTouchEventArgs & touch);
    void touchCancelled(ofTouchEventArgs & touch);
    
    bool is_touch_inside;
    bool is_inside(float x,float y);

# ToggleButton.cpp 追記

void ToggleButton::touchDown(ofTouchEventArgs & touch){
    if (is_inside(touch.x, touch.y)) {
        is_touch_inside = true;
    }
}

void ToggleButton::touchMoved(ofTouchEventArgs & touch){
    
}

void ToggleButton::touchUp(ofTouchEventArgs & touch){
    if (is_touch_inside and is_inside(touch.x, touch.y)) {
        is_on = !is_on;
        is_touch_inside = false;
    }
}

void ToggleButton::touchDoubleTap(ofTouchEventArgs & touch){
    
}

void ToggleButton::touchCancelled(ofTouchEventArgs & touch){
    
}

bool ToggleButton::is_inside(float x,float y){
    if(offset.x < x and x < offset.x + width){
        if (offset.y < y and y < offset.y + height) {
            return true;
        }
    }
    return false;
}

これで「ボタン領域でタッチが開始され,ボタン領域でタッチが終了した時」にボタンがタップされた,と判断することが出来ます. 単純にtouchupにis_insideを噛ませるだけでは,ミスタッチした時にキャンセル出来ない,タッチした瞬間にイベントが発火され,画像変化が指に隠れる,などの問題が生じるので頭においておくと良いでしょう.

イベント通知

ボタンクラスが「自分が押された」ことを認識できるようになったので,その際に「自分押されました!」とイベントを発火し,ofAppに通知してみましょう. いわゆるデリゲートというやつですね. ボタン自身がフラグ管理などしても良いのですが,それだと汎用性が無くなるので,ボタン押された時の処理自体はofAppクラスに任せましょう.

# toggleButton.h 追記

    ofEvent<bool> onClick;

# ToggleButton.cpp 追記

void ToggleButton::touchUp(ofTouchEventArgs & touch){
    if (is_touch_inside and is_inside(touch.x, touch.y)) {
        is_on = !is_on;
        is_touch_inside = false;
        ofNotifyEvent(onClick, is_on); //add
    }
}

ココは前回みたofEventのイベント通知そのままですね.詳細はこちらをどうぞ. 今回はイベント変数として,「今のボタンの状態(on or off)」を通知しています.

では,この情報を用いてofApp側で「画面の色を変える機能の有効・無効化」を実装しましょう.

# ofApp.h 追加
    bool is_locked; //現在のフラグ
    ToggleButton tgl_btn; // 自前のボタンクラス
    void toggleLock(bool &state); // ボタンイベントのコールバック関数

# ofApp.cpp 追加

//--------------------------------------------------------------
void ofApp::setup(){
    ofBackground(0);
    bg_color = ofColor(127);
    
    // add from here
    tgl_btn.setup("lock.png", "unlock.png");
    tgl_btn.width  = 100;
    tgl_btn.height = 100;
    tgl_btn.offset = ofVec2f(ofGetWidth() - tgl_btn.width,0);
    
    ofAddListener(tgl_btn.onClick, this, &ofApp::toggleLock);
    
    is_locked = true;
}

//--------------------------------------------------------------
void ofApp::draw(){
    tgl_btn.draw();
}

//--------------------------------------------------------------
void ofApp::touchMoved(ofTouchEventArgs & touch){
    // 非ロックのみ色変更有効
    if(!is_locked){
        bg_color = ofColor(255 * touch.y / ofGetHeight());
    }
}

//--------------------------------------------------------------
void ofApp::toggleLock(bool &state){
    is_locked = state;
}

変更点はシンプルです.

  • ToggleButtonインスタンス初期化.イベントバインド(setup)
  • ToggleButtonの描画(draw)
  • 機能ロックフラグ追加(touchmoved)
  • イベントコールバック関数作成(toggleLock)

これで冒頭に載せたような機能切替ボタンが実現できます. ofAppでフラグ変数を使ってしまっていますが,イベントコールバックの中から変更する,という形にしておくだけで構造は理解しやすくなるでしょう.

まとめ

ofEventを使った独自ボタンの作り方をまとめました. 自分は始めてofEvent触ったのが前回記事の時なので,ビギナーでもコツさえ掴めば使えるようになるのは難しくはないかな,という印象です. その際には,一番最初にofEventの基本的な使い方をおさえるのが一番です.

イベントを使った処理は慣れるまで敷居が高く感じられます. しかも今回のケースでは「単純にボタンクラスつくってそいつがstate持ってればいーんじゃないの?」というのももっともです. しかしイベント処理を使えるようになるとプログラムの構造を把握しやすくなるのでは,と個人的に思います. mouseClick関数の中でクリックされたリージョンにあわせて様々な処理を記述してしまう...なんてのは誰もが一度は経験しているのでは無いでしょうか. イベントベースにすると,各ボタン自身が「自分が押されたかどうか」を判断してくれるので,mouseClickのコードはスッキリしますよね.

それに全体のフラグ変数を各ボタンが持っているよりは,ofAppで中央集権の方が把握しやすいかなと. まぁフラグ変数が膨大になったりするとそれはそれで見にくいんですが...そのへんはケースバイケースということで, ofEventを習得しておくとプログラム構造を考える際の可能性が広がる,ということは間違いないでしょう.

本日の一言

Xcodeのjump to definitionがライブラリ解読にクソ便利.