Closed7

ImGUI への関心…… (→ microui の内部実装など)

toyboot4etoyboot4e

ゲームの UI に興味があります。まずザクッと宣言的に書いて、後からリッチに作り込めるのが良いと思います。コードやデータをどう分離すれば、年輪が増えていくように自然なレイヤ分けになるのかとぼんやり悩みつつ、まずは ImGUI (immediate-mode GUI) への理解を深めたいと思います。

toyboot4etoyboot4e

画像を出すだけだと飽きてきたところもあって、 Recursive Tree とか vger (SDF) みたいな、時間や空間で補間できるものを使いたいと思います。しかし使い方のイメージが無いため、ツーリングで創造性を補完する方向性になります。たとえば ImGUI でシェーダや SDF を気軽に使えたら……?

UI に関して良い分離ができたら、ツーリングも良くなり、創造性も増すという流れに期待しています。

toyboot4etoyboot4e

Immediate-mode GUI はバッチデータまで用意してくれるけれど、手動で描画できた方が嬉しいです。全画面自作するためのベースが欲しいと思います。

toyboot4etoyboot4e

microui のデモを起動してみました。 1,504 行のライブラリでこの出来栄え:

Dear ImGUI は 12,000 行程度です。

やはりボタンの開閉状態などを自動的に用意してくれるのが ImGUI の面白いところだと思います。 egui だったらアニメーションすら再生してくれるもので、可能性を感じます。

toyboot4etoyboot4e

microui (1,504 行) をパラパラと読んでみました。

ステート管理 (retained-mode)

mu_Id のハッシュマップ (ある種の)

ステートはプールに保存します:

#define MU_CONTAINERPOOL_SIZE   48
#define MU_TREENODEPOOL_SIZE    48

// free 用
typedef struct { mu_Id id; int last_update; } mu_PoolItem;

typedef struct {
  /* ~~~~ */
  mu_Vec2 scroll;
  /* ~~~~ */
  int open;
} mu_Container;

struct mu_Context {
  /* ~~~~ */
  /* retained state pools */
  mu_PoolItem container_pool[MU_CONTAINERPOOL_SIZE];
  mu_Container containers[MU_CONTAINERPOOL_SIZE];
  mu_PoolItem treenode_pool[MU_TREENODEPOOL_SIZE];
  /* ~~~~ */
}

プールの API はハッシュマップで、要素を mu_Id で探します:

int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) {
  int i;
  unused(ctx);
  for (i = 0; i < len; i++) {
    if (items[i].id == id) { return i; }
  }
  return -1;
}

ID の計算

Widget の ID は、 widget 相当の関数呼び出しの引数から計算します。また、親子関係を考慮します (親のハッシュ値を引き継ぎます):

/* 32bit fnv-1a hash */
#define HASH_INITIAL 2166136261

static void hash(mu_Id *hash, const void *data, int size) {
  const unsigned char *p = data;
  while (size--) {
    *hash = (*hash ^ *p++) * 16777619;
  }
}


mu_Id mu_get_id(mu_Context *ctx, const void *data, int size) {
  int idx = ctx->id_stack.idx;
  mu_Id res = (idx > 0) ? ctx->id_stack.items[idx - 1] : HASH_INITIAL;
  hash(&res, data, size);
  ctx->last_id = res;
  return res;
}

レイアウト

mu_layout_next は、テーブルレイアウトを考慮した四角形を返します:

mu_Rect mu_layout_next(mu_Context *ctx) {
  mu_Layout *layout = get_layout(ctx);
  mu_Style *style = ctx->style;
  mu_Rect res;

   /* ~~~~*/
    // 列サイズが定義されていたらその値を使う
    res.w = layout->items > 0 ? layout->widths[layout->item_index] : layout->size.x;
    res.h = layout->size.y;

    // サイズが 0 だったら初期値を使う
    if (res.w == 0) { res.w = style->size.x + style->padding * 2; }
    if (res.h == 0) { res.h = style->size.y + style->padding * 2; }

    // サイズが負の値なら、残スペースを加算する。
    // たとえば w = -31 の場合は、 w = (残りスペース - 30) になる
    if (res.w <  0) { res.w += layout->body.w - res.x + 1; }
    if (res.h <  0) { res.h += layout->body.h - res.y + 1; }
   /* ~~~~*/

  /* apply body offset */
  res.x += layout->body.x;
  res.y += layout->body.y;

  /* update max position */
  layout->max.x = mu_max(layout->max.x, res.x + res.w);
  layout->max.y = mu_max(layout->max.y, res.y + res.h);

  return (ctx->last_rect = res);
}
toyboot4etoyboot4e

microui の 作者 (rxi 氏) ブログを読んでみました。いかにステートレスに見せるかという話が面白かったです (Cachd Software Rendering. Software rendering というのは Vec<Pixel> を CPU サイド?で作ることだと思います) 。

rxi 氏は lite (テキストエディタ) の作者でもあり、約 7,000 行 + プラグインで恐ろしい完成度です。。

lite のリポジトリで IME サポートの issue/commit を見ました。いいですね。僕も 縦書きエディタを作ってみたいと思っている のですが、 IME を縦書きにしたり、確定後の文字を再変換するのが厳しいんじゃないかと思っています。いつか SDL のコードを読んでみたいものですが……

toyboot4etoyboot4e

micioui のおかげで ImGUI を自作するイメージが湧きました。実際に作ることは無いと思うので、ここで閉じます。

このスクラップは2022/04/25にクローズされました