RGBバーで色を選択

RGB バーで色を選択
これまでは描画する色が固定だったので、今回は、RGB バーを付けて描画色を選択できるようにします。

描画エリア内を左ドラッグすると、選択された色で自由線が描画されます。
スクリーンショット
ソースコード
007_rgbbar.c
#include "sptk.h"

#define DRAWAREA_X    130
#define DRAWAREA_Y    10
#define DRAWAREA_W    250
#define DRAWAREA_H    250

#define COLBOX_X    10
#define COLBOX_Y    80
#define COLBOX_W    50
#define COLBOX_H    50

#define WIDTH  (DRAWAREA_X+DRAWAREA_W+10)
#define HEIGHT (DRAWAREA_Y+DRAWAREA_H+10)

SPTK_IMAGE *image;
int lastx,lasty;
int col_rgb[3] = {0,0,0};
uint32_t drawcol = 0;

/** 描画エリアのハンドラ */

void handle_area(SPTK_EVENT *ev)
{
    SPTK_RECT rc;

    switch(ev->type)
    {
        case SPTK_EVENT_BTTDOWN:
            if(ev->mouse.btt == SPTK_MOUSEBTT_LEFT)
            {
                lastx = ev->mouse.x;
                lasty = ev->mouse.y;
                            
                sptk_grab(ev->widget);
                sptk_image_clip_widget(image, ev->widget);
            }
            break;
        case SPTK_EVENT_BTTUP:
            if(ev->mouse.btt == SPTK_MOUSEBTT_LEFT && sptk_isgrab())
            {
                sptk_ungrab();
                sptk_image_clip_none(image);
            }
            break;
        case SPTK_EVENT_MOUSEMOVE:
            if(sptk_isgrab())
            {
                rc.x1 = lastx;
                rc.y1 = lasty;
                rc.x2 = ev->mouse.x;
                rc.y2 = ev->mouse.y;
            
                sptk_image_line(image,
                    ev->widget->x + rc.x1, ev->widget->y + rc.y1,
                    ev->widget->x + rc.x2, ev->widget->y + rc.y2, drawcol);
                
                sptk_update_rect(ev->widget, &rc, 0);
                
                lastx = rc.x2;
                lasty = rc.y2;
            }
            break;
    }
}

/** バーのハンドラ */

void handle_bar(SPTK_WIDGET *wg,int type,int pos)
{
    col_rgb[wg->id] = pos;
    
    drawcol = SPTK_RGB(col_rgb[0], col_rgb[1], col_rgb[2]);
    
    /* 色プレビュー */
    
    sptk_image_fillbox(image,
        COLBOX_X, COLBOX_Y, COLBOX_W, COLBOX_H, drawcol);
    
    sptk_update(NULL, COLBOX_X,COLBOX_Y,COLBOX_W,COLBOX_H, 5);
}

int main()
{
    int i;
    
    sptk_init("test", WIDTH, HEIGHT);
    
    image = sptk_window_get_image();
    
    sptk_image_text(image, 10,11, "R", 1, 0);
    sptk_image_text(image, 10,31, "G", 1, 0);
    sptk_image_text(image, 10,51, "B", 1, 0);
    
    sptk_image_box(image, COLBOX_X-1, COLBOX_Y-1, COLBOX_W+2, COLBOX_H+2, 0);
    sptk_image_fillbox(image, COLBOX_X, COLBOX_Y, COLBOX_W, COLBOX_H, drawcol);

    sptk_image_box(image, DRAWAREA_X-1, DRAWAREA_Y-1, DRAWAREA_W+2, DRAWAREA_H+2, 0);
    sptk_image_fillbox(image, DRAWAREA_X, DRAWAREA_Y, DRAWAREA_W, DRAWAREA_H, 0xffffff);

    /* ウィジェット作成 */
    
    for(i = 0; i < 3; i++)
        sptk_widget_bar_create(i, 20,10 + i * 20,100,12, 0,255,0, handle_bar);
    
    sptk_widget_create(-1, DRAWAREA_X, DRAWAREA_Y, DRAWAREA_W, DRAWAREA_H, handle_area, 0);
    
    sptk_run();

    return 0;
}
ウィジェットについて
今まではメインウィンドウ自体を対象に描画などを行ってきましたが、今回はバーなどの GUI 部品をメインウィンドウに配置するので、「ウィジェット」を使います。

SPTK においての「ウィジェット」は、メインウィンドウに配置できる子アイテムのことです。
SPTK では、ウィジェットは子ウィンドウとしては作成されず、データだけ定義されてメインウィンドウ内に配置されます。

例えば、メインウィンドウ内でマウスボタンが押された場合は、
押された位置が、作成されているウィジェットの領域範囲内であれば、ウィジェットのハンドラを呼び出し、そうでなければメインウィンドウのハンドラを呼び出します。

この場合、SPTK_EVENT 構造体におけるカーソル位置は、ウィジェットの左上位置を基準にした座標が入ります。
また、sptk_update() での更新時も、ウィジェットの指定がある場合は、ウィジェットの左上位置が基準です。

ただし、sptk_image_*() 関数でイメージ上に直接描画する際には、常にメインウィンドウの左上を基準とした座標で指定しなければならないので、注意してください。
ウィジェットはメインウィンドウの一部という扱いで描画する必要があります。

