Max 5 API Reference

UI オブジェクトの分析

Max のユーザインターフェイスオブジェクトは、通常の非ユーザインターフェイスオブジェクトよりも複雑です。

特に表示すべきものがない場合や、ユーザによるインタラクションや編集のために独自のインターフェイスを作成する必要がない場合には、UI オブジェクトを書く必要はないでしょう。しかし、詳細な処理が必要な場合には、これを利用することができます。

ユーザインターフェイスオブジェクトを作成する場合には、アトリビュートが広範囲に渡って使用されるため、これに精通している必要があります。Max のインスペクタで toggle オブジェクトを調べると、toggle クラスに属するものとして定義されているアトリビュートがいくつかあることがわかるでしょう。すなわち、次のようなものです。

このアトリビュートがどのように定義され、記述されているかを説明していきます。これにより、インスペクタでアトリビュートを正確に編集することができるようになります。

アトリビュートに加え、ユーザインターフェイスオブジェクトは、box の再描画を行ない、マウスクリックやキーボードイベントなどのユーザによるイベントに反応します 。オブジェクトの paint メソッドによる描画や、mousedown、mousedrag、mouseup メソッドでのユーザインタラクションの実装について示します。

この章では、直線や矩形の塗りつぶしといった基本的な描画処理だけを取り上げます。しかし、ユーザインターフェイスオブジェクトの paint メソッドとして使用されることを前提にした jgraphics という 完全なグラフィックス API を利用することができます。JGraphics については別の章で詳しく説明します。また、JGraphics 関数セットのヘッダファイルの記述も役に立つのではないかと思います。

SDK のサンプルには2つのユーザインターフェイスプロジェクトがあります。1つはこの章で取り上げる uisimp というもので、toggle オブジェクトに複雑なチェックボックスとユーザインタラクションを追加したバージョンです。もう1つのプロジェクトは pictmeter~, というもので、イメージファイルやオーディを使用するより高度なオブジェクトです。

uisimp オブジェクトは、toggleオブジェクトといくつかの点で異なっています。

それ以外は、主として toggle と同じように動作します。

まず最初に、uisimp をビルドし、試して見ることをお勧めします。オブジェクトが正しくビルドされた後、オブジェクトボックスに「uisimp」と入力すると、試すことができます。

必要なヘッダ

UI オブジェクトでは、jpatcher_api.h および jgraphics.h という2つのヘッダファイルをインクルードする必要があります。

    #include "jpatcher_api.h"
    #include "jgraphics.h"

jpatcher_api.h というヘッダファイルは、UI オブジェクトで必要なデータ構造とアクセサ関数をインクルードします。 jgraphics.h というヘッダファイルは、データ構造と描画関数をインクルードします。

UI オブジェクトのデータ構造体

UI オブジェクトの最初のフィールドは t_object ではなく、 t_jbox です。一般的に t_jboxフィールドへ直接アクセスすることは避けるべきです。特に値の変更や、 jpatcher_api.h. で定義されているアクセサ関数を使用するときには、直接アクセスしないようにして下さい。例えば、アクセサ関数 jbox_set_rect() を使用せずにボックスの矩形領域を変更した場合、パッチに適切に通知されず、スクリーンの更新が行なわれません。

t_jbox の後には、オブジェクトの内部状態(ステート)を保存するフィールドを追加することができます。特に、色を使って何らかの描画を行なう場合、オブジェクト 内で色情報を保持しているフィールドを参照するアトリビュートを作る必要があるでしょう。この方法については後述します。次に示すのは、t_uisimp データ構造体の宣言です。

    typedef struct _uisimp 
    {
        t_jbox u_box;                       // UI オブジェクトのヘッダ
        void *u_out;                        // アウトレットへのポインタ
        long u_state;                       // ステート(状態:1 または 0)
        char u_mouseover;                   // マウスがオブジェクト上にあるかどうか
        char u_mousedowninside;             // マウスボタンがオブジェクト上で押されたかどうか
        char u_trackmouse;                  // これが0でない場合、マウスボタンが押し下げられていなければマウスの動きをトラッキングする。
        t_jrgba u_outline;                  // アウトラインの色
        t_jrgba u_check;                    // チェックを表す矩形の色
        t_jrgba u_background;               // 背景の色
        t_jrgba u_hilite;                   // ハイライト色 (マウスがオブジェクト上にあって、マウスボタンがクリックされた場合)
    } t_uisimp;

t_jrgba 構造体は、赤(red )、緑(green)、青(blue)、アルファ(alpha)を表す 4つの double 型の値で色を定義します。各コンポーネントの値の範囲は 0. - 1.です。red、green、blue の値がすべて0. のとき、色は黒になります。red、green、blue の値がすべて 1. のとき、色は白になります 。 t_jrgba 構造体を使って color アトリビュートを定義することにより、ユーザは、インスペクタから標準のカラーピッカーを使用して、オブジェクトの色を設定することができるようになります。

構造体メンバ u_mouseover と u_mousedowninside は マウスインタラクションを扱うコードから、toggle の描画を行なう コードに合図を送るために使用されます。これについては、後ほど「インタラクション・ストラテジ(方針)」セクションでより詳細に検討します。

