GUIX カスタムWidgetの作り方

UI開発

はじめに

GUIXとは、ExpressLogic社が開発した組込み向けのGUIツールです。
現在はEclipseFoundationが管理・公開しているOSS(オープンソースソフトウェア)です。
過去の記事にも記載がありますのでご興味がある方は参照してみてください。

前提(ご注意)

本記事の「カスタムWidget」とは、筆者がこのように呼んでいるだけで、GUIXとしてこのような言葉があるわけではありません。
また、現段階ではGUIX Studioまでは対応できていないので、カスタムWidgetを作成しても、GUIX Studioを使用して配置やパラメータの設定などは出来ず、すべてをソースコードに記述する必要があります。

カスタムWidgetとは

カスタムWidgetとは、GUIX、GUIX Studioには無い機能を持ったWidgetの事を言います。
イメージとしては、GUIXにデフォルトで実装されているボタンWidgetやリストWidgetを独自に作ると言ったものとなります。
例えば、ユーザーに処理中を伝えるクルクル回る表示や、バッテリーの残容量を表示する機能などGUIXがデフォルトで持っていない表示機能を独自に作ることになります。
これらの機能は、アプリケーションに依存せず、再利用性が高いが、毎回アプリケーションとして実装するのが面倒な機能なので、これを、汎用性が高い状態で作り、使いまわせれば作業性が上がります。
理想的にはGUIX Studioにも登録して編集、コード生成が出来ればさらに汎用性が高くなります。
注意点としては、GUIXに依存するので、GUIXに変更があった場合に動かなくなる可能性が少なからずある事です。

必要な項目

カスタムWidgetを作る場合、最低限必要な項目がいくつかあります。
それは、初期化、イベントハンドラ、描画ハンドラ、管理構造体です。

初期化

管理構造体を初期化し、指定座標にカスタムWidgetを配置したり、GUIXの内部リンクにWidgetを繋げたりする必要があります。

イベントハンドラ

イベントハンドラはカスタムWidgetに動きを付ける場合には必要になりますが、特にイベントを受け取らない場合はWidgetのデフォルトハンドラを設定していても良いかもしれません。

描画ハンドラ

描画ハンドラは画面表示が無ければ不要ですが、その場合だとカスタムWidgetにする必要はないのではないかと思っていますので項目に入れています。
これらはすべてソースコードで作成する必要があります。

管理構造体

GUIXの管理構造体には、すべてのWidgetで共通で必要なメンバ変数が必要です。この最低限必要なメンバ変数は構造体の先頭アドレスからのオフセットが決まっており、どんなWidgetの管理構造体でも先頭アドレスが分かれば各変数にアクセスできる作りになっています。
共通の変数以外で必要な変数はこの後に付け足す形で定義していきます。

カスタムWidgetの実際

では実際にカスタムWidgetを作ってみたいと思います。

作るものの仕様

今回はスマートフォンなどの右上に表示されるようなバッテリーの表示を作ってみます。
仕様としては以下の様にしてみます。

・バッテリーの残量を5段階で表示

・点滅機能

・充電中の雷マークの表示

・アプリケーションからは上記3つの状態を指定できる事

管理構造体と定数の定義

まず、カスタムWidgetとして表示段階や点滅状態、雷マークの表示状態を保持するメンバが必要になります。
また、点滅の為にはタイマが必要になるのでこちらの定数も定義が必要です。
これらを、ヘッダーファイルを作成して記述します。
実際に記述したコードは以下の様になります。

#ifndef _BATTERY_H_
#define _BATTERY_H_

/**** DEFINITIONS *******************************************************************************/
#define BATTERY_UPDATE_TIMER                101                         /* タイマー番号 */
#define BATTERY_UPDATE_TIMER_VAL            (100)                       /* 0.1秒 */
#define BATTERY_UPDATE_TIMER_TICKS          (BATTERY_UPDATE_TIMER_VAL / GX_TICKS_SECOND)

