2024年07月30日

Zig言語:GUI: Zig言語でDear ImGui/ImPlotを使ってみたメモ 2024/07

Zig言語:GUI: Zig言語でDear ImGui/ImPlotを使ってみたメモ 2024/07

Zig言語:GUI: Zig言語でDear ImGui/ImPlotを使ってみたメモ 2024/07

alt alt
alt

はじまり


Zig-0.12.0が自分の環境で珍しくまともに動いたのを機にGUIアプリが作れる
軽量 Dear ImGuiライブリ(以下ImGuiと記す)をZig言語から呼び出して使ってみました
ついでにグラフ表示ライブラリのImPlotも使ってみたメモ

ちなみにZig言語は発見して以来2.5年ぶりに触ってみることになりました 😃

動作環境・必要条件


  • WindowsOS (Window10以降)
  • Linuxに対応しました (2024.10) Ubuntu/Debianファミリなら多分OK [1]
  • MSys2/MinGW の基本コマンド (make, rm, cp, strip ...)を使っています [2]
  • Gitコマンド

Zig言語の注意点 現時点(2024/08 - 2025/04)


Zig言語は現在も[3] 😃 開発中につき言語仕様がリリース毎に変更されて(数ヶ月から半年周期くらいか)
ユーザー側のソースコードとの互換がなくなります
従ってこのページの内容を試す場合、Zig言語のバージョンは以下を使うことを強く推奨します
具体的にはこれ

[x] zig-0.14.0 Windows OS
[x] zig-0.14.0 Linux OS

Dear ImGuiライブラリの使用


C++言語で書かれたDear ImGuiライブラリをZig言語から直接呼ぶことはできないので
C言語のヘッダファイルを持つCImGui(/CImPlot)ライブラリを経由して
Zig言語から呼び出して使います

%%{init: {'theme':'forest'}}%% flowchart LR; A("Dear ImGuiライブラリ<br/>C++言語") B("CImGuiライブラリ<br/>C言語"); C("ImPlotライブラリ<br/>C++言語"); D("CImPlotライブラリ<br/>C言語"); A --> B; A -- アドオンライブラリ --> C; C --> D; B -- C言語Header: @cImport() --> M; D -- C言語Header: @cImport() --> M; B -- Header --> D; M("ユーザーGUIプログラム<br/>Zig言語") style A color:black, fill:yellow, stroke:black style C color:black, fill:yellow, stroke:black style M color:white, fill:darkblue, stroke:white
  • Dear ImGui
    C++言語で書かれたイミディエートモード [4]で動作する軽量GUIライブラリ

    • CImGui: Dear ImGuiを各種言語から使える様にするC言語ライブラリ
  • ImPlot
    C++言語で書かれたDear ImGuiのアドオン・グラフ描画ライブラリ

    • CImPlot: ImPlotを各種言語から使える様にするC言語ライブラリ
  • ImNodes/CImNodes, ImGuizmo/CImGuizmo, etc
    今回未使用の他のアドオンライブラリ

  • 本当はラッパーライブラリが...
    例えばCImGuiライブラリユーザーGUIプログラムの間に
    *.jsonファイルからZig言語で書かれた関数変換ッパーライブラリ(後述)を生成し
    ユーザー側はそのライブラリ関数経由でを使うのが将来の姿
    今回はCImPlot用の関数変換ラッパーライブラリ(後述)のみを作りました

Zig言語のメリットの一つにC言語ライブラリを直接呼べるというのがあって
今回はそれを使ってみました[5]

Dear ImGuiのバージョン


ImGui/CimGui version 1.91.8dock (2025/08) (最新バージョンはここ)

GUIライブラリ使用時のポイント