UI オブジェクトの初期化ルーチン

オブジェクト構造体の宣言を行なった後、クラスのセットアップを行なう初期化ルーチン(main 関数)を書き、メソッド、およびUI オブジェクトが使用するアトリビュートを宣言します。

ノーマル Max オブジェクトのクラスの初期化に対して、まず追加しなければならないのは、jbox_initclass() の呼び出しです。これは、すべての UI オブジェクトに共通する、標準のメソッドとアトリビュートを追加します。どのようにこれを行なうべきかについて、次に示します。

    c = class_new("uisimp", (method)uisimp_new, (method)uisimp_free, sizeof(t_uisimp), 0L, A_GIMME, 0);

    c->c_flags |= CLASS_FLAG_NEWDICTIONARY;
    jbox_initclass(c, JBOX_FIXWIDTH | JBOX_COLOR);

c->c_flags |= CLASS_FLAG_NEWDICTIONARY という行は必要です、しかし、jbox_initclass に渡されるフラグ、 -- JBOX_FIXWIDTHJBOX_COLOR -- はオプションです。 JBOX_FIXWIDTH は、パッチ内でオブジェクトが選択されたときに、オブジェクトのサイズを、クラスのデフォルトの大きさに変更する Fix Width メニュー項目が使用可能(イネーブル)になることを意味します。この後で、すぐにデフォルトの範囲を指定します。JBOX_COLORは、オブジェクトに color アトリビュートが与えられることを意味します。これにより、Color... メニュー項目によって表示される色を選択して編集を行なえるようになります。これは、インスペクタを開かずにオブジェクトの「基本(basic)」の色を 編集する方法です。こういった動作がどれもあなたのオブジェクトにあてはまらないのであれば、 jbox_initclass() に対する引数のフラグに 0 を渡しても構いません。

UI オブジェクトのメソッド

次に、いくつかの標準メソッドをバインドする必要があります。UI オブジェクトに不可欠な唯一のメソッドは paint メソッドです。これは、box が可視の状態で再描画が必要なときに、オブジェクトの内容を描画します。

    class_addmethod(c, (method)uisimp_paint,        "paint",    A_CANT, 0);

paint メソッドの詳細については後述します。 このメソッドは JGraphics API を利用しますが、このAPI に関しては独自に章を立てて詳細に説明します。

私たちの uisimp toggle はマウスの動作に反応します。そのため、マウスを取り扱う一組のメソッドを定義します。

    class_addmethod(c, (method)uisimp_mousedown,    "mousedown",    A_CANT, 0);
    class_addmethod(c, (method)uisimp_mousedrag,    "mousedrag",    A_CANT, 0);
    class_addmethod(c, (method)uisimp_mouseup,      "mouseup",      A_CANT, 0);
    class_addmethod(c, (method)uisimp_mouseenter,   "mouseenter",   A_CANT, 0);
    class_addmethod(c, (method)uisimp_mouseleave,   "mouseleave",   A_CANT, 0);
    class_addmethod(c, (method)uisimp_mousemove,    "mousemove",    A_CANT, 0);
    class_addmethod(c, (method)uisimp_mousewheel,   "mousewheel",   A_CANT, 0);

 

ユーザがオブジェクトをクリックしたときに、言い換えれば、マウスがオブジェクトの上に移動して第1マウスボタンが押されたときに、オブジェクトに対して mousedown が送信されます。mousedrag は、最初にマウスボタンが押し下げられた後、マウスが移動し、その間マウスボタンがクリック後から押し下げられたままになっているとき、 mousedrag が送信されます。mousedown が送信された後、マウスボタンが放されたとき、mouseup が送信されます。 マウスボタンが押し下げられない状態でマウスがオブジェクトボックスの範囲内に移動したとき、mouseenter が送信されます。 mouseenter が送信された後、マウスボタンが押し下げられずに、オブジェクトボックスの範囲内でマウス位置が変更されたときに、 mousemove が送信されます。マウスボタンが押し下げられない状態で、マウス位置がオブジェクトボックスの範囲内からオブジェクトボックスの外へ移動したとき、 mouseleave が送信されます。マウスカーソルがオブジェクト上にある間、マウスのスクロールホイール情報(あるいは、トラックパッドのような他のデバイスによるスクロール情報)がmousewheel として送信されます。

これらすべてのメッセージに対して応答する義務はありません。例えば、mousedown にだけ応答し、他のメッセージを無視するということもできます。

mouse メッセージを以下のような「ルール」にまとめることは、役に立つのではないかと思います(しかし、通常、これらについて明確に考えておく必要はありません)。

以下では、マウスを取り扱うメソッドの実際の実装について見ていきます。

アトリビュートの定義