#define GRP_GX_TYPE_BATTERY                 (GX_TYPE_WINDOW - 1)        /* TYPE定義 */

#define GRP_GX_BLINK_COUNTER                (400 / BATTERY_UPDATE_TIMER_VAL)

/**** TYPE DEFINITIONS **************************************************************************/
typedef struct GRP_GX_BATTERY_WIDGET_STRUCT {                           /* battery widget */
    GX_WIDGET_MEMBERS_DECLARE                                           /* widget共通メンバ */
    INT                         enable;                                 /* 有効フラグ */
    INT                         blink;                                  /* 点滅フラグ */
    INT                         b_cnt;                                  /* 点滅カウンタ */
    INT                         b_sts;                                  /* 点滅表示状態 */
    INT                         charge;                                 /* 充電フラグ */
    INT                         value;                                  /* 残容量 0-100 */
} GRP_GX_BATTERY;

/**** FUNCTION PROTOTYPES ***********************************************************************/
#define grp_gx_battery_create               _grp_gx_battery_create
#define grp_gx_battery_start                _grp_gx_battery_start
#define grp_gx_battery_stop                 _grp_gx_battery_stop
#define grp_gx_battery_draw                 _grp_gx_battery_draw
#define grp_gx_battery_event_process        _grp_gx_battery_event_process

UINT  _grp_gx_battery_create(GRP_GX_BATTERY *battery,
                             GX_CHAR *name,
                             GX_WIDGET *parent,
                             ULONG style,
                             USHORT battery_id,
                             GX_RECTANGLE *size);
VOID _grp_gx_battery_start(GRP_GX_BATTERY *battery);
VOID _grp_gx_battery_stop(GRP_GX_BATTERY *battery);
UINT _grp_gx_battery_draw(GRP_GX_BATTERY *battery);
UINT _grp_gx_battery_event_process(GX_WINDOW *window, GX_EVENT *myevent);

#endif /* _BATTERY_H_ */

まず、定数宣言としてタイマの定義と点滅の為のカウンタの定義を行っています。

構造体定義では、今回のカスタムWidgetとしてGRP_GX_BATTERYと言う名前で定義しています。この中のGX_WIDGET_MEMBERS_DECLAREがGUIXのWidget共通のメンバ定義になります。ですので、構造体の先頭で定義しています。残りのenableからvalueまでが、今回のカスタムWidget特有のメンバです。
この後の関数名のdefineはGUIXの作法に則って記述したもので、特に必要なければ不要です。
その後、カスタムWidgetの関数プロトタイプ宣言を行っています。
使い方としては、grp_gx_battery_create()でWidgetを生成して、grp_gx_battery_start()/grp_gx_battery_stop()で開始と終了します。今回は点滅状態などは構造体のメンバに直接設定することにしますが、より分かりやすくするためには設定用のAPIを作った方が良いかもしれません。
grp_gx_battery_draw()は表示の更新時にGUIXから呼ばれる描画ハンドラになりますので、この関数の中に表示処理を実装します。
grp_gx_battery_event_process()はイベント発生時にGUIXから呼ばれるイベントハンドラになります。点滅時のタイマ処理などはこちらに実装します。

カスタムWidget初期化処理

初期化処理では管理構造体を初期化し、GUIXのWidgetツリーにリンクします。初期化の際に必要となる情報は、Widgetの名称、親Widgetのポインタ、表示スタイル、ID、Widget領域の座標です。
Widgetの名称は何でも良いですが、分かりやすい名称を指定しておくとデバッグ時にWidgetのリンクをダンプした際に分かりやすくなります。親WidgetのポインタはカスタムWidgetがどのWidgetの子Widgetになるかを決めるポインタになり、GUIXのWidgetツリーにリンクする際に必要になります。表示スタイルは表示の仕方を指定するものです。IDはWidgetとしてのIDで重複することはできません。カスタムWidgetとして指定できる範囲が決められていますのでご注意ください。(詳しくはGUIXのドキュメントを参照ください。)Widgetの表示領域はそのままの意味で画面上のどこに、どのくらいのサイスで表示するかを指定します。表示領域を受け取ることでスケーリングしての表示も可能になります。(今回はスケーリングは行っていません。指定された左上から描画します。)