基本的にリポジトリの中に必要なライブラリを全て持つことにします(外部依存なし(Linuxは除く))

  • 生成されるEXEファイルはスタティックリンクとし単一ファイル(dll依存なし)で実行可能とする
  • GUI描画のバックエンドはOpenGL3とし GLFW-OpenGL3, SDL2-OpenGL3, SDL3-OpenGL3の組み合わせで使います
  • アイコンフォント Font Awesomeを使います。添付
    alt
  • GLFW 3.4.0 スタティックライブラリを添付
  • SDL2/SDL3 スタティックライブラリを添付
  • 各種画像(JPEG,PNG..etc)表示、保存用にSTB (stb_imageだけ)ライブラリを添付
  • ImPlotでImDrawIdx="unsigned int"を有効にする
  • ImGuiで日本語入力候補位置を正常化するように Input method (IME)定義
    IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONSを有効にする[6]

ビルドと実行


  1. Windowsのコマンドライン上でプロジェクトをダウンロードします

    git clone --recurse-submodules https://github.com/dinau/imguinz
    

    (Githubリポジトリはここ ImGuinZ) [7]

  1. 例えばexamplesの一つに移動します

    cd imguinz/examples/glfw_opengl3_jp
    
  2. ビルドと実行

    make run       #  又は  zig build --release=fast run
    

ImGui: GUIプログラムの概略


%%{init: {'theme':'forest'}}%% flowchart LR; A("プログラム開始<br/>main()") --> B(":: 初期化処理 ::"<br/> GLFW, OpenGL3<br/> Dear ImGui<br/> フォント<br/> 変数その他); B -->C{"while<br/> メインループ"}; C -- 終了 --> G(:: 終了処理 ::) -->I(プログラム終了) C -- 継続 -->D(:: 処理 ::<br/>描画系1); D -->E[":: GUI メイン ::<br/> Dear ImGui"]; E -->F(:: 処理 ::<br/>描画系2); F -->C; style E color:black, fill:orange, stroke:black
  • 上のフローチャートでグリーンの部分は定型処理なので新規プログラムにコピペで使用可能部分
    これらの詳細は簡単なGUIならひとまず理解する必要のない部分
  • GUIの具体的なプログラムはオレンジ色の 「:: GUIメイン :: Dear ImGui」の部分に書いていく
    各種イベント処理もこの中で行う
GUIのメイン処理 Dear ImGui

flowchart TB; E[":: GUI メイン ::<br/> Dear ImGui"]; style E color:black, fill:orange, stroke:black

メイン処理の中でWindowを一つ作ってGUI部品(ウィジェット)を並べていきます
レイアウターはなく、部品を縦横に配置していくだけなので簡単です[8]

GUI部品を縦(タテ/垂直)に並べる

alt

上図のレイアウトを分かり易く引数を省略して書くと以下の様な仮想コードになります

// Show window
{ // defer文を使う時、このブロック分離は必須
  _ = ig.igBegin();   // Windowを一つ作って開始
  defer ig.igEnd ();  // defer文によるWindow終了処理
  // 
  ig.igText();        // テキストウィジェットを配置
  if (ig.igButton()){ // ボタンウィジェットを配置、ボタン押下イベントもここで取る
    MySaveFunction(); // 押下イベント処理を実行
  }
  ig.igInputText();  // テキスト入力ウィジェットを配置
  ig.igSliderFloat(); // スライダーウィジェットを配置 
  // Window処理終了
} // ブロック分離終了

GUI部品関数を上から下に書くと、その順に画面に表示されていきます
実際のコードは関数引数を以下の様に指定します

// Show window
{
  _ = ig.igBegin ("Dear ImGui", null, 0);
  defer ig.igEnd ();
  // 
  ig.igText("Hello, world %d", 123); // テキストウィジェットを配置
  if (ig.igButton("Save", btnSize)){ // ボタンウィジェットを配置、ボタン押下イベントもここで取る
    MySaveFunction();                // 押下イベント処理を実行
  }
  ig.igInputText("string", &buf, buf.len, 0, null, null);  // テキスト入力ウィジェットを配置
  ig.igSliderFloat("float", &fval, 0.0f, 1.0f, "%.3f", 0); // スライダーウィジェットを配置 
}
GUI部品を横(ヨコ/水平)に並べる