標準的なメソッドの宣言を行なった後、オブジェクト独自のアトリビュートを定義します。私たちが「アトリビュートのアトリビュート(attribute attributes)」と呼んでいるものを使用して、アトリビュートについてさらに詳しく記述することができます。これにより、インスペクタでアトリ ビュートを適切に表示、編集することができるようになるだけでなく、パッチに保存する(あるいは保存しない)ことも可能になります。また、アトリビュート に初期値を設定し、オブジェクトがインスタンス化される際に、自動的にオブジェクトにコピーされるようにしたり、アトリビュートにマークをつけておき、こ の値が変更されたときにオブジェクトを再描画することもできます。

便宜をはかるために、すでに ext_obex_util.h には一連のマクロが定義されています(これは、オブジェクトが ext_obex.h) をインクルードした際にインクルードされます)。これにより、アトリビュートや「アトリビュートのアトリビュート」を定義する場合に必要となるタイピングの分量を減らすことができます。

ほとんどの UI オブジェクトのアトリビュートはオフセットアトリビュートです。オフセットアトリビュートとは、オブジェクトのデータ構造体の中の場所をオフセットとサイ ズによって参照するものです。例えば、uisimp では trackmouse という char型のオフセットアトリビュートがあります。これは、マウスがオブジェクトの領域内に移動しときにオブジェクトのアピアランス(外観)を変更するか どうかを指定するものです。これは、次のように定義されています。

    CLASS_ATTR_CHAR(c, "trackmouse", 0, t_uisimp, u_trackmouse);
    CLASS_ATTR_STYLE_LABEL(c, "trackmouse", 0, "onoff", "Track Mouse");
    CLASS_ATTR_SAVE(c, "trackmouse", 0);

 

1行目の CLASS_ATTR_CHAR は、charのサイズのオフセットアトリビュートを定義します。t_uisimp の宣言を見ると、u_trackmouse フィールドが char 型で宣言されていることがわかります。 CLASS_ATTR_CHAR マクロは5個の引数を取ります。 

第4と第5の引数は、構造体の先頭からフィールドまでのオフセットを計算するために使用されます。これにより、フィールドが占有しているメモリを直接に読み書きできるようになります。

2行目の CLASS_ATTR_STYLE_LABEL は、trackmouse アトリビュートのために、いくつかの「アトリビュートのアトリビュート」を定義しています。このマクロも同様に5個の引数を取ります。

「アトリビュートのアトリビュート」category(カテゴリ)は、インスペクタウィンドウの中でオブジェクトのアトリビュートを系統立てるために用い られます。trackmouse アトリビュートに対しては、"Behavior" カテゴリを使用します。また、後述しますが、color アトリビュートに対しては "Color" を使用します。Max の既存のUI オブジェクト何種類かのインスペクタを表示させ、カテゴリタブを見ると、標準的なカテゴリ名がわかると思います。独自のカテゴリを作成することもできま す。

単独のアトリビュートに対してカテゴリを定義する場合、CLASS_ATTR_CATEGORY マクロを使用します。

    CLASS_ATTR_CATEGORY(c, "trackmouse", 0, "Behavior");

 

一連のアトリビュートに対してカテゴリを定義する場合には、 CLASS_STICKY_ATTR マクロを使用します。これは、指定された「アトリビュートのアトリビュート」が現在持っている値を、それに続く任意のアトリビュートに適用するものです。この適用は、「アトリビュートのアトリビュート」の名前に対して、CLASS_STICKY_ATTR_CLEAR が設定されるまで続きます。uisimp では、3つの color アトリビュートに "Color" カテゴリを適用するために、 CLASS_STICKY_ATTR を使っています。

    CLASS_STICKY_ATTR(c, "category", 0, "Color");

 

color アトリビュートは、 CLASS_ATTR_RGBA を使って定義されます。 uisimp オブジェクトでは、4つの color アトリビュートを定義しています。これは最初に、bgcolor によって呼び出されます。

    CLASS_ATTR_RGBA(c, "bgcolor", 0, t_uisimp, u_background); 
    CLASS_ATTR_DEFAULTNAME_SAVE_PAINT(c, "bgcolor", 0, "1. 1. 1. 1."); 
    CLASS_ATTR_STYLE_LABEL(c,"bgcolor",0,"rgba","Background Color");

 

アトリビュートを定義する CLASS_ATTR_RGBACLASS_ATTR_CHAR の違いは、CLASS_ATTR_RGBA が前提とする構造体メンバ名は char ではなく t_jrgba であるという点です。値を設定する場合、アトリビュートは色(color)のコンポーネントを構成する4つの double に割り当てられます。

次の行では CLASS_ATTR_DEFAULTNAME_SAVE_PAINT というマクロを使っています。これは、bgcolor アトリビュートに対して3つの設定を行ないます。その第1は、この bgcolor という color アトリビュートにオブジェクトのデフォルトウィンドウを介して初期値を与えることができるという設定です。そのため、オブジェクトが標準で設定する白を好 まないのであれば、新規に生成される uisimp オブジェクトの背景色に独自の色を割り当てることができます。 CLASS_ATTR_DEFAULTNAME_SAVE_PAINT の最後に引数として割り当てられる値 1 1 1 1 は 「標準の」デフォルト値を与えるものです。これは、ユーザによる上書きが行なわれない場合に、bgcolor アトリビュートの値として使用されます。