実際に記述したコードは以下の様になります。

/************************************************************************************************/
/* wait_circle widget生成関数                                                                   */
/************************************************************************************************/
UINT  _grp_gx_battery_create(GRP_GX_BATTERY *battery, GX_CHAR *name, GX_WIDGET *parent,
                             ULONG style, USHORT battery_id, GX_RECTANGLE *size)
{
    UINT                        status;

    status = _gx_widget_create((GX_WIDGET *)battery,        /* widget作成&共通項目初期化 */
                               name,
                               GX_NULL,
                               style | GX_STYLE_BORDER_NONE|GX_STYLE_TRANSPARENT|GX_STYLE_ENABLED,
                               battery_id,
                               size);

    if (status == GX_SUCCESS) {                             /* 作成成功 */
        battery->gx_widget_type = GRP_GX_TYPE_BATTERY;      /* widget id 設定 */
        _gx_widget_status_add((GX_WIDGET *)battery,  GX_STATUS_VISIBLE);
                                                            /* 表示属性設定 */
        battery->gx_widget_draw_function = (VOID (*)(GX_WIDGET *))_grp_gx_battery_draw;
                                                            /* 表示ハンドラ設定 */
        battery->gx_widget_event_process_function =
                        (UINT (*)(GX_WIDGET *, GX_EVENT *))_grp_gx_battery_event_process;
                                                            /* イベントハンドラ設定 */
        battery->gx_widget_normal_fill_color = GX_COLOR_ID_BUTTON_LOWER;
                                                            /* 通常色設定(未使用) */
        battery->gx_widget_selected_fill_color = GX_COLOR_ID_BUTTON_UPPER;
                                                            /* 選択色設定(未使用) */
        battery->enable = 0;                                /* widget有効フラグ */
        battery->blink = 0;                                 /* widget点滅フラグ */
        battery->b_cnt = 0;                                 /* widget点滅カウンタ */
        battery->b_sts = 0;                                 /* widget点滅表示状態 */
        battery->charge = 0;                                /* widget充電フラグ */
        battery->value = 0;                                 /* 残容量 0-100 */


        if (parent) {                                       /* 親widgetがあれば */
            _gx_widget_link(parent, (GX_WIDGET *)battery);  /* 親にリンクする */
        }
    }

    return(status);
}

スタート/ストップ処理

今回の表示は点滅もあることからタイマ駆動で表示の更新を行います。ですのでスタートするにはタイマを動作させ、ストップ時にはタイマを止める事で処理を実現させます。

実際に記述したコードは以下の様になります。

VOID _grp_gx_battery_start(GRP_GX_BATTERY *battery)
{
    battery->enable = 1;                                    /* 有効フラグセット */
    gx_system_timer_start((GX_WIDGET *)battery,             /* タイマースタート */
                          BATTERY_UPDATE_TIMER,
                          BATTERY_UPDATE_TIMER_TICKS,
                          BATTERY_UPDATE_TIMER_TICKS);
}

VOID _grp_gx_battery_stop(GRP_GX_BATTERY *battery)
{
    battery->enable = 0;                                    /* 有効フラグリセット */
    gx_system_timer_stop((GX_WIDGET *)battery,              /* タイマーストップ */
                          BATTERY_UPDATE_TIMER);
}

描画ハンドラ処理

描画ハンドラではバッテリーの表示をすべて記述します。また、子Widgetがある場合はその描画ハンドラの呼び出しも必要になります。実際に記述したコードは以下の様になります。