なお、ウィジェットが作成された場合は、自動的に SPTK 内部のリストに追加されます。
アプリケーション終了時には、それを元にすべてのウィジェットが削除されるので、解放処理に関しては意識する必要はありません。
解説
今回は sptk_window_set_handle() でウィンドウ用のハンドラを指定していませんが、デフォルトで設定されているハンドラが SPTK_EVENT_WINDOW_CLOSE 時に sptk_quit() を呼び出しているので、そのままデフォルトハンドラを使うことにしています。
main() 内での初期描画
今回は描画関数を多く使うので、初期化後すぐに sptk_window_get_image() でイメージのポインタを取得して、以降使い回せるようにしています。

R,G,B の文字や枠などは最初にイメージ内に描画しておきます。

//四角形枠描画
void sptk_image_box(SPTK_IMAGE *img,int x,int y,int w,int h,uint32_t col);

//四角形塗りつぶし描画
void sptk_image_fillbox(SPTK_IMAGE *img,int x,int y,int w,int h,uint32_t col);

//テキスト描画
void sptk_image_text(SPTK_IMAGE *img,int x,int y,const char *text,int len,uint32_t col);

sptk_image_text() は、1文字が 6 x 9 px の文字を描画します。
あらかじめ定義されたイメージを使うので、描画できるのは 'A'-'Z', '0'-'9', '.' の文字のみです。
len は文字数。負の値でヌル文字までを描画します。

ちなみに、ウィンドウが初期表示される際には、メインウィンドウの内容はすべて更新されるので、sptk_run() を呼ぶ前の時点では、わざわざ sptk_update() を呼ぶ必要はありません。
ウィジェットの作成
次に、メインウィンドウに配置するウィジェットを作成します。
今回は RGB のバーを各3つと、描画エリアのウィジェットを作成します。

基本ウィジェット作成
sptk_widget_create() は、基本のウィジェットを作成します。

SPTK_WIDGET *sptk_widget_create(int id,
    int x,int y,int w,int h,SPTK_HANDLE handle,int size);

size は、ウィジェットの構造体の全サイズです。
最小で sizeof(SPTK_WIDGET) のサイズが必要です。
size が最小サイズより小さい場合は、最低限必要なサイズで確保します。

描画エリアに関しては、特に必要なデータはないので、基本のウィジェットで作成しています。

バーウィジェット作成
sptk_widget_bar_create() は、バーウィジェットを作成します。

SPTK_WIDGET *sptk_widget_bar_create(int id,int x,int y,int w,int h,
    int min,int max,int pos,void (*handle)(SPTK_WIDGET *,int,int));

idウィジェットの ID です。
一つのイベントハンドラを複数のウィジェットで共有する場合に、各ウィジェットを区別できるように指定します。
値は好きなものにして構いません。
minバーの値の最小値
maxバーの値の最大値
posバーの値の初期値
handleバーウィジェットのイベントハンドラ
バーウィジェットのイベントハンドラ
バーウィジェットのイベントハンドラは、「ボタン押し時」「ドラッグ移動によりバーの値が変更された時」「ボタンが離された時」に呼ばれます。

第二引数は、SPTK_EVENT_BTTDOWN/BTTUP/MOUSEMOVE のいずれかです。
第三引数は、現在のバーの値です。

ここでは、RGB バーの値が変更された場合、描画色を変更して、色プレビューの色を更新します。
R,G,B 各3つのバーは同じハンドラで処理します。

現在の描画色は drawcol、現在の描画色の各 RGB 値は col_rgb[3] に格納されています。
描画色の RGB 値をバーの値に変更し、drawcol を再計算します。

col_rgb[3] は R,G,B の順に格納され、バーのウィジェットの ID もそれぞれ 0,1,2 と設定してあるので、第一引数のウィジェットのポインタから直接 id の値を取得し、配列の添字とします。

col_rgb[wg->id] = pos;
drawcol = SPTK_RGB(col_rgb[0], col_rgb[1], col_rgb[2]);

SPTK_RGB() マクロは、各 RGB 値から 0xRRGGBB の値を作成します。
描画エリアのハンドラ
描画エリア内でイベントが起こった場合は、描画エリアのハンドラが呼ばれます。

クリッピング
今回は、自由線描画の開始時に sptk_image_clip_widget()、描画の終了時に sptk_image_clip_none() を呼び出しています。
これは、点を描画する際のクリッピング範囲の指定です。

今回はメインウィンドウ全体ではなく、メインウィンドウの一部を描画エリアとしているので、描画エリア外には自由線が描画されないようにしなければなりません。
指定範囲外には描画されないようにするには、前述の関数によりクリッピングの指定を行います。

void sptk_image_clip_widget(SPTK_IMAGE *img,SPTK_WIDGET *widget);
void sptk_image_clip_none(SPTK_IMAGE *img);

sptk_image_clip_widget() は、指定したウィジェットの領域範囲内をクリッピング領域とします。
sptk_image_clip_none() は、クリッピングの指定をなしにします。

なお、これらの関数によるクリッピングは、sptk_image_setpixel() 関数での点描画時に処理されます。
描画しようとしている位置が、指定されたクリッピング範囲の外なら何もしません。

描画座標
また、今回はもう一つ注意しなければならないことがあります。
線の描画時には、描画エリアウィジェットにおける相対位置ではなく、メインウィンドウにおける座標で位置を指定しなければなりません。

マウスカーソルの位置はウィジェットの左上からの相対位置なので、描画座標はメインウィンドウの座標に変換する必要があります。
ウィジェットの左上位置は SPTK_WIDGET 構造体の x,y メンバで得られるので、マウスカーソルの座標にその値を足します。