このマクロの名前にある SAVE は、このアトリビュート値をオブジェクトと共にパッチ内に保存するよう指定するものです。パッチファイルは、オブジェクトのクラス、配置、接続を保存しま す。しかし、"save" という「アトリビュートのアトリビュート」を用いると、オブジェクトのアピアランスや指定した任意のアトリビュートの値を保存することも可能です。

このマクロの名前にある PAINT は、このアトリビュート(bgcolor)が変更されたときにオブジェクトの再描画を行なわせるという機能を提供するものです。しかし、アトリビュートが 変更された場合の自動的な再描画を実装するためには、クラスの初期化を行なう際に、次のコードを追加する必要があります。

    class_addmethod(c, (method)jbox_notify, "notify", A_CANT, 0);

 

jbox_notify() という関数は、変更のノーディフィケイション(通知)が送信されたアトリビュートが paint という「アトリビュートのアトリビュート」のセットを持っているかどうかを判定します。そして、持っている場合には jbox_redraw()を呼び出します。アトリビュートの変更や他の環境の変化に応答する必要があるために独自の notify メソッドを書くという場合には、その中で jbox_notify() を呼び出すことが可能です。

標準の Color アトリビュート

初期化ルーチンの最初の部分では、jbox_initclass() に対し、フラグとして JBOX_COLOR を渡します。これにより、オブジェクトに color というアトリビュートを追加します。このアトリビュートは t_jbox によって与えられた記憶領域を使用して色の値を保持します。color アトリビュートはオブジェクトが使用する「最も基本的な」色のための標準的な名前です。そして、これを定義すると、オブジェクトを選択したときに Object メニューの Color というメニュー項目が使用できるようになり、インスペクタを開かなくてもユーザがオブジェクトの色を変更できるようになります。

JBOX_COLOR を使用した場合、CLASS_ATTR_RGBA を使って color アトリビュートを定義する必要はありません。jbox_initclass() がこれを行なってくれます。しかし、この color アトリビュートに関しては何も設定されていない状態になっているため、「アトリビュートのアトリビュート」を使ってこれを自由に強化することができます。 uisimp では次のようになっています。

    CLASS_ATTR_DEFAULTNAME_SAVE_PAINT(c, "color", 0, "0. 0. 0. 1."); 
    CLASS_ATTR_STYLE_LABEL(c,"color",0,"rgba","Check Color");

 

デフォルトサイズの設定

jbox_initclass() によってオブジェクトに対して定義されるもう1つのアトリビュートは、patching_rect と呼ばれるものです。これは、オブジェクトの box の大きさを保持します。オブジェクトのインスタンス生成の際の標準サイズを設定したい場合には、patching_rect にデフォルト値を設定することができます。最初の2つの値(x 位置と y 位置)に 0 0 を設定し、次の2つの値を使って幅と高さを定義します。uisimp のデフォルトサイズには小さな矩形領域を指定したいと思います。そのため、次のように CLASS_ATTR_DEFAULT を使って patching_rect アトリビュートにデフォルト値を割り当てます。

    CLASS_ATTR_DEFAULT(c,"patching_rect",0, "0. 0. 20. 20.");

 

インスタンス生成ルーチン(new instance ルーチン)

UI オブジェクトのインスタンス生成ルーチン(new instance ルーチン)は、Max のノーマルオブジェクトより複雑です。それぞれの UI オブジェクトには、t_dictionary (シンボル名によってアクセスされる、階層化構造のデータコレクション)が渡されます。これには、インスタンス化を行なうために必要な情報が含まれていま す。UI オブジェクトでは、dictionary の中のデータ要素はアトリビュート値に対応しています。例えば、オブジェクトが "bgcolor" というアトリビュートを保存した場合、インスタンス生成ルーチンの中で保存された値にアクセスすることができます。アクセスは、同じ名前 "bgcolor" を使って dictionary から行います。

インスタンスがオブジェクトパレットから作られる場合、あるいは、オブジェクトボックスにオブジェクト名を入力して作られる場合には、 dictionary にはデフォルト値が設定されます。オブジェクトがパッチファイルの読み込みによって作られる場合、dictionary にはファイルに格納されている保存済みのアトリビュート値が設定されます。オブジェクトの dictionary に専用の非アトリビュート情報を追加し、それを参照したり、抽出したりするのでない限り、ほとんどの場合、dictionary を直接使って処理を行なう必要はありません。しかし、いくつかの標準ルーチンに対しては dictionary を渡す必要があり、すべてを正しい順序で初期化する必要があります。

オブッジェクとのインスタンス生成ルーチン(new instance ルーチン)を書くために、従わなければならないパターンを見てみましょう。

まず、インスタンス生成ルーチン(new instance ルーチン)は次のように宣言されます。

    void *uisimp_new(t_symbol *s, long argc, t_atom *argv);

 