UINT _grp_gx_battery_draw(GRP_GX_BATTERY *battery)
{
    INT                         i;
    INT                         xpos;
    INT                         ypos;
    GX_RECTANGLE                tBox;
    GX_WIDGET                   *widget;
    INT                         top[11]={  0, 12,  2,  2, 10,  4,  4,  2,  2,  2,  2 };
    INT                         lft[11]={  2,  2, 15,  0,  0,  0,  2,  4,  7, 10, 13 };
    INT                         rit[11]={ 16, 16, 16,  3,  3,  1,  3,  5,  8, 11, 14 };
    INT                         btm[11]={  1, 13, 11,  3, 11,  9,  9, 11, 11, 11, 11 };
    INT                         x[4]={ 11,  4, 11,  4 };
    INT                         y[4]={  2,  7,  6, 11 };

    widget = (GX_WIDGET *)battery;

    if (battery->b_sts) {                                   /* 表示状態 */
        /* ブラシの設定 */
        gx_context_brush_style_set(GX_BRUSH_SOLID_FILL | GX_BRUSH_OUTLINE | GX_BRUSH_ROUND);

        /* 色を設定 リソースの色ではなくRGBで設定 */
        /* とりあえずの設定。本来は gx_widget_normal_fill_colorや */
        /* gx_widget_selected_fill_colorを使うべき。 */
        /* またはROWカラーを設定できるAPIを作るべき */
        gx_context_raw_line_color_set(0x00000000);
        gx_context_raw_fill_color_set(0x00000000);

        /* ブラシの太さを設定 */
        gx_context_brush_width_set(1);

        /* 現在の設定値を取得 */
        xpos = battery->gx_widget_size.gx_rectangle_left;                  /* 左上座標取得 */
        ypos = battery->gx_widget_size.gx_rectangle_top;

        /* 描画 */
        for (i = 0; i < 6; i++) {                           /* 外枠 */
            tBox.gx_rectangle_top    = ypos + top[i];
            tBox.gx_rectangle_left   = xpos + lft[i];
            tBox.gx_rectangle_right  = xpos + rit[i];
            tBox.gx_rectangle_bottom = ypos + btm[i];
            gx_canvas_rectangle_draw(&tBox);
        }

        /* 色を設定 リソースの色ではなくRGBで設定 */
        /* とりあえずの設定。本来は gx_widget_normal_fill_colorや */
        /* gx_widget_selected_fill_colorを使うべき。 */
        /* またはROWカラーを設定できるAPIを作るべき */
        gx_context_raw_line_color_set(0x0000FF00);
        gx_context_raw_fill_color_set(0x0000FF00);

        /* 残量(75以上) */
        if (battery->value >= 75) {
            tBox.gx_rectangle_top    = ypos + top[6];
            tBox.gx_rectangle_left   = xpos + lft[6];
            tBox.gx_rectangle_right  = xpos + rit[6];
            tBox.gx_rectangle_bottom = ypos + btm[6];
            gx_canvas_rectangle_draw(&tBox);
            tBox.gx_rectangle_top    = ypos + top[7];
            tBox.gx_rectangle_left   = xpos + lft[7];
            tBox.gx_rectangle_right  = xpos + rit[7];
            tBox.gx_rectangle_bottom = ypos + btm[7];
            gx_canvas_rectangle_draw(&tBox);
        }
        
        /* 残量(50以上) */
        if (battery->value >= 50) {
            tBox.gx_rectangle_top    = ypos + top[8];
            tBox.gx_rectangle_left   = xpos + lft[8];
            tBox.gx_rectangle_right  = xpos + rit[8];
            tBox.gx_rectangle_bottom = ypos + btm[8];
            gx_canvas_rectangle_draw(&tBox);
        }

        /* 残量(25以上) */
        if (battery->value >= 25) {
            tBox.gx_rectangle_top    = ypos + top[9];
            tBox.gx_rectangle_left   = xpos + lft[9];
            tBox.gx_rectangle_right  = xpos + rit[9];
            tBox.gx_rectangle_bottom = ypos + btm[9];
            gx_canvas_rectangle_draw(&tBox);
        }
        
        /* 残量(1以上) */
        if (battery->value >= 1) {
            tBox.gx_rectangle_top    = ypos + top[10];
            tBox.gx_rectangle_left   = xpos + lft[10];
            tBox.gx_rectangle_right  = xpos + rit[10];
            tBox.gx_rectangle_bottom = ypos + btm[10];
            gx_canvas_rectangle_draw(&tBox);
        }

        /* 充電マーク */
        gx_context_raw_line_color_set(0x00FFFF00);
        gx_context_raw_fill_color_set(0x00FFFF00);
        if (battery->charge) {
            gx_canvas_line_draw(xpos + x[0], ypos + y[0],
                                xpos + x[1], ypos + y[1]);
            gx_canvas_line_draw(xpos + x[1], ypos + y[1],
                                xpos + x[2], ypos + y[2]);
            gx_canvas_line_draw(xpos + x[2], ypos + y[2],
                                xpos + x[3], ypos + y[3]);
        }
    }
    _gx_widget_children_draw(widget);                       /* 子widgetの描画 */

    return(GX_SUCCESS);
}