GUI部品のデフォルト配置が縦なのでヨコに並べる時は、GUI部品の後に水平に並べる関数 igSameLine() を呼び出します

flowchart LR; A["Button1"]-->B["Button2"]-->C["Button3"]; style A color:white, fill:navy, stroke:black style B color:white, fill:navy, stroke:black style C color:white, fill:navy, stroke:black

上の様にボタンを3つ横に並べたいときは以下の順に関数を呼び出します

flowchart LR; A["igButton()"]-- igSameLine()-->B["igButton()"]--igSameLine()-->C["igButton()"]; style A color:white, fill:navy, stroke:black style B color:white, fill:navy, stroke:black style C color:white, fill:navy, stroke:black

実際のコード

// Show window
{
 _ = ig.igBegin ("Dear ImGui", null, 0);
 defer ig.igEnd ();
 // 
 if (ig.igButton("Button1", btnSize)){ // ボタン1 ウィジェットを配置
   Button1Func();                      
 }
 ig.igSameLine (0, -1.0)               // 次の部品を横に並べる関数
 if (ig.igButton("Button2", btnSize)){ // ボタン2 ウィジェットを配置
   Button2Func();                      
 }
 ig.igSameLine (0, -1.0)               // 次の部品を横に並べる関数
 if (ig.igButton("Button3", btnSize)){ // ボタン3 ウィジェットを配置
   Button3Func();                      
 }
}

igSameLine()の引数(0, -1.0)はデフォルト値でこれを指定しておくと適度に配置してくれます 😃

特定の値にしたい時は最初の引数が開始位置で、次がスペース量です。

GUI部品のタテ間隔を空ける

flowchart LR; A["Button1"] B["Button2"] style A color:white, fill:navy, stroke:black style B color:white, fill:navy, stroke:black

上の場合はigNewLine()関数を挿入します

flowchart LR; A["igButton()"]-- igNewLine()-->B["igButton()"] style A color:white, fill:navy, stroke:black style B color:white, fill:navy, stroke:black
部品間隔に関する関数群

ひとまず以下のような読めばだいたい想像がつく関数が用意されていますが
詳細は省略します

igSeparator(void);
igSameLine(float offset_from_start_x,float spacing);
igNewLine(void);
igSpacing(void);
igDummy(const ImVec2 size);
igIndent(float indent_w);
igUnindent(float indent_w);
BeginGroup();                   
EndGroup();                     
AlignTextToFramePadding();      
GetTextLineHeight();            
GetTextLineHeightWithSpacing(); 
GetFrameHeight();               
GetFrameHeightWithSpacing();    

説明はソースコードに書かれていて
使い方はデモコードで知ることになります