オブジェクトを定義する dictionary を引数 argc, argv で渡される引数から取得します(s というシンボル型の引数はオブジェクトの名前です)。dictionary の取得に失敗した場合には、インスタンスが生成されなかったことを示すために NULL を返さなければなりません。

    void *uisimp_new(t_symbol *s, long argc, t_atom *argv);
    {
        t_uisimp *x = NULL;
        t_dictionary *d = NULL;
        long boxflags;

        if (!(d = object_dictionaryarg(argc,argv)))
            return NULL;

 

次に、オブジェクトクラスの新しいインスタンスにメモリを割り当てます。

    x = (t_uisimp *)object_alloc(s_uisimp_class);

その後、box のオプションを初期化する必要があります。このオブジェクトが使用するオプションはコメントアウトされていません。

    boxflags = 0 
            | JBOX_DRAWFIRSTIN 
            | JBOX_NODRAWBOX
            | JBOX_DRAWINLAST
            | JBOX_TRANSPARENT  
    //      | JBOX_NOGROW
            | JBOX_GROWY
    //      | JBOX_GROWBOTH
    //      | JBOX_HILITE
    //      | JBOX_BACKGROUND
            | JBOX_DRAWBACKGROUND
    //      | JBOX_NOFLOATINSPECTOR
    //      | JBOX_MOUSEDRAGDELTA
    //      | JBOX_TEXTFIELD
            ;

個々の box フラグについて少し詳しく説明します。

このフラグは、新しく作られたインスタンスへのポインタや引数 argc、argv と共に、jbox_new() に渡されます。この関数名はやや紛らわしいのですが、 jbox_new() はこの box の初期化を行なうわけではありません。すでに説明したように、UI オブジェクトは最初に t_jbox を持ちます。 jbox_new()は単に t_jbox を初期化してくれるだけです。 jbox_new() はオブジェクトのデータ構造体の中の t_jbox に続く他のメンバについては何も知りません。その他の項目については、あなた自身で初期化しなければなりません。

    jbox_new((t_jbox *)x, boxflags, argc, argv);

Once jbox_new() の呼び出しが終わった後、 t_jbox ヘッダの b_firstin ポインタを、あなたのオブジェクトを指すように割り当てます。基本的に、これは、左端のインレット(インレットやプロキシを介する他のインレットも同様です)に接続されたオブジェクトからメッセージを受信するオブジェクトに対して割り当てられるものです。このステップは忘れやすいものですが、これを怠るとオブジェクトは動作しません。 jbox_new() は、patching_rect のようなすべての box で共通はアトリビュートを取得し、これをあなたのオブジェクトに割り当てます。

    x->u_box.b_firstin = (void *)x;

次に、オブジェクトのデータ構造体のメンバを自由に初期化し、インレットを宣言します。これらの処理は、UI オブジェクトでも、非 UI オブジェクトでも同じです。

    x->u_mousedowninside = x->u_mouseover = x->u_state = 0;
    x->u_out = intout((t_object *)x);

オブジェクトの初期化が無事に終了して安全な初期化状態になった後、あなた自身が何らかのアトリビュートを定義している場合には attr_dictionary_process() を呼び出します。この関数は、オブジェクトが受け取った dictionary からアトリビュートを見つけ出し、dictionary に保存されている値をアトリビュートに設定します。アトリビュートが設定される順序に関しては保証されません。これで問題がある場合には、あなた自身がアトリビュートの値を「手作業で」取得し、これをオブジェクトに割り当てることも可能です。

あなた自身で全くアトリビュートを定義していない場合には attr_dictionary_process() を呼び出す必要がないという点に注意して下さい。 jbox_new() は、すべての UI オブジェクトで共通したアトリビュートの設定はすべて行なってくれます。

        attr_dictionary_process(x,d);

A新しく作成された UI オブジェクトを返す前に行なう最後の処理として、より詳しくは、オブジェクトのアピアランス(外観)を確定する全てのものの初期化が完了した後に行なう処理として jbox_ready() の呼び出しがあります。jbox_ready() はオブジェクトを描画し、インレットとアウトレットの位置を計算し、他の初期化タスクを実行して、あなたの box が可視のパッチの正しいメンバであることを保証します。

インスタンス化を行なってもオブジェクトが表示されない場合 jbox_ready() の呼び出しを行なったかどうかをチェックすべきです。

        jbox_ready((t_jbox *)x);

最後に、他のインスタンス生成ルーチンの場合と同様、新しく作られたオブジェクトを返します。

    return x;

動的なアップデート

スクリーンへの描画は、paint メソッドでのみ行なわなければなりません(以前の Max の UI オブジェクトはそうではありませんでした)。何かを再描画したい場合、 jbox_redraw() を呼び出して、スクリーンへの再描画を行なわなければなりません。これは、あなたのオブジェクトがユーザインターフェイスの一部となるため必要なことです。スクリーン上での不自然な描画を避けるため、パッチはユーザインターフェイスを一体のものとして管理しなければなりません。 jbox_redraw() ルーチンは再描画を行なう必要があるスクリーン領域を計算し、この領域が無効なものであることを、Mac や Windows の「ウィンドウマネージャ」に知らせます。その後、OS は適切なタイミングで パッチの paint ルーチンを呼び出します。paint ルーチンは、無効な領域の中にあるすべての box に対して、現在の すべての box のZ オーダに基づいて渡されます。バックグラウンドにある box が最初に描画されます。これで、透明な、あるいは半透明な box をその上に描画することができます。加えて、特に指定しない限り、box の最後に描画されたイメージはバッファにキャッシュされています。そのためルーチンは再描画を行なう必要があるスクリーン領域を計算し、この領域が無効なものであることを、Mac や Windows の「ウィンドウマネージャ」に知らせます。その後、OS は適切なタイミングで パッチの paint ルーチンを呼び出します。paint ルーチンは、無効な領域の中にあるすべての box に対して、現在の すべての box のZ オーダに基づいて渡されます。バックグラウンドにある box が最初に描画されます。これで、透明な、あるいは半透明な box をその上に描画することができます。加えて、特に指定しない限り、box の最後に描画されたイメージはバッファにキャッシュされています。そのため jbox_redraw() によってあなたのオブジェクトの内容が無効であることが明示された場合にのみ、あなたのオブジェクトの paint メソッドが呼び出されます。言い換えると、あなたのオブジェクトの paint メソッドを呼び出す場合に「グローバルなパッチの描画(global patcher drawing)」を期待することはできないということです。

再描画を行なおうとする場合に使用すべき基本的な方策は、他のメソッドの内部状態(ステート)を設定した後、 jbox_redraw()を呼び出すということにです。paint メソッドは内部状態を読み取り、描画が適切なものになるよう調整します。uisimp オブジェクトの中で使われているこの方策は、オブジェクトがマウスのトラッキングを行なうようすを見ると理解できるでしょう。

Paint メソッド

オブジェクトの paint メソッドは描画を行なう場合 jgraphics API を使用します。ヘッダファイル jgraphics.h にはAPI の各ルーチンについての記述があります。ここでは、一般的な法則や uisimp で用いているような簡単な paint メソッドについてのみ説明します。また、jgraphics を用いた UI オブジェクトの例もあり、これには、様々な描画タスクがどのように実行されるかを示すために数多くの関数が含まれています。

Max での描画は解像度とは無関係です。オブジェクトの矩形領域の「サイズ」は、ズームレベルに関わらず、パッチが100% でスケールされている場合には常にjピクセルサイズになります。そして、実際のスクリーンの拡大や縮小は、マトリックス変換によって自動的に処理されます。自動的に取り扱われる処理のもう1つは、重なり合った画像の描画です。パッチが不可視(すなわち、サブパッチであればダブルクリックされていない状態)であれば、ビューを持ちません。しかし、パッチが可視であば、多くのパッチビュー(patchview)を持つことができます。開発した UI オブジェクトボックスがパッチ内で複数のビューを開くものである場合、paint メソッドはそれぞれのビューごとに1回呼び出されます。そして、そのたびに様々な patchview オブジェクトが渡されます。ほとんどのオブジェクトでは、これによって問題が生じることはありません。しかし、0 ~ 10 個のビューが開かれているときにオブジェクトを正しく動作させる場合、 paint メソッドで内部状態(ステート)を変更することはできず、読み出しのみ可能です。例えば、オブジェクトが構造体の中に "painted" というブーリアンのフィールドを持っていて、paint メソッドが完了したときにこれがセットされるような場合、box が不可視の場合にはうまく動作しません。あるいは、 複数のパッチのビューのなかで表示されている場合には、これが 0 にセットされるか、何回もセットされてしまい、同様にうまく動作しません。

paint メソッドの最初のステップは、paint メソッドに渡された patchview オブジェクトから t_jgraphics を取得することです。 patchview は t_object 型で、その内容を見ることはできません。これは box の矩形領域や、グラフィックスコンテキストに関する情報にアクセスするために使用します。patchview はパッチと同じものではありません。上で述べたように、パッチがマルチビューを開いている場合、1つのパッチに対して複数のパッチビューが存在することができます。

    void uisimp_paint(t_uisimp *x, t_object *patcherview)
    {
        t_rect rect;

        t_jgraphics *g = (t_jgraphics*) patcherview_get_jgraphics(patcherview);     // グラフィックコンテキストの取得

t_jgraphics オブジェクトを取得した後、box の矩形領域を決定するということを次に行なわなければなりません。パッチのビューは、パッチングモード(編集モード)かプレゼンテーションモードのどちらかになっているはずです。それぞれのモードは独自の矩形領域を持っているため、パッチビュー(patchview) を使用するためには、オブジェクトの矩形領域を取得する必要があります。

    jbox_get_rect_for_view((t_object *)x, patcherview, &rect);

The t_rect 構造体は、矩形領域の左上隅の x, y 座標と、幅、高さの値を使って矩形領域を指定します。しかし、通常描画に使用する t_jgraphics の座標は、常に左上隅が 0 から始まっています。そのため、描画を行なう際に最低限指定しなければならない値は幅と高さです。

最初に描画するものは、box のアウトラインだけです。これはアウトラインの color アトリビュートの値を用います。まず、使用する色を設定します。その後、矩形領域のパスを作り、作成したペンを使って線を引きます。

With calls such as jgraphics_rectangle() のような関数を呼び出すことによって、既存のパスに矩形が追加されます。最初のパスは空になっていて、jgraphics_stroke() や jgraphics_fill() の呼び出しによってパスは再びクリアされます(パスを保持したい場合には、jgraphics_stroke_preserve() や jgraphics_fill_preserve variants() を使用します)。

    jgraphics_set_source_jrgba(g, &x->u_outline);
    jgraphics_set_line_width(g, 1.);
    jgraphics_rectangle(g, 0., 0., rect.width, rect.height);
    jgraphics_stroke(g);

paint メソッドが終了する前にパスを破棄する必要はありません。この処理は自動的に行なわれます。しかし、paint メソッドが完了した後にパスが残っていないということは、最初にコピーしておかないと、パスの作成や保存ができないということを意味します。このような方策はあらゆるケースで推奨されるものではありません。それは、1回の paint メソッドの呼び出しから、次の呼び出しまでの間に、オブジェクトの矩形領域が予測不可能な変更を受ける可能性があるためです。この場合、保存してあるパスは間違った形やサイズを表すものになってしまいます。

paint メソッドの次の機能は、mouse が box 内部に移動した場合に内側のアウトラインを描画するというものです。マウスが box 上に存在するかどうかの検知は、後述する mouseenter / mouseleave メソッドの中で行われますが、基本的には、このようなマウストラッキングメソッドによって u_mouseover がセットされているかどうかを見ることによって、マウスがオブジェクトの領域にあるかどうかを知ることができます。

box の矩形領域より 1 ピクセル内側の矩形を描くためには、1, 1 から始まる、box の幅 -2 、box の高さ - 2 の矩形を使用します。

    // paint "inner highlight" to indicate mouseover
    if (x->u_mouseover && !x->u_mousedowninside) {
        jgraphics_set_source_jrgba(g, &x->u_hilite);
        jgraphics_set_line_width(g, 1.);
        jgraphics_rectangle(g, 1., 1., rect.width - 2, rect.height - 2);
        jgraphics_stroke(g);
    }

いくつかの同様なコードでは、ユーザが toggle をチェックした(オンに切り替えた)ときにハイライト色を表示する機能を提供しています。

    if (x->u_mousedowninside && !x->u_state) {      // paint hilite color
        jgraphics_set_source_jrgba(g, &x->u_hilite);
        jgraphics_rectangle(g, 1., 1., rect.width - 2, rect.height - 2);
        jgraphics_fill(g);
    }

最後に、toggle のステートがゼロでない場合、ボックスがチェックされていることを表示するために、オブジェクトの中央に四角形を描画します。ここでは、パスを直線で描画するのではなく、パスの領域内を塗りつぶしています。 jbox_get_color() の呼び出しを使って、オブジェクトの 「標準の」 色を取得した場合、この値が t_jbox. の内部に保存されているということも覚えておいて下さい。私たちは、初期化ルーチンの jbox_initclass() で、 JBOX_COLOR フラグを使った指定を行なっているため、 jbox_get_color() で取得される "check" 状態での色(実際には単色の四角形です)は、ユーザが Object メニューの Color... 項目で変更することができます。

    if (x->u_state) {
        t_jrgba col;

        jbox_get_color((t_object *)x, &col);
        jgraphics_set_source_jrgba(g, &col);
        if (x->u_mousedowninside)       // make rect bigger if mouse is down and we are unchecking
            jgraphics_rectangle(g, 3., 3., rect.width - 6, rect.height - 6);
        else
            jgraphics_rectangle(g, 4., 4., rect.width - 8, rect.height - 8);
        jgraphics_fill(g);
    }

jgraphics.h というヘッダファイルを眺めてみただけでも、明らかにここで説明した以上に数多くの描画処理があることがわかるでしょう。しかし、uisimp の paint メソッドを取り上げた主な目的は、マウスによる「動的」なグラフィックスの実装方法を示すことです。それでは、マウストラッキングの処理を見てみましょう。

マウス動作の取り扱い

マウスに対してクリック、ドラッグ、マウスボタンを放す、box の領域で移動させるといった動きを加えると、オブジェクトはメッセージを受信します。uisimp の例では、ほとんどのマウスの動きに対応してメッセージが得られるようメソッドが定義され、このメソッドによってオブジェクト内部のステートが変更されるような実装が行なわれているため、その都度、jbox_redraw()が呼び出され、新しいステートを反映して再描画が実行されます。この方策により、典型的なグラフィカルインターフェイス(この例ではトグルスイッチとして利用されるチェックボックス)と結合されたオブジェクトの「動的」なアビアランス(外観)が作り出されています。

あらゆるマウスの動作に対するメソッドは同じ方法で宣言されます。

    void myobect_mouse(t_myobject *x, t_object *patcherview, t_pt pt, long modifiers);

最初に、最も一般的に実装されるマウスの動作を扱うメソッドを見てみましょう。mousedown メソッドはオブジェクトに対する最初のクリックに応答します。見ておわかりのように、非常にシンプルです。これは、単に u_mousedowninside を true に設定した後、jbox_redraw() を呼び出して box を再描画させます。このトグルは、マウスボタンが放されるまで実際のステートの変更を行なわないように定義されています(この点が、標準の Max オブジェクト toggle と異なっています)。しかし、最初にマウスボタンが押されたとき、何かが起こったことをユーザに知らせるために、ユーザに対する何らかのフィードバックを行ないたいと思います。もう一度 paint メソッドを見てみましょう。u_mousedowninside がオブジェクトの描画状態を変更するために使われていることがわかるでしょう。これにより、オブジェクトは「ステートの変更を待っている」というアピアランス(外観)を与えられます。この状態は、box の中でマウスボタンが放されたときに確定します。

    void uisimp_mousedown(t_uisimp *x, t_object *patcherview, t_pt pt, long modifiers)
    {
        x->u_mousedowninside = true;    // wouldn't get a click unless it was inside the box
        jbox_redraw((t_jbox *)x);
    }

マウスボタンが放された時ときのマウス位置が box の中にあるかどうかを確認することにより、ユーザが、トグルによるオブジェクトの内部状態(ステート)の変更をキャンセルする機会を与えることができます。マウスボタンを放す前にマウスカーソルをオブジェクトの外部に移動させると、処理はキャンセルされます。私たちは、ユーザに対して何かが起こっているということを知らせるためのフィードバックを提供するためにmousedrag メソッドを実装し、このメソッドによってこのテストを行ないます。さらに、「マウスがオブジェクトの内部にある - "mouse inside"」という状態がその前の状態から変化している場合、オブジェクトの再描画を行ないます。最初のマウスクリックからずっとマウスボタンが押されたままで、マウスカーソルが移動している間、オブジェクトに対して mousedrag メッセージが送信されます。たとえカーソルがオブジェクトの box の境界の外に出た場合でもこのメッセージは送信されます。

paint メソッドの場合ど同様に、ptachview を使って、現在の box が持つ矩形領域を取得している点に注意して下さい。これによって、与えられた点を( jgraphics_ptinrect() を使って)テストし、これが box の中にあるか、外にあるかを確かめることができます。

    void uisimp_mousedrag(t_uisimp *x, t_object *patcherview, t_pt pt, long modifiers)
    {
        t_rect rect;

        jbox_get_rect_for_view((t_object *)x, patcherview, &rect);

        // test to see if mouse is still inside the object, redraw if changed

        if (jgraphics_ptinrect(pt, rect)) {
            if (!x->u_mousedowninside) {
                x->u_mousedowninside = true;
                jbox_redraw((t_jbox *)x);
            }
        } else {
            if (x->u_mousedowninside) {
                x->u_mousedowninside = false;
                jbox_redraw((t_jbox *)x);
            }
        }
    }

mouseup メソッドは u_mousedowninside の最後の値を、オブジェクトの内部状態(ステート)が切り替えられたかどうかの判断材料として使います。u_mousedowninside が false の場合、ステートの変更は起こりません。しかし、これが true の場合にはステートが変更され、新しいステートの値がオブジェクトのアウトレット(内部的な uisimp_bang() 関数)から送信されます。

    if (x->u_mousedowninside) {
        x->u_state = !x->u_state;
        uisimp_bang(x);
        x->u_mousedowninside = false;
        jbox_redraw((t_jbox *)x);
    }

最後に、 「マウスオーバー」 スタイルのハイライト表示を別のレベルでオブジェクトに提供するため、mouseenter、mousemove、mouseleave メソッドを実装しました。mouseenter メッセージを受信したとき、u_mousedowninside を変更するのではなく、u_mouseover フィールドをセットします。 そして、mouseleave メソッドを受信したら、これをクリアします。さらに、この変数の操作が行なわれた後 jbox_redraw() でbox の再描画を行います。

    void uisimp_mouseenter(t_uisimp *x, t_object *patcherview, t_pt pt, long modifiers)
    {
        x->u_mouseover = true;
        jbox_redraw((t_jbox *)x);
    }

    void uisimp_mouseleave(t_uisimp *x, t_object *patcherview, t_pt pt, long modifiers)
    {
        x->u_mouseover = false;
        jbox_redraw((t_jbox *)x);
    }

UI オブジェクトの消滅(開放)関数

あなたのオブジェクトが clock を作っている場合、あるいは何らかのメモリ割り当てを行なっていて、オブジェクトが消滅する際にこれを開放する必要がある場合、free ルーチンを使ってこれを処理しなければなりません。しかし、最も重要なことは jbox_free(). If your UI object doesn't need to do anything special in its free routine, you can pass jbox_free() という関数を呼び出さなければならないという点です。あなたの UI オブジェクトが free ルーチンで特別に行なうべきことがない場合、初期化ルーチンの中の class_new() へ free ルーチンを渡すための引数として、 jbox_free() を渡すことができます。 しかし、ここでは、この方法を用いませんでした。それは、実際の関数を作っておくことにより、将来オブジェクトを改良する中で、開放すべきメモリを必要とするような修正を、簡単に行なうことができるようにするためです。

    void uisimp_free(t_uisimp *x)
    {
        jbox_free((t_jbox *)x);
    }

Copyright © 2008, Cycling '74