今回は表示をスケーリングしないので複雑な計算は行いません。

表示するための座標を配列として固定で持ち、各座標に描画を行います。
処理としては、まず描画の為のブラシを定義します。その後、色を設定します。(ここでは黒(0x00000000)としています。)色はAPIで設定できた方が汎用性は高くなるかもしれません。その場合は色を保持するためのメンバなども必要になると思います。
ブラシの設定が出来たら、Widgetの左上座標を取得し、まず外枠を描画しています。外枠の描画は中身を塗りつぶす四角を描画することで複数ピクセルの幅を持つ枠を描画しています。もちろん、四角を描画するのではなく線を描画する事でも同じ事が出来ます。この辺りは作る人の好みでしょうか。
外枠の描画が終われば中身の描画になります。ブラシの色を緑に設定しなおして、設定残量ごとに描画処理を分けています。
その後、雷マークが必要な場合の描画を行っています。(ここでは黄色の線を3本書いています。)

描画を行うと以下のような表示になります。

100%表示
50%と雷表示

イベントハンドラ処理

イベントハンドラでは_grp_gx_battery_start()で開始されたタイマイベントを処理します。
実際に記述したコードは以下の様になります。

UINT _grp_gx_battery_event_process(GX_WINDOW *window, GX_EVENT *myevent)
{
    UINT                        status = 0;
    GRP_GX_BATTERY              *ptBattery;
    
    ptBattery = (GRP_GX_BATTERY *)window;
    switch(myevent->gx_event_type) {
        case GX_EVENT_TIMER:                                /* タイマイベント */
            if (ptBattery->enable == 1) {                   /* 有効フラグ確認 */
                if (ptBattery->blink) {                     /* 点滅有 */
                    ptBattery->b_cnt++;                     /* 点滅カウンタ更新 */
                    if (ptBattery->b_cnt >= GRP_GX_BLINK_COUNTER) {
                        ptBattery->b_sts = !ptBattery->b_sts;
                                                            /* 点滅表示状態更新 */
                        ptBattery->b_cnt = 0;               /* 点滅カウンタリセット */
                    }
                } else {
                    ptBattery->b_sts = 1;                   /* 点滅表示状態=表示 */
                    ptBattery->b_cnt = 0;                   /* 点滅カウンタリセット */
                }
                gx_system_dirty_partial_add((GX_WIDGET *)window, &(window->gx_widget_size));
                                                            /* 自分の領域を更新領域に追加 */
                                                            /* ここでは描画せずGUIXからの */
                                                            /* 呼び出しに任せる */
            }
            break;

        default:
            status = _gx_widget_event_process(window, myevent);
            break;
    }
    return status;
}