最も重要な: GUIのラベルは識別子

  1. GUI部品ラベルとはGUI部品関数の最初の引数ラベルのこと
    ig.igButton("Save", btnSize)の場合は「"Save"」ラベルが識別子となる

  2. その型は文字列

  3. このラベルがその部品の識別子となるので同一スコープ内での重複は許されない
    重複した場合はプログラムの動作が不規則に挙動不審であやしい動きをする (-:
    最近のバージョンでは識別子が重複していると動作時に以下の様にGUI上でメッセージが表示される様になった

  4. WindowもGUI部品
    Windowラベルも固有の識別子が必須

    ig.igBegin("win1")  // 一つ目のWindow 
    ...
    ig.igEnd()
    
    ig.igBegin("win2")  // 二つ目のWindow
    ...
    ig.igEnd()
    
  5. Windowが分かれていれば重複名OK
    "Button1"ラベルは重複使用だがOK

    ig.igBegin("win1")  // 一つ目のWindow 
      if ig.igButton("Button1",...){
      }
    ...
    ig.igEnd()
    
    ig.igBegin("win2")  // 二つ目のWindow
      if ig.igButton("Button1",...){
      }
    ...
    ig.igEnd()
    
  6. 識別子の可否
    GUI部品のラベル部分だけを抜き出すと
    以下はアプリ内で別Windowにボタンが所属しているので"Button1"ラベルの使用はOK

    OK: "win1" - "Button1"
    OK: "win2" - "Button1"
    

    同一Window内でもTabウィジェット内に所属するなら、そこで識別子分離(別スコープ)が起こるので重複ラベルOK

    OK: "win1" - "Button1" 
               - "tab1" - "Button1"   // Tabウィジェットで分離されたので"Button1"重複はOK
    OK: "win2" - "Button1" 
               - "tab1" - "Button1"
               - "tab2" - "Button1"   // Tabウィジェットで分離されたので"Button1"重複はOK
    NG:                 - "Button1"   // 同一Tab内で"Button1"重複はNG
    
  7. 同一スコープ内で同じ表示ラベルを使いたい場合
    ## + 任意の文字列 で識別子を作る

    ig.igButton("Button1##bb1",..)
    ig.igButton("Button1##bb2",..)
    

    ##bb1, ##bb2の部分は表示されず識別要素として使われる

  8. まとめ
    最初のWindowラベルからたどって最後の当該GUI部品のラベルを全部並べて
    Hash値を取り、それがアプリ内で重複しなければOK

    部品を大量にプログラムで並べたいときは
    ig.igPushID(..)
    ig.igPopID()
    を使って意図的に識別子分離することも可能な様だ

  9. 参考: このあたり
    https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system

  10. 識別子が不要なGUI部品
    マウスでクリックできないGUI部品は識別子カウントされない
    例えば igText()やセパレータ等々

次に重要な: GUI部品のラベルを隠す

部品を並べていくと勝手に表示される部品ラベルを非表示にしたい場合がある
ラベルを隠すには

ig.igSliderFloat("",...)
ig.igSliderFloat("##",...)

の様にラベル部分を""(空文字)や##にすれば良いが
これだと識別子重複違反になるので普通は##slider1の様に
##の後に任意で非重複な識別子を追加する

ig.igSliderFloat("##slider1",...)

Examplesプログラムを実行した時のスクリーンショット


ImGui-Toggle / CImGui-Toggle


トグルスイッチ・ウィジェット・ライブラリを使ってみた (2025/02)

glfw_opengl3_imgui_toggle.zig

alt

ImGui-Knobs / cimgui-knobs

アナログ的なツマミ調整ウィジェット
ミキサー類に使える

glfw_opengl3_imknobs.zig

alt

ImSpinner / CImSpinner

スピナーウィジェット
回転系のスゴめなアイテム群

glfw_opengl3_imspinner.zig

alt
alt

glfw_opengl3, sdl2_opengl3, sdl3_opengl3

glfw_opengl3 , sdl2_opengl3 , sdl3_opengl3

アイコンフォントを表示

alt

glfw_opengl3_jp

glfw_opengl3_jp
日本語の表示と入力

alt

IconFontViewer

iconFontViewer
Iconフォントビューア、部分ズーム

alt

glfw_opengl3_image_load

glfw_opengl3_image_load
画像の表示と保存、部分ズーム

alt

黄色の「Save Image」ボタンでキャプチャした画像は./zig-out/binフォルダに保存されます
保存形式はJPEG / PNG / BMP / TGA が選択可能

glfw_opengl3_implot

glfw_opengl3_implot
ImPlotライブラリ使用デモ

alt

ImPlot デモを「 Zig言語」で書いてみた


現状、全てではないです(残りはおいおい)
C++言語で書かれたデモを手動 (^^; でZig言語に変換しています
ChatGPTにお願いしたけど無料版なので無理でした
orz
orz

ビルドと実行
pwd
examples/imPlotDemo
make run   # or zig build --release=fast run

Zig言語で書いたImPlot デモソース demoAll.zig
ImPlot使用時のImVec4周りが多重定義でコンパイルできない(理由は不明)ので
やっつけ(TODO)な感じになっています
orz

以下はC++だったコードをZig言語で書き直したデモを実行した時の表示画像です
(C++コードと全く同じ画面なわけですが... 😃

Plots Tab

LinePlots (アニメーション)

alt

BarGroups

alt

BarStacks

alt

PieCharts

alt

Heatmaps

alt

Histogram2D

alt

Images

alt

Axes Tab

LogScale

alt

Subplots Tab

Tables (アニメーション)

alt

Tools Tab

DragRects

alt

コンソールウインドウを表示・非表示にする


各exampleフォルダの中の build.zig を開いて以下の行で表示/非表示にします
デフォルト: 非表示
以下をコメントアウト: 表示

... snip ...
exe.subsystem = .Windows;  // Hide console window
... snip ...

その後 make run を実行します

ImPlotラッパー関数について


上で書いた関数変換ラッパーライブラリです
ImPlotの場合このライブラリを作らないと結構大変なことになるので
仕方なく(オイ) 作りました
ImPlot関数の呼び出しが簡単になるようにラッパー関数を半自動生成しています
本来は上で書いたJsonデータを使って自動生成プログラムを書くのが正攻法ですが
とりあえずJsonファイルを使わず半手動プログラムから生成しています 😃

以下は生成された関数変換ラッパーライブラリ
examples/imPlotDemo/src/zimplot.zig

Zig言語用Dear ImGuiラッパーライブラリ


ちゃんとラッパーライブラリを作ったプロジェクトがあるので
そのうちというかZig言語の正式リリース以降にさらに活発にメンテナンスされるかも

https://github.com/pdoane/zig-imgui/blob/main/src/imgui.zig
https://github.com/SpexGuy/Zig-ImGui/blob/master/zig-imgui/imgui.zig

SDL libraries


https://github.com/libsdl-org/SDL/releases

My tools version


  • Git version 2.46.0.windows.1
  • Make: GNU Make 4.4.1
  • Zig: 0.14.0
  • SDL2 2.32.0
  • SDL3 3.2.6

類似プロジェクト


Language [9] Project
Lua Script LuaJITImGui
NeLua Compiler NeLuaImGui
Nim Compiler ImGuin, Nimgl_test, Nim_implot
Python Script DearPyGui for 32bit WindowsOS Binary
Ruby Script igRuby_Examples
Zig, C lang. Compiler Dear_Bindings_Build
Zig Compiler ImGuinZ

SDL game tutorial Platfromer


ald

Language [9:1] SDL Project
LuaJIT Script SDL2 LuaJIT-Platformer
Nelua Compiler SDL2 NeLua-Platformer
Nim Compiler SDL3 / SDL2 Nim-Platformer-sdl2/ Nim-Platformer-sdl3
Ruby Script SDL3 Ruby-Platformer
Zig Compiler SDL2 Zig-Platformer

  1. Linux上ではSDL3未対応 ↩︎

  2. MSys2/MinGW系コマンドなくても多分OK(未確認) ↩︎

  3. 順調にいっても2025年末が最速と予測、普通にいけばもっと先かも 😃 ↩︎

  4. メイン処理がスーパーループになっていてGUIの描画とイベント処理を
    スーパーループのなかで実行する。 保持モードとイミディエイト モード ↩︎

  5. build.zigにいろいろ登録する必要がある ↩︎

  6. C++コンパイラがMSVC以外の場合このオプションが必要 ↩︎

  7. Githubページのほうが最新版かも ↩︎

  8. スプリッターはある ↩︎

  9. Alphabectial order ↩︎ ↩︎

posted by Copyright (C) audin All Rights Reserved. at 23:05| Comment(0) | Zig言語 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
×

この広告は90日以上新しい記事の投稿がないブログに表示されております。