タイマイベントではカウンタを設け、複数回のタイムアップで点滅状態を反転させています。タイマの間隔を大きくしても対応できますが、今回の表示更新のタイミングがタイマイベントにしているため、間隔が大きいとスタート/ストップで少し違和感を感じるかもしれません。

アプリケーションへの組み込み

ここまででカスタムWidget側の実装は終了です。次は実際にアプリケーションに組み込みます。
実際に記述したコードは以下の様になります。

#include <stdio.h>
#include "tx_api.h"
#include "gx_api.h"

#include "demo_guix_resources.h"
#include "demo_guix_specifications.h"

#include "battery.h"

/************************************************************************************************/
/* スレッドの定義                                                                               */
/************************************************************************************************/
TX_THREAD           demo_thread;
UCHAR               demo_thread_stack[4096];

/************************************************************************************************/
/* グローバルで使用する変数の定義                                                               */
/************************************************************************************************/
GX_WINDOW_ROOT      *root;
GRP_GX_BATTERY      tBattery;

/************************************************************************************************/
/* プロトタイプ宣言                                                                             */
/************************************************************************************************/
VOID  demo_thread_entry(ULONG thread_input);
extern UINT win32_graphics_driver_setup_565rgb(GX_DISPLAY* display);

/************************************************************************************************/
/* メイン関数                                                                                   */
/* スタートアップ後呼ばれる                                                                     */
/************************************************************************************************/
int main(int argc, char ** argv)
{
    tx_kernel_enter();
    return(0);
}

/************************************************************************************************/
/* ThreadXから起動時に呼ばれる関数                                                              */
/* メイン関数のtx_kernel_enter()の延長線上で呼ばれる                                            */
/************************************************************************************************/
VOID tx_application_define(void *first_unused_memory)
{
    /* GUIXの初期化のためのスレッド */
    tx_thread_create(&demo_thread, "GUIX Demo Thread", demo_thread_entry, 
                     0,  demo_thread_stack, sizeof(demo_thread_stack), 
                     1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
}

/************************************************************************************************/
/* GUIXの初期化の処理                                                                           */
/* GUIXの初期化と画面オブジェクトの作成、画面の表示とGUIXのスタートを行う                       */
/************************************************************************************************/
VOID  demo_thread_entry(ULONG thread_input)
{
    GX_RECTANGLE                tBatSize;
    
    /* GUIX 初期化 */
    gx_system_initialize();

    /* ディスプレイオブジェクトを生成 */
    gx_studio_display_configure(DISPLAY_1, win32_graphics_driver_setup_565rgb,
        LANGUAGE_ENGLISH, DISPLAY_1_DEFAULT_THEME, &root);

    /* メイン画面を作成(rootを指定) */
    gx_studio_named_widget_create("wndMain", (GX_WIDGET *) root, GX_NULL);

    tBatSize.gx_rectangle_top    = 88;
    tBatSize.gx_rectangle_bottom = 88 + 40;
    tBatSize.gx_rectangle_left   = 140;
    tBatSize.gx_rectangle_right  = 140 + 40;
    grp_gx_battery_create(&tBattery, "BAT", (GX_WIDGET *)&wndMain, 0, 32769, &tBatSize);

    /* メイン画面を表示(実際の表示はGUIXが動作してから) */
    gx_widget_show(root);

    /* GUIXスタート */
    gx_system_start();

まず、今回作ったカスタムWidgetのヘッダーファイル(battery.h)をインクルードします。

そして、管理構造体の実体を定義します。(GRP_GX_BATTERY tBattery;)

その後、demo_thread_entry()の初期化の中でwndMainWidgetを親としてカスタムWidgetを生成しています。

次にアプリケーションで表示を更新する部分を記述します。今回はwndMainのイベントハンドラに記述しましたが、実際にはバッテリーの残量が変わった際に行えばよい部分になります。

実際に記述したコードは以下の様になります。

#include <stdio.h>
#include "tx_api.h"
#include "gx_api.h"

#include "demo_guix_resources.h"
#include "demo_guix_specifications.h"

#include "battery.h"

#define MAIN_BATTERY_UPDATE_TIMER           1
#define MAIN_BATTERY_UPDATE_TIMER_TICKS     (1000 / GX_TICKS_SECOND)        /* 0.5秒 */

/************************************************************************************************/
/* グローバルで使用する変数の定義                                                               */
/************************************************************************************************/
extern GRP_GX_BATTERY   tBattery;

int     val[20]={ 100, 74, 49, 24, 0, 100, 74, 49, 24, 0, 100, 74, 49, 24, 0, 100, 74, 49, 24, 0 };
int     blk[20]={   0,  0,  0,  0, 0,   1,  1,  1,  1, 1,   0,  0,  0,  0, 0,   1,  1,  1,  1, 1 };
int     chg[20]={   0,  0,  0,  0, 0,   0,  0,  0,  0, 0,   1,  1,  1,  1, 1,   1,  1,  1,  1, 1 };
int     pos=0;

/************************************************************************************************/
/* イベントハンドラ                                                                             */
/* main_event_handlerはGUIXStudioでリソースに設定する                                           */
/************************************************************************************************/
UINT main_event_handler(GX_WINDOW *window, GX_EVENT *myevent)
{
    UINT status = 0;

    switch(myevent->gx_event_type) {
        case GX_EVENT_SHOW:                                 /* 表示イベント */
            status = gx_window_event_process(window, myevent);
            gx_system_timer_start((GX_WIDGET *)window,      /* バッテリ残量更新タイマ */
                                  MAIN_BATTERY_UPDATE_TIMER,
                                  MAIN_BATTERY_UPDATE_TIMER_TICKS,
                                  MAIN_BATTERY_UPDATE_TIMER_TICKS);
            break;

        case GX_EVENT_TIMER:                                /* タイマイベント */
            if (tBattery.enable == 1) {                     /* バッテリの残量や点滅を設定 */
                tBattery.value  = val[pos];
                tBattery.blink  = blk[pos];
                tBattery.charge = chg[pos];
                pos = (pos+1) >= 20 ? 0: pos+1;
            }
            break;

        case GX_EVENT_PEN_DOWN:
            if (tBattery.enable == 1) {
                grp_gx_battery_stop(&tBattery);
            } else {
                grp_gx_battery_start(&tBattery);
            }
            break;

        default:
            status = gx_window_event_process(window, myevent);
            break;
    }
    return status;
}

表示の状態を変えるために、バッテリー残量、点滅、雷マークの設定をテーブルで持ち、タイマイベントで表示を更新しています。
表示の更新は管理構造体のメンバの値を変えることで行っています。(設定用のAPIを用意すれば分かりやすくなると思います。)
また、wndMainWidgetのどこかをタッチすることでスタート/ストップを切り替えます。

実際の動作

以上でデモ表示の処理が書けました。
実際に動かすと以下のような表示になります。

今回作った表示が小さいので見にくいかもですが。

まとめ

いかがでしたでしょうか。

今回作ったカスタムWidgetはソースコードで閉じた処理になるので再利用がしやすくなります。(ソースコードを他のプロジェクトにコピーするだけで使えるようになります。)
バッテリー表示だけでなく様々な表示部分に応用が可能になります。
また、画面表示の部品ごとにソースコードを作成できるので、分割しての作業や異常動作時の問題の特定も行いやすくなるかもしれません。
GUIX Studio側には触れませんでしたが、GUIX Studio側も対応できれば配置やパラメータの設定、リソースとしての生成なども行えるようになります。

もしご興味があれば弊社までご連絡いただければと思います。