std.ui

画面描画および入力管理。プリミティブ描画(点・線・矩形・円・テキスト)およびキーボード・マウス入力。

2つのレイヤーで構成されます。**低レベルレイヤー**は単一の描画サーフェス(フレームバッファ)とキー・マウスイベント処理を提供します。**高レベルレイヤー**(即時モードUI)は、ウィジェットツリーを構築して `Widget` を返し、自動レイアウトと描画を行います。両レイヤーはシームレスに混在して利用できます(高レベルウィジェットは低レベル描画 API 上に構築されています)。

メインループは、どちらのレイヤーでも基本的に以下の4ステップの繰り返しとなります。

1. ui.poll() イベント(キー入力・マウス移動など)を処理する 2. 描画処理 低レベル API(ui.clear, ui.fill_rect, ui.text 等)または ui.show(widget) 3. ui.present() 描画結果を画面に反映(バックバッファのフリップ)する 4. ui.frame() 次のフレーム刻みまで待機する(sleep ではなく ui.frame を推奨)

GUI 環境を持たないプラットフォームでも同一コードで動作します。描画先が**オフスクリーンサーフェス**となり、描画結果は ui.get() や ui.to_png() で取得可能です(ui.visible() で画面の有無を判定できます)。

`shark` コマンドは、macOS / Windows / Linux ではネイティブウィンドウを開きます。 GUI をサポートしない環境(SSH 接続先など)では自動的にオフスクリーン描画となります。環境変数 `SHARK_UI=off` を設定すると、ウィンドウを開かずにヘッドレスモードで実行できます。

テキスト描画はデフォルトで内蔵の 5×7 ビットマップフォント(ASCII のみ)を使用します。日本語などマルチバイト文字を表示する場合は、ui.font() でシステムフォントを指定します(Web/ブラウザ環境ではブラウザが描画します)。

import std.ui;

ui.open("さめ", 160, 120);
var x = 10;
while ui.poll() {
  if ui.key("right") { x += 2; }
  if ui.pressed("esc") { ui.quit(); }
  ui.clear(ui.rgb(0, 20, 40));
  ui.fill_circle(x, 60, 12, ui.rgb(255, 140, 60));
  ui.text(4, 4, "press esc to quit", ui.rgb(255, 255, 255));
  ui.present();
  ui.frame();
}
ui.close();
ui.open ui.close ui.width ui.height ui.scale ui.pixel_ratio ui.visible ui.present ui.frame ui.rgb ui.rgba ui.alpha ui.red ui.green ui.blue ui.mix ui.clear ui.set ui.get ui.hline ui.vline ui.line ui.rect ui.fill_rect ui.circle ui.fill_circle ui.clip ui.clip_off ui.blit ui.canvas ui.load_png ui.draw ui.tri ui.depth ui.clear_depth ui.text ui.text_width ui.text_height ui.poll ui.quit ui.key ui.pressed ui.released ui.typed ui.mouse_x ui.mouse_y ui.mouse ui.clicked ui.wheel ui.wheel_x ui.cursor ui.input ui.input_off ui.marked ui.has_ime ui.selected ui.clipboard ui.set_clipboard ui.menu ui.menu_pick ui.menu_close ui.menu_open ui.pick_file ui.pick_save ui.to_png ui.font ui.font_builtin ui.font_name ui.run ui.label ui.button ui.checkbox ui.radio ui.slider ui.drag ui.number ui.field ui.textarea ui.password ui.combo ui.listbox ui.tabs ui.space ui.spacer ui.col ui.row ui.grid ui.center ui.stack ui.scroll ui.color ui.tree ui.image ui.point_x ui.point_y ui.divider ui.show ui.edited ui.has_action ui.action ui.value ui.text_value ui.float_value ui.chosen ui.theme Canvas.width Canvas.height Canvas.get Canvas.set Canvas.clear Canvas.hline Canvas.vline Canvas.line Canvas.rect Canvas.fill_rect Canvas.circle Canvas.fill_circle Canvas.blit Canvas.text Canvas.draw Canvas.tri Canvas.clear_depth Canvas.clip Canvas.clip_off Canvas.to_png Widget.color Widget.background Widget.padding Widget.width Widget.height Widget.align Widget.valign Widget.border Widget.radius Widget.decimals Widget.filter Widget.tooltip Widget.placeholder Widget.font Widget.disabled

関数

ui.open(width: int, height: int) → void

描画サーフェスおよびウィンドウを初期化する。解像度はピクセル単位で指定する。

GUI 環境ではネイティブウィンドウも同時に開きます。GUI 非対応環境やパイプ出力時(SSH 接続先など)では、**オフスクリーンサーフェス**として初期化されます(ui.visible() が false)。どちらの環境でも描画 API の動作は同一です。

既にオープンしている場合は、一度破棄してから再生成します。

ウィンドウ対応環境では、ウィンドウ境界をドラッグしてリサイズ可能。リサイズ時は次回の ui.poll() でサーフェスも追従して再生成され、ui.width() / ui.height() が新しい解像度を返します。 resizable に false を渡すと固定サイズのウィンドウとなります(ウィンドウ非対応環境では初期サイズのまま固定)。

オーバーロード: ui.open(title: string, width: int, height: int) -> void ui.open(title: string, width: int, height: int, resizable: bool) -> void

widthint水平解像度(ピクセル数: 1〜16384)
heightint垂直解像度(ピクセル数: 1〜16384)
ui.open(64, 32);
print(f"{ui.width()}x{ui.height()}");   // 64x32
ui.close();
ui.close() → void

描画サーフェスを解放し、開いているウィンドウを閉じます。

ui.open(8, 8);
ui.close();
print(ui.width());   // 0
ui.width() → int

描画サーフェスの幅(水平ピクセル数)。未オープンの場合は 0。

ui.open(120, 80);
print(ui.width());   // 120
ui.height() → int

描画サーフェスの高さ(垂直ピクセル数)。未オープンの場合は 0。

ui.open(120, 80);
print(ui.height());   // 80
ui.scale() → int

ディスプレイのスケーリング係数(整数)。標準ディスプレイでは 1、HiDPI/Retina ディスプレイでは 2 など。

**ui.open() 呼び出し前に取得可能。**HiDPI 環境では解像度をスケール倍してサーフェスを確保することで、物理ピクセルと 1:1 に対応し、鮮明な文字・グラフィックス描画が得られます。

整数丸めされた値を返します。Windows の 125% 表示(DPI 120)やブラウザ拡大など、小数スケール(1.25, 1.5 等)に対応するには ui.pixel_ratio() を使用してください。

var k = ui.scale();
ui.open("さめ", 420 * k, 300 * k);   // 見た目は 420×300 のまま、中身は高解像度に
_ = ui.font(12 * k);                 // 12pt 相当
print(ui.width() == 420 * k);        // true
ui.pixel_ratio() → float

ディスプレイのデバイスピクセル比(DPI スケール)。浮動小数点数(1.0, 1.25, 1.5, 2.0 等)を返します。

ui.scale() と異なり丸めを行いません。 **サーフェスのピクセルを物理ディスプレイのピクセルと完全一致させたい場合は、この値に基づいて解像度を決定します。** 丸めた値で作成すると、例えばスケール 1.5 の画面に 2 倍のサーフェスを割り当てることになり、 OS 側でリサンプリング(縮小表示)が発生して文字や線がぼやける原因となります。

ui.open() 呼び出し前に取得可能です。ヘッドレス環境では 1.0 を返します。

var r = ui.pixel_ratio();
var w = int(420.0 * r + 0.5);        // 論理幅 420 に対する物理ピクセル数
ui.open("さめ", w, int(300.0 * r + 0.5));
_ = ui.font(int(12.0 * r + 0.5));    // 12pt 相当
print(r >= 1.0);                     // true
ui.visible() → bool

ウィンドウ(画面表示)が利用可能かどうかを返します。

GUI 非対応環境やヘッドレスモード(パイプ出力、SHARK_UI=off 等)では false になります。オフスクリーン描画は継続して可能なため、通常は意識する必要はありません。画面非表示時のみ別処理(結果を PNG 保存するなど)を行いたい場合に利用します。

ui.open(8, 8);
if !ui.visible() { print("画面がありません"); }   // 画面がありません
ui.present() → void

バックバッファの描画内容をフロント(画面)に転送・表示(フリップ)します。

1フレームの描画処理が完了したタイミングで呼び出します。ヘッドレス環境では何もしません。

ui.open(16, 16);
ui.clear(ui.rgb(0, 0, 0));
ui.present();
ui.frame() → void

次のフレーム時刻まで待機する。メインループの末尾で呼び出す。

**`sleep()` で待機しないこと。**「1フレーム描画してから 16ms スリープする」と記述すると、描画にかかった処理時間分だけ遅延が蓄積し、目標フレームレート(60fps = 16.7ms など)を維持できません。本関数は指定されたフレームレートに基づき**目標時刻を計算して差分時間だけ待機**するため、描画処理が速い環境でも遅い環境でも一定のペースを保ちます。

**特にブラウザ環境(WebAssembly)で重要です。**ブラウザは requestAnimationFrame(通常 60Hz)で実行タイミングを管理しており、フレーム境界直後に起床すると次の描画フレームを丸ごと待たされることになり、 `sleep(0.016)` と書くだけで**フレームレートが半減(30fps)**します。

処理落ちが発生した場合は目標時刻を現在時刻にリセットするため、遅延が累積することはありません。

オーバーロード: ui.frame(fps: int) -> void

ui.open(8, 8);
var n = 0;
while ui.poll() && n < 3 {
  ui.clear(ui.rgb(0, 0, 0));
  ui.present();
  ui.frame(120);        // 120 のつもりで進める
  n += 1;
}
print(n);   // 3
ui.rgb(r: int, g: int, b: int) → int

赤・緑・青(それぞれ 0〜255)から色を作る。

色は int 1つで表す。上から順に赤・緑・青の 8 ビットずつで、 16 進で書けば 0xRRGGBB と同じ。0〜255 の外は端で止める。

rint赤(0〜255)
gint緑(0〜255)
bint青(0〜255)
print(ui.rgb(255, 0, 0));   // 16711680
ui.rgba(r: int, g: int, b: int, a: int) → int

赤・緑・青に「濃さ(不透明のぶん)」を足して色を作る。

a は 255 で不透明、0 でまるごと透明、途中なら半透明。半透明の色で描くと、下にあるものが透けて見える。

ui.rgb(r, g, b) は ui.rgba(r, g, b, 255) と同じ色になる。 **今までどおり描くぶんには、何も変わらない。**

rint
gint
bint
aint濃さ(0〜255)。255 が不透明
ui.open(4, 4);
ui.clear(ui.rgb(0, 0, 0));
ui.fill_rect(0, 0, 4, 4, ui.rgba(255, 255, 255, 128));   // 半分だけ白
print(ui.red(ui.get(1, 1)));   // 128
ui.alpha(color: int) → int

色の濃さ(0〜255)。255 なら不透明、0 ならまるごと透明。

print(ui.alpha(ui.rgb(10, 20, 30)));         // 255
print(ui.alpha(ui.rgba(10, 20, 30, 128)));   // 128
ui.red(color: int) → int

色の赤のぶん(0〜255)。

print(ui.red(ui.rgb(10, 20, 30)));   // 10
ui.green(color: int) → int

色の緑のぶん(0〜255)。

print(ui.green(ui.rgb(10, 20, 30)));   // 20
ui.blue(color: int) → int

色の青のぶん(0〜255)。

print(ui.blue(ui.rgb(10, 20, 30)));   // 30
ui.mix(a: int, b: int, t: float) → int

2つの色を混ぜる。t が 0.0 なら a、1.0 なら b。

aint0.0 のときの色
bint1.0 のときの色
tfloat混ぜ具合(0.0〜1.0)
print(ui.mix(ui.rgb(0, 0, 0), ui.rgb(100, 100, 100), 0.5));   // 3289650
ui.clear(color: int) → void

サーフェス全体を指定色で塗りつぶす。クリッピング領域(ui.clip)が設定されている場合はその内部のみをクリアする。

ui.open(8, 8);
ui.clear(ui.rgb(0, 0, 255));
print(ui.get(4, 4));   // 255
ui.set(x: int, y: int, color: int) → void

指定座標 (x, y) にピクセルを描画する。サーフェス範囲外への描画は無視される。

ui.open(8, 8);
ui.set(2, 3, ui.rgb(255, 0, 0));
print(ui.get(2, 3));   // 16711680
ui.get(x: int, y: int) → int

指定座標 (x, y) のピクセル色を取得する。サーフェス範囲外は 0(黒)を返す。

描画結果の検証やテスト等に利用できる。

ui.open(8, 8);
ui.clear(ui.rgb(1, 2, 3));
print(ui.get(0, 0));    // 66051
print(ui.get(99, 0));   // 0
ui.hline(x: int, y: int, w: int, color: int) → void

横の線。x から右へ w 画素。

ui.open(8, 8);
ui.hline(0, 4, 8, ui.rgb(0, 255, 0));
print(ui.get(7, 4));   // 65280
ui.vline(x: int, y: int, h: int, color: int) → void

縦の線。y から下へ h 画素。

ui.open(8, 8);
ui.vline(4, 0, 8, ui.rgb(0, 255, 0));
print(ui.get(4, 7));   // 65280
ui.line(x0: int, y0: int, x1: int, y1: int, color: int) → void

2点を結ぶ線。斜めにも引ける。

ui.open(8, 8);
ui.line(0, 0, 7, 7, ui.rgb(255, 255, 255));
print(ui.get(3, 3));   // 16777215
ui.rect(x: int, y: int, w: int, h: int, color: int) → void

四角の枠。左上が (x, y) で、横 w・縦 h。

ui.open(8, 8);
ui.rect(1, 1, 6, 6, ui.rgb(255, 0, 0));
print(ui.get(1, 1) != 0);   // true
print(ui.get(3, 3) != 0);   // false
ui.fill_rect(x: int, y: int, w: int, h: int, color: int) → void

中まで塗った四角。

ui.open(8, 8);
ui.fill_rect(1, 1, 6, 6, ui.rgb(255, 0, 0));
print(ui.get(3, 3) != 0);   // true
ui.circle(x: int, y: int, r: int, color: int) → void

円の輪郭。中心が (x, y) で半径 r。

ui.open(16, 16);
ui.circle(8, 8, 5, ui.rgb(255, 255, 0));
print(ui.get(8, 3) != 0);   // true
ui.fill_circle(x: int, y: int, r: int, color: int) → void

中まで塗った円。

ui.open(16, 16);
ui.fill_circle(8, 8, 5, ui.rgb(255, 255, 0));
print(ui.get(8, 8) != 0);   // true
ui.clip(x: int, y: int, w: int, h: int) → void

クリッピング矩形を設定し、指定領域内のみに描画を制限する。

矩形外への描画は破棄される。スクロールコンテナやリストの描画等で利用する。解除するには ui.clip_off() を呼び出す。

ui.open(16, 16);
ui.clip(0, 0, 8, 8);
ui.clear(ui.rgb(255, 0, 0));
print(ui.get(4, 4) != 0);    // true
print(ui.get(12, 12) != 0);  // false
ui.clip_off() → void

クリッピング矩形を解除し、サーフェス全体を描画対象に戻す。

ui.open(16, 16);
ui.clip(0, 0, 4, 4);
ui.clip_off();
ui.clear(ui.rgb(255, 0, 0));
print(ui.get(12, 12) != 0);   // true
ui.blit(x: int, y: int, w: int, pixels: list<int>) → void

ピクセル配列をサーフェスに転送(Blit)する。

pixels は左上から行優先(Row-major)の順序で指定し、w 個ごとに次の行へ折り返す。高さは配列長と w から自動計算される。

xint
yint
wint転送元データの幅(1以上)
pixelslist<int>ピクセルカラー配列
ui.open(8, 8);
var dot = [ui.rgb(255, 0, 0), ui.rgb(0, 255, 0),
           ui.rgb(0, 0, 255), ui.rgb(255, 255, 255)];
ui.blit(2, 2, 2, dot);
print(ui.get(3, 3));   // 16777215
ui.canvas(width: int, height: int) → Canvas

オフスクリーン Canvas(描画バッファ)を生成する。背景色を省略した場合は完全透明(RGBA=0)で初期化される。

ウィンドウ画面のほかにも独立した描画サーフェスを保持できます。主な用途:

・画像ファイルの読み込みと描画(ui.load_png)・事前描画によるキャッシュと高速描画(スプライト・タイルマップなど)・3D ラスタライズのレンダリングターゲット(canvas.tri と ui.depth)

Canvas は**値(Value semantics)**として扱われ、代入時は参照共有されます。変更(書き込み)時に初めて内部バッファがコピーされる(Copy-on-Write)ため、編集前のスナップショット保存(Undo/Redo 等)を低コストで行えます。

var backup = image; // 参照のみ共有(コピーは発生しない) image.clear(0); // 変更時に自動複製。backup は変更前の状態を維持

描画メソッドは ui モジュールと共通のインターフェースを持ちます(image.line など)。

オーバーロード: ui.canvas(width: int, height: int, color: int) -> Canvas

var e = ui.canvas(4, 4, ui.rgb(0, 0, 255));
print(f"{e.width()}x{e.height()} {e.get(2, 2)}");   // 4x4 255
ui.load_png(data: bytes) → Canvas?

PNG 画像データをデコードして Canvas を生成する。デコード失敗時は none。

アルファチャンネル(透明度)も保持されます。モノクロ・インデックスカラー(パレット)・RGB・RGBA、ビット深度 1/2/4/8/16 ビットに対応しています(インターレース形式の PNG には非対応)。

PNG エンコードは Canvas.to_png() で行います。デコード → 編集 → エンコードを行っても透明度は失われません。

var e = ui.canvas(2, 2, ui.rgba(255, 0, 0, 128));
if var back = ui.load_png(e.to_png()) {
  print(f"{back.width()} {ui.alpha(back.get(0, 0))}");   // 2 128
}
ui.draw(image: Canvas, x: int, y: int) → void

Canvas の画像を現在のサーフェスに描画する。透明部分は下地とアルファブレンドされる。

幅と高さを指定すると、そのサイズにスケーリング描画(最近傍補間)されます。ドット絵などを拡大表示しても輪郭がぼやけません。

オーバーロード: ui.draw(image: Canvas, x: int, y: int, w: int, h: int) -> void

imageCanvas描画する Canvas
xint
yint
var e = ui.canvas(2, 2, ui.rgb(0, 255, 0));
ui.open(8, 8);
ui.draw(e, 1, 1);
print(ui.get(2, 2));   // 65280
ui.tri(x0: int, y0: int, z0: int, x1: int, y1: int, z1: int, x2: int, y2: int, z2: int, color: int) → void

三角形を単色で塗りつぶす(ラスタライズ)。各頂点はスクリーン座標 (x, y) および深度値 (z)。

**z 値は小さいほど手前**を表します。`ui.depth(true)` が有効な場合、ピクセル単位で Z テスト(深度比較)を行い、奥にあるピクセルはカリング(描画スキップ)されます。

回転や透視投影などの 3D 幾何変換は Shark 側(スクリプト側)で計算し、本関数には投影変換後の**スクリーン座標**を渡します。

バックフェイスカリング(裏面カリング)は行いません(時計回り・反時計回りの両方をラスタライズします)。カリングが必要な場合は、3頂点の外積などからアプリケーション側で判定してください。

ui.open(8, 8);
ui.tri(0, 0, 0, 7, 0, 0, 0, 7, 0, ui.rgb(255, 0, 0));
print(f"{ui.get(1, 1)} {ui.get(6, 6)}");   // 16711680 0
ui.depth(on: bool) → void

Z バッファ(深度バッファ)の有効/無効を切り替える。

true に設定すると、ui.tri() 描画時に Z テストおよび Z 値の更新が行われます。 false に設定すると Z バッファは無効化されメモリが解放されます。サーフェス解像度に合わせてバッファが自動管理されます。

ui.open(8, 8);
ui.depth(true);
ui.clear_depth();
ui.tri(0, 0, 50, 7, 0, 50, 0, 7, 50, ui.rgb(0, 255, 0));   // 奥
ui.tri(0, 0, 10, 7, 0, 10, 0, 7, 10, ui.rgb(255, 0, 0));   // 手前
print(ui.get(1, 1));   // 16711680
ui.depth(false);
ui.clear_depth() → void

Z バッファ(深度バッファ)を初期値(最遠)にクリアする。毎フレームの描画開始時に呼び出す。

ui.depth(false) のときは何もしません。

ui.open(8, 8);
ui.depth(true);
ui.clear_depth();
print(ui.width());   // 8
ui.depth(false);
ui.text(x: int, y: int, s: string, color: int) → void

文字を描く。左上が (x, y)。

内蔵の 5×7 ビットマップフォント(文字枠は 6×8 ドット)を使用する。 scale を渡すと、その倍率で整数倍拡大する。改行(\n)で次の行に移る。

内蔵フォントは ASCII のみ対応しており、日本語等の全角文字は □(未定義文字)になる。 ui.font() でアウトラインフォントを読み込むと、日本語も描画できる。

オーバーロード: ui.text(x: int, y: int, s: string, color: int, scale: int) -> void

xint
yint
sstring描く文字
colorint色
ui.open(64, 16);
ui.text(1, 1, "Hi", ui.rgb(255, 255, 255));
print(ui.get(1, 1));   // 16777215
ui.text_width(s: string) → int

その文字を描いたときの横幅(画素)。

真ん中に置きたいときに使う。改行があれば、いちばん長い行の幅。 ui.font() でフォントを読んでいれば、その字幅で数える。

オーバーロード: ui.text_width(s: string, scale: int) -> int

ui.font_builtin();
print(ui.text_width("Hi"));      // 12
print(ui.text_width("Hi", 2));   // 24
ui.text_height() → int

1行ぶんの高さ(画素)。**字の上端から下端まで**で、上下にあきを含まない。

部品の高さもこれで決まる。改行(\n)のときは、これより少し広く下げる。

オーバーロード: ui.text_height(scale: int) -> int

ui.font_builtin();
print(ui.text_height());    // 8
print(ui.text_height(3));   // 24
ui.poll() → bool

OS/ウィンドウシステムのイベントキューをポーリングし、入力状態を更新する。終了要求時は false を返す。

メインループの先頭で1フレームにつき1回呼び出します。ui.key() や ui.mouse_x() などの入力取得関数は、直近の ui.poll() 時点のスナップショットを返します。

ヘッドレス環境ではイベントが発生しないため常に true を返し続けます。

ui.open(8, 8);
var n = 0;
while ui.poll() && n < 3 { n += 1; }
print(n);   // 3
ui.quit() → void

アプリケーションの終了フラグをセットする。次回の ui.poll() 呼び出しが false を返します。

ui.open(8, 8);
ui.quit();
print(ui.poll());   // false
ui.key(name: string) → bool

指定されたキーが現在押下状態(押し下げ中)にあるかを返す。

キー名は印字可能文字ならその文字("a", "1", "-" 等。大文字・小文字は区別しません)。特殊キーは以下の識別名を使用します:

"left" "right" "up" "down" "space" "enter" "esc" "tab" "back" "delete" "home" "end" "pageup" "pagedown" "shift" "ctrl" "alt" "f1"〜"f12"

キー離下(Key Up)イベントが制限される環境では、押下フレーム間のみ true となる場合があります。

押しっぱなしによる移動などを正確に追跡したい場合は、ui.pressed() で状態を保持する実装を推奨します(spec/library/ui.md 参照)。

ui.open(8, 8);
_ = ui.poll();
print(ui.key("right"));   // false
ui.pressed(name: string) → bool

指定されたキーが現在のフレームで新たに押された(押下エッジ)かを返す。押し続けても初回のみ true。

ui.open(8, 8);
_ = ui.poll();
print(ui.pressed("space"));   // false
ui.released(name: string) → bool

指定されたキーが現在のフレームで離された(離下エッジ)かを返す。

ui.open(8, 8);
_ = ui.poll();
print(ui.released("space"));   // false
ui.typed() → string

現在のフレームで入力(タイピング)された確定文字を返す。入力がない場合は空文字列。

キー識別名ではなく確定された文字コードが返るため、テキストフィールド等の入力処理に利用します。

ui.open(8, 8);
_ = ui.poll();
print(f"[{ui.typed()}]");   // []
ui.mouse_x() → int

マウスカーソルのX座標(描画サーフェス内のピクセル座標)。

ui.open(8, 8);
_ = ui.poll();
print(ui.mouse_x());   // 0
ui.mouse_y() → int

マウスカーソルのY座標(描画サーフェス内のピクセル座標)。

ui.open(8, 8);
_ = ui.poll();
print(ui.mouse_y());   // 0
ui.mouse(button: int) → bool

指定されたマウスボタンが現在押下状態にあるかを返す。0: 左ボタン、1: 中ボタン(ホイール)、2: 右ボタン。

ui.open(8, 8);
_ = ui.poll();
print(ui.mouse(0));   // false
ui.clicked(button: int) → bool

指定されたマウスボタンが現在のフレームでクリック(押下)された瞬間であるかを返す。

ウィジェットのヒットテストと組み合わせてクリック判定を行います。

ui.open(16, 16);
_ = ui.poll();
if ui.clicked(0) && ui.mouse_x() < 8 { print("左半分が押された"); }
ui.wheel() → int

マウスホイールの垂直スクロール量。行数単位で、**下スクロールが正**の値。

内部でスクロール処理を持つウィジェット(ui.listbox, ui.textarea, ui.combo 等)上にマウスカーソルがある場合はウィジェット側でイベントが消費され、この関数は 0 を返します。これにより親のスクロールビュー等との多重スクロールを防止します。

トラックパッド等からのサブピクセルスクロールは累積され、1行分に達した段階で返されます。ホイール入力が存在しない環境では常に 0 となります。

ui.open(80, 40);
_ = ui.poll();
print(ui.wheel());   // 0
ui.wheel_x() → int

マウスホイールの水平スクロール量。行数単位で、**右スクロールが正**の値。

トラックパッドの横スワイプや、チルトホイール・水平スクロール対応マウスからの入力を取得します。

ui.open(80, 40);
_ = ui.poll();
print(ui.wheel_x());   // 0
ui.cursor(name: string) → void

マウスの形を決める。押せるところに合わせたら手にする、など。

**その回かぎり**で、次の ui.poll() で `arrow`(ふつう)に戻る。描き直すたびに「いまその場所にふさわしい形」を頼む、という使い方をする。宣言的な層(ui.show / ui.run)は、ボタン・つまみ・入力欄の上で自分で形を変えるので、呼ぶ必要は無い。

使える名前: arrow ふつう hand 押せるところ text 文字を打つところ cross 照準 wait 待たせているところ resize_x 横に伸ばす resize_y 縦に伸ばす move つかんで動かす none 消す(自分でマウスを描くとき)

持っていない形は、その機種で近いものになる。形を持たない出し先や、画面が無いときは何も起きない。

ui.open(60, 30);
_ = ui.poll();
if ui.mouse_x() < 30 { ui.cursor("hand"); }   // 左半分は押せる、という見せ方
ui.present();
print(ui.width());   // 60
ui.input(x: int, y: int, h: int, value: string) → string

その場所で文字を受け取る。日本語などの変換(IME)も通る。

自分で入力欄を描くとき用(ui.field は中でこれを使っている)。 **受け付けているあいだ、毎回呼ぶ。**呼ぶのをやめるか ui.input_off() を呼ぶと止まる。

返るのは**確定した中身**。変換中の文字は ui.marked() で別に取れるので、確定ぶんの後ろに下線つきで出す。

xint
yint
hint
valuestringいまの中身(呼んだ側が持っているもの)

変換を機種に任せられるかは出し先による(ui.has_ime())。

任せられないときは、打たれた文字がそのまま入り、back で1文字消える。

変換中のキー(確定の enter、取り消しの esc、候補を選ぶ空白と矢印)は、

プログラムには届かない。ui.pressed("enter") などで受けているものと

ぶつからないようにするため。

ui.open(120, 40);
var name = "さめ";
_ = ui.poll();
name = ui.input(4, 4, 16, name);
print(name);   // さめ
ui.input_off() → void

文字の受け付けをやめる。

ui.open(80, 40);
ui.input_off();
print(ui.marked());   //
ui.marked() → string

変換中(未確定)の文字列。無ければ空。

確定した文字の後ろに、下線を引いて出す。

ui.open(80, 40);
print(f"[{ui.marked()}]");   // []
ui.has_ime() → bool

変換つきの文字入力を、出し先が持っているか。

macOS ウィンドウおよびブラウザ環境では true。それ以外の環境や画面なしの場合は false となり、入力された文字が直接渡される。

ui.open(80, 40);
if !ui.has_ime() { print("変換は出し先に任せられません"); }
// 変換は出し先に任せられません
ui.selected() → string

いま選んでいる文字。選んでいなければ空。

入力欄(ui.field)や ui.input() で選んでいるところ。

ui.open(120, 40);
_ = ui.poll();
print(f"[{ui.selected()}]");   // []
ui.clipboard() → string

切り貼りの置き場(クリップボード)の中身。

持っていない出し先では空が返る。

ui.open(80, 40);
var s = ui.clipboard();
print(s.len() >= 0);   // true
ui.set_clipboard(s: string) → bool

切り貼りの置き場に入れる。入れられなければ false。

ui.open(80, 40);
if ui.set_clipboard("さめ") {
  print(ui.clipboard());   // さめ
}
ui.menu(x: int, y: int, items: list<string>) → void

この場所にメニューを出す。

選ばれるか、外を押されるまで出たまま。押されたところはメニューが先に受け取るので、下にある部品は反応しない。番号は ui.menu_pick() で受け取る。

**押したまま動かして、選びたいところで離す**でも選べる。面に入りきらないときは、上と下に送るしるし(▲▼)が出て、そこに合わせているあいだ少しずつ送れる。

入力欄(ui.field)は、右で押すと**切り貼りのメニューを自分で出す**ので、これを書く必要はない。

xint
yint
itemslist<string>並べる文字
ui.open(120, 80);
if ui.clicked(2) { ui.menu(ui.mouse_x(), ui.mouse_y(), ["消す", "やり直す"]); }
print(ui.menu_open());   // false
ui.menu_pick() → int

この回に選ばれたメニューの番号。選ばれていなければ -1。

ui.open(120, 80);
ui.menu(4, 4, ["消す", "やり直す"]);
var pick = ui.menu_pick();
if pick == 0 { print("消す"); }
print(pick);   // -1
ui.menu_close() → void

メニューを閉じる。

ui.open(120, 80);
ui.menu(4, 4, ["消す"]);
ui.menu_close();
print(ui.menu_open());   // false
ui.menu_open() → bool

メニューが出ているか。

ui.open(120, 80);
ui.menu(4, 4, ["消す"]);
print(ui.menu_open());   // true
ui.pick_file() → string?

ネイティブファイル選択ダイアログ(開く)を表示し、選択されたファイルパスを返す。

キャンセル時やダイアログ非対応のプラットフォームでは「値なし(none)」。取得したパスは std.file 等の標準関数でそのまま利用できる。

**ダイアログ表示中はプログラムの実行がブロック(一時停止)する**。ダイアログのサポート状況は環境による。macOS・Windows は OS ネイティブダイアログを表示し、 Linux ではデスクトップ環境のダイアログ(zenity または kdialog)を利用する。ブラウザ環境やヘッドレスモード(SHARK_UI=off)では常に none を返す。

オーバーロード: ui.pick_file(title: string) -> string?

ui.open(40, 20);
var got = ui.pick_file() ?? "";
print(f"[{got}]");   // [](画面が無いので出せない)
ui.pick_save() → string?

ネイティブファイル保存ダイアログを表示し、選択された保存先パスを返す。

ui.pick_file と同様だが、**引数1つの場合はタイトルではなく「デフォルトファイル名」**となる。タイトルも指定する場合は2引数で渡す。既存ファイル選択時は OS が上書き確認を行う。

オーバーロード: ui.pick_save(name: string) -> string? ui.pick_save(title: string, name: string) -> string?

ui.open(40, 20);
var where = ui.pick_save("memo.txt") ?? "";
print(f"[{where}]");   // [](画面が無いので出せない)
ui.to_png() → bytes

今の面を PNG にして返す。

画面が無いところでも、描いた結果をそのまま渡せる。縮めずに入れるので、同じ絵の PNG より少し大きい。

ui.open(4, 4);
ui.clear(ui.rgb(255, 0, 0));
var png = ui.to_png();
print(png.len() > 0);                            // true
print(png.to_hex().starts_with("89504e47"));     // true
ui.font(size: int) → bool

システムフォントに切り替えて、日本語等のアウトラインフォントを描画可能にする。

明示的に呼び出さない場合は**内蔵の 5×7 フォント**が維持される。内蔵フォントは ASCII のみだが、全環境で同一サイズ・同一グリフで描画される。システムフォントに切り替えると日本語を描画できるが、文字のメトリクス(幅や高さ)はロードされたフォントに依存する。

フォントの探索・ラスタライズは以下の2つの方法を順に試行する。どちらも利用できない場合は false を返し、内蔵ビットマップフォントのまま動作を継続する。

1. FreeType によるフォントファイルの直接読み込み(FreeType 有効ビルド時。設定方法は README の「日本語フォントの表示」参照) 2. プラットフォーム API によるラスタライズ(ブラウザ環境はこちら。不可視 Canvas に文字を描画してアルファビットマップを抽出)

大きさは**画素**で渡す。画面が細かいところでも同じ見た目にするなら、 ui.scale() を掛ける(`ui.font(12 * ui.scale())` で 12pt くらい)。

探すのは本文の太さ(Regular くらい)のもの。別の太さにしたいときは、そのフォントの場所を渡す。

オーバーロード: ui.font(path: string, size: int) -> bool ui.font(data: bytes, size: int) -> bool

sizeint字の大きさ(画素)
ui.open(200, 60);
if ui.font(16) {
  ui.text(4, 4, "こんにちは", ui.rgb(255, 255, 255));
}
print(ui.width());   // 200
ui.font_builtin() → void

内蔵の 5×7 フォントに戻す。

ui.open(80, 40);
ui.font_builtin();
print(ui.text_height());   // 8
ui.font_name() → string

現在ロードされているフォントのパス。ブラウザ環境ではフォントファミリ名(例: `Hiragino Sans`)。内蔵フォント使用時は空文字列。

ui.open(80, 40);
ui.font_builtin();
print(f"[{ui.font_name()}]");   // []
ui.run(title: string, width: int, height: int, view: func() -> Widget, update: func(string) -> void) → void

宣言的 UI のエントリポイント。**イベントループと画面の再描画を統合管理する。**

アプリケーション側は view() を定義するだけで画面を構築できる。状態は通常の変数で管理し、各ウィジェットには **ref でバインド**する。操作時に変数が直接更新されるため、update 関数やウィジェットID、ui.value() による値取得は不要。

view() 現在の状態に応じたウィジェットツリー(Widget)を構築して返却 update(hit) 押下されたウィジェットの ID を受け取って状態を更新するハンドラ(**省略可能**)

内部の処理フロー:

1. ウィンドウおよびサーフェスの初期化(HiDPI スケールおよびフォントを自動ロード) 2. ユーザー入力イベントのポーリング処理 3. view() を呼び出してウィジェットレイアウトおよび描画を実行 4. イベント発生時は update() またはアクションコールバックを呼び出し、**即時再描画** 5. ウィンドウが閉じられるか ui.quit() が呼ばれるまで 2 へ戻る

変数を ref でバインドするか、ウィジェットに個別アクション関数を指定している場合は update は不要(省略可能)。

オーバーロード: ui.run(title: string, width: int, height: int, view: func() -> Widget, update: func(string) -> void, resizable: bool) -> void ui.run(title: string, width: int, height: int, view: func() -> Widget) -> void ui.run(title: string, width: int, height: int, view: func() -> Widget, resizable: bool) -> void

titlestringウィンドウのタイトル
widthint
heightint
viewfunc() -> Widget, update: func(string) -> void現在の UI 構造を返す関数

画面が無いところ(SHARK_UI=off など)では、1回だけ描いて返る。

面はそのままなので、ui.get() や ui.to_png() で結果を見られる。

字の大きさを自分で決めたいときは、呼ぶ前に ui.font() を呼んでおく。

var count = 0;
var sound = true;
func view() -> Widget {
  return ui.col([
    ui.label(f"{count} 回"),
    ui.button("ふやす", func() -> void { count += 1; }),
    ui.checkbox("音を出す", ref sound),   // 押されたら sound が直に変わる
  ]);
}
ui.run("かうんた", 200, 120, view);       // update は要らない
print(f"{count} {sound}");   // 0 true
ui.label(text: string) → Widget

ラベル。文字を出す部品。

部品は `Widget` という値で、作っただけでは何も起きない。 ui.show() に渡すと、そこで初めて描かれる。並べるときは ui.col / ui.row / ui.grid に配列で入れて、1つにする。

長い文が置ける幅(親がくれた幅か、.width(n) で決めた幅)に入らないときは、 **折り返して**何行かになる。折り目はその行の空白、無ければ字の手前。

ui.open(80, 40);
_ = ui.show(ui.label("こんにちは"));
print(ui.get(5, 4) != 0);   // true
ui.button(label: string, id: string) → Widget

ボタンウィジェット。クリック可能。

クリックイベントの処理方法は2通りある。**コールバック関数を渡すとクリック時に実行される。** ウィジェットID(`id`)を渡すと、ui.show() がその ID 文字列を返却する。

``` ui.button("カウントアップ", func() -> void { n += 1; }) // 無名関数(ラムダ式) ui.button("カウントアップ", inc) // 名前付き関数 ui.button("カウントアップ", "inc") // ui.show() が "inc" を返却 ```

オーバーロード: ui.button(label: string, action: func() -> void) -> Widget ui.button(child: Widget, id: string) -> Widget ui.button(child: Widget, action: func() -> void) -> Widget

labelstring表示ラベル
idstringウィジェットID(関数の代わりに指定)
ui.open(160, 60);
_ = ui.show(ui.button(ui.label("なかみ"), "b"), 0, 0);
print(ui.get(2, 2) != 0);   // true
ui.checkbox(label: string, id: string, on: bool) → Widget

チェックボックスウィジェット。真偽値のトグル切り替え。

**変数を ref でバインドするのが最も簡潔である。**クリックされるたびに変数の真偽値が直接反転する。ウィジェットID や update()、ui.value() は不要。

``` ui.checkbox("音声出力", ref sound) // sound が直接トグル切り替え ui.checkbox("音声出力", "sound", sound) // ui.show() が "sound" を返却 ```

ウィジェットID の代わりに関数を渡すと、クリック時にその関数が呼ばれる(新しい真偽値は関数内で ui.value() から取得)。

オーバーロード: ui.checkbox(label: string, action: func() -> void, on: bool) -> Widget ui.checkbox(label: string, ref on: bool) -> Widget

labelstring表示ラベル
idstringウィジェットID
onbool現在のチェック状態(ref 指定時はバインド先変数)

ref で渡せるのは**一番外側の var** だけ(誤り E0307)。ui.field と同じ理由で、

覚えているのは借用ではなく「どの var か」だから。

ui.open(80, 40);
var on = true;
_ = ui.show(ui.checkbox("音を出す", ref on));
print(on);   // true(まだ押していない)
ui.radio(label: string, id: string, on: bool) → Widget

ラジオボタン。丸いほうのチェックで、**いくつか並べて、そのうち1つを選ぶ**。

**同じ変数を ref で渡し、それぞれに「自分の数」を持たせるのがいちばん短い。** 押されると、その数が変数に入る。選ばれて見えるのは、変数と自分の数が同じとき。

``` ui.row([ui.radio("小", ref mode, 0), ui.radio("中", ref mode, 1), ui.radio("大", ref mode, 2)]) ```

ウィジェットID を指定する形式もある。その場合は選択状態を bool で渡し、押下時に ui.show() がウィジェットID を返却する。

オーバーロード: ui.radio(label: string, action: func() -> void, on: bool) -> Widget ui.radio(label: string, ref value: int, mine: int) -> Widget

labelstring表示ラベル
idstringウィジェットID(ref の代わりに指定)
onbool現在の選択状態(ID 形式の場合に指定)
ui.open(160, 40);
var mode = 1;
_ = ui.show(ui.row([ui.radio("小", ref mode, 0), ui.radio("大", ref mode, 1)]));
print(mode);   // 1(まだ押していない)
ui.slider(id: string, value: int, lo: int, hi: int) → Widget

スライダーウィジェット。lo から hi までの数値範囲をドラッグで調整する。

**変数を ref でバインドするのが最も簡潔である。**操作するたびに変数が直接更新される。

``` ui.slider(ref vol, 0, 100) // vol が直接更新される ui.slider("vol", vol, 0, 100) // ui.show() が "vol" を返却 ```

**ポインタキャプチャ機能。**ドラッグ開始後はマウスカーソルがスライダーの領域外に出ても追跡が継続するため、快適に微調整が可能。

ウィジェットID の代わりに関数を渡すと、操作中にその関数が継続的に呼び出される(新しい値は関数内で ui.value() から取得)。

**実数(float)にも対応。**値および範囲に float を渡すと浮動小数点スライダーになり、新しい値は ui.float_value() で取得する(ref バインディング時は変数が直接更新される)。

オーバーロード: ui.slider(action: func() -> void, value: int, lo: int, hi: int) -> Widget ui.slider(ref value: int, lo: int, hi: int) -> Widget ui.slider(id: string, value: float, lo: float, hi: float) -> Widget ui.slider(action: func() -> void, value: float, lo: float, hi: float) -> Widget ui.slider(ref value: float, lo: float, hi: float) -> Widget

idstringウィジェットID(ref の代わりに指定)
valueint現在の値(ref 指定時はバインド先変数)
loint
hiint
ui.open(120, 40);
var vol = 40;
_ = ui.show(ui.slider(ref vol, 0, 100));
print(vol);   // 40(まだ動かしていない)
var mix = 0.25;
_ = ui.show(ui.slider(ref mix, 0.0, 1.0));
print(mix);   // 0.25
ui.drag(id: string, value: int, lo: int, hi: int) → Widget

ドラッグ入力ウィジェット(ドラッグ&ダイレクト数値入力)。**ドラッグで数値を増減し、クリックで直接テキスト編集モードに入る。**

スライダーよりも専有面積が小さいため、パラメータの多いインスペクター(座標・色・サイズ等)に適している。バーの背景には範囲に対する現在値の割合がインジケーターとして表示される。

**int および float の両方に対応。**float 指定時は ui.float_value() で値を取得し、表示桁数は `.decimals(n)` で指定可能(未指定時は数値範囲に応じて自動決定)。

フルスケールは横 200 ピクセルのドラッグ移動に相当。lo から hi の範囲制限は数値入力欄(ui.number)と共通。

オーバーロード: ui.drag(action: func() -> void, value: int, lo: int, hi: int) -> Widget ui.drag(ref value: int, lo: int, hi: int) -> Widget ui.drag(id: string, value: float, lo: float, hi: float) -> Widget ui.drag(action: func() -> void, value: float, lo: float, hi: float) -> Widget ui.drag(ref value: float, lo: float, hi: float) -> Widget

idstringウィジェットID(ref の代わりに指定)
valueint現在の値(ref 指定時はバインド先変数)
loint
hiint
ui.open(120, 40);
var x = 20;
_ = ui.show(ui.drag(ref x, 0, 100));
print(x);   // 20(まだ引いていない)
var t = 0.5;
_ = ui.show(ui.drag(ref t, 0.0, 1.0).decimals(2));
print(t);   // 0.5
ui.number(id: string, value: int, lo: int, hi: int) → Widget

数の入力欄(スピンボックス)。**lo から hi の外には出られない。**

打てるのは数字だけ(lo が負のときは、先頭の `-` も打てる)。限りから出てしまう数は**打っても入らない**ので、範囲の外の値になることがない。右の − と + でも 1 ずつ動かせる(上下の矢印も同じ)。

**変数を ref で渡すのがいちばん短い。**打つたびに、その変数が書き換わる。

``` ui.number(ref age, 0, 120) // age が直に書き換わる ui.number("age", age, 0, 120) // ui.show() が "age" を返す ```

ウィジェットIDの代わりにコールバック関数(action)を渡した場合は、その中で ui.value() を取得します。

オーバーロード: ui.number(action: func() -> void, value: int, lo: int, hi: int) -> Widget ui.number(ref value: int, lo: int, hi: int) -> Widget

idstringウィジェットID(ref の代わりに使用)
valueint現在の数値(ref 渡しの場合はバインド先の変数)
loint
hiint

入力途中の一時的な状態(空文字やマイナス記号 `-` のみなど)も許容されます。

その間 ui.show() はウィジェットIDを返さないため、値は変更されません。

Enter キーまたはフォーカス喪失で入力を確定し、Esc キーで入力前の値に復帰します。

ui.open(160, 40);
var age = 12;
if ui.show(ui.number("age", age, 0, 120)) == "age" { age = ui.value(); }
print(age);   // 12
ui.field(ref value: string) → Widget

テキストフィールド。1行のテキスト入力・編集欄。

フォーカスを当ててテキストを入力します。値のバインドまたはイベント検知方法は2通りあります。

**変数を ref で渡すと、入力内容が変更されるたびにその変数が自動的に更新されます。** ウィジェットID(`id`)を渡した場合は、テキスト変更時に ui.show() がその ID を返します。 `ui.text_value()` で新しい文字列を取得して変数に代入してください。

``` ui.field(ref name) // name が直に書き換わる ui.field("name", name) // ui.show() が "name" を返す ```

状態(文字列)はアプリケーション側が保持し、UI ランタイム側では状態を永続化しない即時モード(Immediate Mode)の設計です。

オーバーロード: ui.field(id: string, value: string) -> Widget

valuestring現在の入力文字列(ref 渡しの場合はバインド先の変数)

ref で渡せるのは**モジュールレベルの var** のみです(エラー E0307)。他のモジュールの var でも可能です。

参照借用ではなく変数の実体を特定して書き戻すため、通常の代入操作と等価です。

関数内のローカル変数はスコープを抜けると破棄されるため渡せません。ローカル変数で管理する場合はウィジェットIDを使用してください。

IME(日本語入力など)の変換処理はプラットフォーム側に委譲されます。ウィンドウ環境(macOS・Windows)や

ブラウザ環境ではインライン変換(下線付きプレエディット)が表示され、確定した文字列が入力されます。

IME 非対応環境では、入力された文字が直接反映されます。

Backspace で1文字削除、Enter で入力確定(フォーカス移動)。なお、IME 変換中の Enter は確定操作となるためフォーカスは移動しません。

マウスドラッグや Shift+矢印キーで範囲選択が可能。ダブルクリックで単語選択、トリプルクリックで行選択、4連続クリックで全選択に対応。

アンドゥ(Undo)は Cmd-Z / Ctrl-Z、リドゥ(Redo)は Cmd-Shift-Z / Ctrl-Y。右クリックでコンテキストメニュー

(コピー・切り取り・貼り付け・全選択)が表示されます。macOS では Cmd-C/V/X/A、Windows/Linux では Ctrl-C/V/X/A のショートカットも有効です。

ui.open(140, 60);
var name = "さめ";
var nick = "しゃち";
var hit = ui.show(ui.col([ui.field(ref name), ui.field("nick", nick)]));
if hit == "nick" { nick = ui.text_value(); }
print(f"[{hit}] {name} {nick}");   // [] さめ しゃち
ui.textarea(ref value: string) → Widget

テキストエリア。複数行のテキスト入力・編集欄。

値の受取方法は ui.field と同様です。**変数を ref で渡すと、変更時にその変数が自動的に更新されます。** ウィジェットID(`id`)を渡した場合は、変更時に ui.show() がその ID を返すため、 `ui.text_value()` で新しい文字列を取得します。

``` ui.textarea(ref memo) // memo が直に書き換わる ui.textarea(ref memo, 8) // 8 行ぶんの高さで出す ui.textarea("memo", memo) // ui.show() が "memo" を返す ```

値は**改行文字(`\n`)を含む単一の文字列**です。行ごとに処理したい場合は `memo.split("\n")` 等で分割してください。

オーバーロード: ui.textarea(ref value: string, rows: int) -> Widget ui.textarea(id: string, value: string) -> Widget ui.textarea(id: string, value: string, rows: int) -> Widget

valuestring現在の入力文字列(ref 渡しの場合はバインド先の変数)

表示幅に収まらない行は自動的に**折り返し(ワードラップ)**表示されます。折り返し位置は空白文字または文字境界です

(ui.label と同様)。折り返しを行っても内部の文字列データ自体は変化しません。

Enter キーで**改行を挿入**します(ui.field と異なりフォーカスは外れません)。フォーカスを外すには、

エリア外をクリックするか Esc キーを押します。矢印キーで行間移動、Home / End で行頭・行末へ移動。

行数が表示領域を超える場合は、右側に垂直スクロールバーが表示されます。

ドラッグによる範囲選択、右クリックのコンテキストメニュー、IME による日本語入力等は ui.field と共通の操作体系です。

ui.open(200, 140);
var memo = "さめは 海に すんでいます。\n2行目";
var hit = ui.show(ui.col([ui.label("メモ"), ui.textarea(ref memo, 3)]));
var lines = memo.split("\n");
print(f"[{hit}] {lines.len()}");   // [] 2
ui.password(ref value: string) → Widget

パスワード入力欄。入力された文字をマスク表示する1行入力フィールド。

内部データおよび値の受取方法は ui.field と同様で、**画面表示時のみ**各文字を `*` でマスク表示します。ui.text_value() やバインドした変数には、入力された生の文字列がそのまま格納されます。

**show に true を渡すと、マスクを行わずに平文で表示します。** チェックボックスと連動させることで、「パスワードを表示する」機能を簡単に実装できます。

``` ui.col([ui.password(ref word, seen), ui.checkbox("見せる", ref seen)]) ```

オーバーロード: ui.password(id: string, value: string) -> Widget ui.password(ref value: string, show: bool) -> Widget ui.password(id: string, value: string, show: bool) -> Widget

valuestring現在の入力文字列(ref 渡しの場合はバインド先の変数)

セキュリティ保護のため、右クリックのコンテキストメニューでは**コピーおよび切り取りは無効化**されます

(中身の持ち出しを防止するため。貼り付けと全選択のみ利用可能。平文表示時も同様)。

ui.open(160, 40);
var word = "さめ";
_ = ui.show(ui.password(ref word));
print(word);   // さめ
var seen = true;
_ = ui.show(ui.password(ref word, seen));   // 伏せずに出す
print(word);   // さめ
ui.combo(id: string, options: list<string>, index: int) → Widget

プルダウン(コンボボックス)。クリックしてポップアップリストを表示し、項目を選択します。

**インデックス変数を ref で渡すと簡潔に記述できます。**選択変更時に変数が自動更新されます(0-indexed)。

``` ui.combo(ref iro, iro_na) // iro が直に書き換わる ui.combo("iro", iro_na, iro) // ui.show() が "iro" を返す ```

ウィジェットIDの代わりにコールバック関数を渡した場合は、その中で ui.value() を取得します。

選択方法は以下の2通りに対応しています:・クリックしてポップアップを開いた後、目的の項目をクリックして選択する・**マウスを押したままドラッグし、目的の項目の上で離す**(標準的なドロップダウン操作)

項目数が画面内に収まらない場合は上下にスクロールインジケータ(▲▼)が表示され、マウスカーソルを合わせるかドラッグすることでスクロールできます。

オーバーロード: ui.combo(action: func() -> void, options: list<string>, index: int) -> Widget ui.combo(ref index: int, options: list<string>) -> Widget

idstringウィジェットID(ref の代わりに使用)
optionslist<string>選択肢の文字列リスト
indexint現在選択中のインデックス(ref 渡しの場合はバインド先の変数)
ui.open(160, 60);
var iro_na = ["あか", "あお", "みどり"];
var pick = 1;
_ = ui.show(ui.combo(ref pick, iro_na));
print(iro_na[pick]);   // あお
ui.listbox(id: string, options: list<string>, index: int) → Widget

リストボックス。展開されたリストから項目を選択するウィジェット。

値の受取方法は ui.combo と同様です(ref 渡し、またはウィジェットID+ui.value())。

``` ui.listbox(ref sel, sakana, 5) // sel が直に書き換わる ui.listbox("s", sakana, sel, 5) // ui.show() が "s" を返す ```

項目が表示領域に収まらない場合は右側にスクロールバーが表示されます。スクロールバーのドラッグやホイールスクロールに対応。フォーカスがある間は、上下矢印キーでも項目選択が可能で、選択項目が表示されるよう自動スクロールします。

オーバーロード: ui.listbox(id: string, options: list<string>, index: int, rows: int) -> Widget ui.listbox(action: func() -> void, options: list<string>, index: int) -> Widget ui.listbox(action: func() -> void, options: list<string>, index: int, rows: int) -> Widget ui.listbox(ref index: int, options: list<string>) -> Widget ui.listbox(ref index: int, options: list<string>, rows: int) -> Widget ui.listbox(id: string, options: list<string>, chosen: list<int>) -> Widget ui.listbox(id: string, options: list<string>, chosen: list<int>, rows: int) -> Widget ui.listbox(action: func() -> void, options: list<string>, chosen: list<int>) -> Widget ui.listbox(action: func() -> void, options: list<string>, chosen: list<int>, rows: int) -> Widget ui.listbox(ref chosen: list<int>, options: list<string>) -> Widget ui.listbox(ref chosen: list<int>, options: list<string>, rows: int) -> Widget

idstringウィジェットID(ref の代わりに使用)
optionslist<string>選択肢の文字列リスト
indexint現在選択中のインデックス(ref 渡しの場合はバインド先の変数)
ui.open(160, 100);
var sakana = ["さめ", "まぐろ", "いわし"];
var picked: list<int> = [0, 2];
_ = ui.show(ui.listbox(ref picked, sakana));
print(picked);   // [0, 2](まだ押していない)
ui.tabs(id: string, labels: list<string>, index: int) → Widget

タブバー。タブの見出しを描画・制御します(タブパネルの内容描画は呼び出し側で行います)。

**インデックス変数を ref で渡すと簡潔に記述できます。**タブ選択時に変数が自動更新されます。

``` ui.tabs(ref page, ["メモ", "いろ"]) // page が直に書き換わる ui.tabs("page", ["メモ", "いろ"], page) // ui.show() が "page" を返す ```

``` ui.col([ ui.tabs("tab", ["せってい", "いろ"], tab), page(), // tab の中身は、いまの番号を見て自分で組む ]) ```

見出しとコンテンツを分離することで、即時モード UI において柔軟なレイアウト制御が可能です。

オーバーロード: ui.tabs(action: func() -> void, labels: list<string>, index: int) -> Widget ui.tabs(ref index: int, labels: list<string>) -> Widget

idstringウィジェットID(ref の代わりに使用)
labelslist<string>タブ見出しの文字列リスト
indexint現在選択中のタブインデックス(ref 渡しの場合はバインド先の変数)
ui.open(200, 60);
var tab = 0;
_ = ui.show(ui.tabs(ref tab, ["あ", "い"]));
print(tab);   // 0
ui.space(h: int) → Widget

縦に空ける。h は画素の数。

ui.open(80, 60);
_ = ui.show(ui.col([ui.label("上"), ui.space(20), ui.label("下")]));
print(ui.height());   // 60
ui.spacer() → Widget

スペーサー。空き場所を取る部品で、**並んでいる向き**に空ける。

ui.row の中なら横に、ui.col の中なら縦に場所を取る。数を省くと、**余ったところをぜんぶ取る**(伸びる空き)。横に並べたものを端に寄せたいときは、あいだに1つ置く。

``` ui.row([ui.label("なまえ"), ui.spacer(), ui.button("消す", "del")]) // 右端へ ui.row([ui.spacer(), ui.button("おわる", "end")]) // 右に寄せる ui.row([ui.spacer(), ui.label("まんなか"), ui.spacer()]) // 真ん中へ ui.col([ui.label("上"), ui.spacer(), ui.label("下")]) // 上と下へ ```

オーバーロード: ui.spacer(size: int) -> Widget

ui.space(h) は**縦にしか**空けない。横に並べたときにも効かせたいときは、こちら。

伸びる空きを2つ以上置くと、余りを等分する。

ui.open(120, 40);
var red = ui.rgb(255, 0, 0);
_ = ui.show(ui.row([ui.label("a"), ui.spacer(), ui.label("b").background(red)]), 0, 0);
print(ui.get(119, 0));   // 16711680(右の端まで寄る)
ui.col(children: list<Widget>) → Widget

カラム(縦の並び)。中身を縦に並べる部品。

部品を並べるいちばんふつうの入れ物で、view() が返す1つは、たいていこれになる。横並び(ui.row)の中に縦の並びを入れるのにも使う。

大きさは**中身に合わせて伸び縮みする**。幅はいちばん広い子、高さは子の合計(ui.row はその逆)。だから .background() も中身のぶんだけ塗られる。広げたいときは、取り分(.width(1.0) など)か画素(.width(n))を書く。中の部品に書いた取り分も、入れ物ごしに効く(下の width / height)。

**いちばん外(ui.show に渡した部品)だけ**は、寄せていなければ置ける幅いっぱいに広がる。だからいちばん外の並びでは、区切り線(ui.divider)や寄せ(.align)が面の幅を見る。ui.row も ui.grid も同じ。

ui.open(120, 60);
_ = ui.show(ui.row([
  ui.col([ui.label("左上"), ui.label("左下")]),
  ui.col([ui.label("右上"), ui.label("右下")]),
]));
print(ui.get(5, 4) != 0);   // true
ui.row(children: list<Widget>) → Widget

ロウ(横の並び)。中身を横に並べる部品。

ui.open(160, 40);
_ = ui.show(ui.row([ui.button("はい", "yes"), ui.button("いいえ", "no")]));
print(ui.width());   // 160
ui.grid(cols: int, cells: list<Widget>) → Widget

グリッド(格子・表)。cols 列に、左上から右へ、いっぱいになれば次の行へと升を詰める。

列の幅はその列でいちばん広い升、行の高さはその行でいちばん高い升に揃う。だから升の大きさがまちまちでも、縦の線が通る。升の数が列の数で割り切れなくてもよい(最後の行が途中で終わる)。

升に取り分(`.width(1.0)` など)を書くと、その列(行)の取り分になり、余りを分け合う。

**升ごとの関数から作る形**もある。cell(行, 列) が返す部品を、同じ順に詰める。行も列も 0 から数える。

オーバーロード: ui.grid(rows: int, cols: int, cell: func(int, int) -> Widget) -> Widget

colsint列の数(1 以上)
cellslist<Widget>升に入れる部品。左上から右へ
ui.open(100, 60);
var red = ui.rgb(255, 0, 0);
var blue = ui.rgb(0, 0, 255);
_ = ui.show(ui.grid(2, [ui.label("a").background(red), ui.label("b").background(blue),
                        ui.label("ccc").background(red), ui.label("d").background(blue)]), 0, 0);
var w = ui.text_width("ccc");                  // 左の列は "ccc" の幅に揃う
print(f"{ui.get(0, 0)} {ui.get(w + 11, 3)}");   // 16711680 255

func cell(r: int, c: int) -> Widget { return ui.button(f"{r},{c}", f"{r}-{c}"); }
ui.clear(0);
_ = ui.show(ui.grid(3, 3, cell));
print(f"{ui.get(5, 5) != 0} {ui.get(5, 59) != 0}");   // true false
ui.center(children: list<Widget>) → Widget

中身をまとめて、置ける幅の真ん中に置く。

`ui.col(中身).align("center")` と同じ。中身どうしの並びは変わらない。

ui.open(120, 40);
_ = ui.show(ui.center([ui.label("まんなか")]), 0, 0);
print(ui.width());   // 120
ui.stack(children: list<Widget>) → Widget

スタック(重ね置き・ZStack)。中身を**同じところに重ねて**置く。

書いた順に重なり、あとのものが上に来る。大きさはいちばん大きい中身に合わせる。絵の上に字を出す、下地の上にボタンを置く、といったときに使う。

中身の置き場所は、それぞれの `.align`(横)と `.valign`(縦)で決める。書かなければ左上に置く。

**押しは、カーソルに重なっているうち、いちばん上のものが取る。** 下に敷いたものには届かない。だから、面ぜんぶを覆う幕を1枚かぶせるだけで、うしろの画面がまとめて止まる ―― ダイアログはこれで書ける。

ui.stack([ body(), // うしろの画面 ui.label("").width(float.infinity()).height(float.infinity()) .background(ui.rgba(0, 0, 0, 150)), // うすい幕。ここで止まる dialog().align("center").valign("middle"), // 前に出すもの ])

幕を置かなければ、重ねたものの**その形のところだけ**が止まる。吹き出しや、その場に出す小さな板は、こちらで書く。

ui.open(80, 40);
_ = ui.show(ui.stack([
  ui.label("").width(40).height(20).background(ui.rgb(0, 0, 255)),
  ui.label("x").align("center").valign("middle"),
]), 0, 0);
print(ui.get(0, 0));   // 255
ui.scroll(children: list<Widget>) → Widget

スクロールビュー。垂直方向に子ウィジェットを配置し、表示領域を超えるコンテンツをスクロール表示する。

**高さを指定する**(`.height(n)` または flex レイアウト)ことで、表示領域外のコンテンツがクリップされ、右側に垂直スクロールバーが表示されます。スクロールバーのドラッグやつまみ移動、マウスホイールによるスクロールに対応しています。高さを指定しない場合はコンテンツ全体の高さまで拡張されます。

クリップ領域外の要素はクリック判定から除外されます。スクロール位置はフレーム間で保持されます。ウィジェットID(`id`)を指定しておくと、ウィジェットツリー内の配置順序が変化しても同一のスクロール位置が維持されます。

オーバーロード: ui.scroll(id: string, children: list<Widget>) -> Widget

childrenlist<Widget>内部に垂直配置する子ウィジェットのリスト
ui.open(80, 60);
var rows: list<Widget> = [];
for var i in range(20) { rows.push(ui.label(f"{i} 行目")); }
_ = ui.show(ui.scroll(rows).height(40), 0, 0);
print(ui.get(0, 50) != 0);   // false
ui.color(id: string, value: int) → Widget

カラーピッカー。現在の色見本と16進数カラーコードを表示し、クリックするとカラーパレットを表示する。

パレットには彩度・明度(SV)を選択する2次元領域と、色相(Hue)スライダーが表示されます。ドラッグして直感的に色を調整でき、パレット外のクリックまたは Esc キーで閉じます。

**カラー変数を ref で渡すと簡潔に記述できます。**調整時に変数が自動更新されます。ウィジェットIDを使用した場合は `ui.value()` で新しい色値(RGB/RGBA整数値)を取得します。アルファ値(透明度)は元の値が維持されます。

オーバーロード: ui.color(action: func() -> void, value: int) -> Widget ui.color(ref value: int) -> Widget

idstringウィジェットID(ref の代わりに使用)
valueint現在のカラー値(ref 渡しの場合はバインド先の変数)
ui.open(200, 120);
var iro = ui.rgb(80, 160, 220);
_ = ui.show(ui.color(ref iro));
print(iro == ui.rgb(80, 160, 220));   // true(まだ選んでいない)
ui.tree(label: string, id: string, open: bool, children: list<Widget>) → Widget

ツリーノード(折りたたみツリービュー)。ヘッダーをクリックして子要素の展開/折りたたみを切り替える。

**展開状態(bool)はアプリケーション側で管理します。**ref で渡した変数がクリック時に自動的にトグル反転し、ウィジェットIDを使用した場合は ui.show() がその ID を返します。子要素としてツリーノードをネストすることで多階層のツリー構造を表現できます。

オーバーロード: ui.tree(label: string, action: func() -> void, open: bool, children: list<Widget>) -> Widget ui.tree(label: string, ref open: bool, children: list<Widget>) -> Widget

labelstringヘッダーの見出し文字列
idstringウィジェットID(ref の代わりに使用)
openbool現在の展開状態(ref 渡しの場合はバインド先の変数)
childrenlist<Widget>展開時にインデントして表示する子ウィジェットのリスト
ui.open(160, 100);
var hiraku = true;
_ = ui.show(ui.tree("さかな", ref hiraku, [ui.label("さめ"), ui.label("まぐろ")]));
print(hiraku);   // true
ui.image(image: Canvas) → Widget

イメージビュー。Canvas オブジェクト(ui.canvas() や ui.load_png() で生成)をウィジェットとして表示する。

サイズ未指定時は画像の元解像度で表示され、`.width(n)` / `.height(n)` を指定すると最近傍補間でスケーリング表示されます(ドット絵等の輪郭がぼやけません)。アルファブレンド(透過)に対応しており、ui.stack によるレイヤー重ね合わせが可能です。

**マウスカーソルが画像上にある間、画像内ローカル座標を `ui.point_x()` / `ui.point_y()` で取得できます。** ウィジェットIDまたはコールバック関数を渡すことでクリックイベントも検知でき、ペイントツール等のインタラクティブな描画キャンバスを実装できます。

``` var paper = ui.canvas(160, 120, ui.rgb(255, 255, 255)); // view の中で ui.image("paper", paper) // 押されている間、乗っているところに点を打つ if ui.mouse(0) && ui.point_x() >= 0 { paper.set(ui.point_x(), ui.point_y(), sumi); } ```

オーバーロード: ui.image(id: string, image: Canvas) -> Widget ui.image(action: func() -> void, image: Canvas) -> Widget

imageCanvas表示する Canvas オブジェクト
ui.open(80, 60);
var e = ui.canvas(20, 10, ui.rgb(255, 0, 0));
_ = ui.show(ui.image(e), 0, 0);
print(f"{ui.get(0, 0)} {ui.get(20, 0)}");   // 16711680 0
ui.point_x() → int

マウスカーソルが置かれているイメージビュー(ui.image)内の**画像内X座標(ピクセル単位)**。画像外の場合は -1。

スケーリング表示されている場合でも、元画像のネイティブ解像度に基づくピクセル座標を返します。

ui.open(80, 60);
_ = ui.show(ui.image(ui.canvas(20, 10)), 40, 40);
print(ui.point_x());   // -1(カーソルは乗っていない)
ui.point_y() → int

マウスカーソルが置かれているイメージビュー(ui.image)内の**画像内Y座標(ピクセル単位)**。画像外の場合は -1。

ui.open(80, 60);
_ = ui.show(ui.image(ui.canvas(20, 10)), 40, 40);
print(ui.point_y());   // -1
ui.divider() → Widget

セパレータ(区切り線)。利用可能な幅いっぱいに境界線を描画する。

ui.open(80, 40);
_ = ui.show(ui.col([ui.label("上"), ui.divider(), ui.label("下")]), 0, 0);
print(f"{ui.get(0, 14) != 0} {ui.get(79, 14) != 0}");   // true true
ui.show(view: Widget) → string

ウィジェットを描画し、操作されたウィジェットの ID を返す。

渡すのは部品1つ。いくつも並べるときは ui.col / ui.row / ui.grid に入れて1つにする。誰も押していなければ空の文字。値を持つ部品(checkbox、slider、field)なら、新しい値は ui.value() か ui.text_value() で取れる。

部品は毎回この場で作り直す。処理系は部品の状態を覚えないので、 **「いまどうあるべきか」だけを書けばよい**。

置く場所は左上から (4, 4)。x と y を渡すとそこに置く。

オーバーロード: ui.show(view: Widget, x: int, y: int) -> string

viewWidget部品
ui.open(120, 60);
var count = 0;
var hit = ui.show(ui.col([ui.label(f"{count} 回"), ui.button("ふやす", "inc")]));
if hit == "inc" { count += 1; }
print(count);   // 0
ui.edited() → bool

直前の ui.show() で、ref で渡した変数が書き換わったか。

ui.field(ref ...) に文字が打たれると true になる。ui.run() が中で使っていて、変わっていればその場で描き直す。自分でくり返しを書くときも同じようにする。

ui.open(140, 40);
var name = "さめ";
_ = ui.show(ui.field(ref name));
print(ui.edited());   // false(何も打たれていない)
ui.has_action() → bool

この回、**関数を持った部品**が押されたか。

ui.run() が中で使っている。自分でくり返しを書くときは、これで見て ui.action() を呼ぶ。

ui.open(80, 40);
_ = ui.poll();
print(ui.has_action());   // false
ui.action() -> func() → void

押された部品が持っていた関数。持っていなければ、何もしない関数。

関数の値は、**変数に入れてから呼ぶ**。その変数は関数の中に置く

(トップレベルの変数からは呼べない)。

func once() -> void {
  if ui.has_action() {
    var act = ui.action();
    act();
  }
}
ui.open(80, 40);
_ = ui.poll();
once();
print(ui.width());   // 80
ui.value() → int

直前の ui.show() で動いた部品の、新しい数。

checkbox なら 1 か 0、slider なら選ばれた数。

ui.open(120, 40);
var vol = 30;
if ui.show(ui.slider("vol", vol, 0, 100)) == "vol" { vol = ui.value(); }
print(vol);   // 30
ui.text_value() → string

直前の ui.show() で動いた入力欄の、新しい文字。

ui.open(140, 40);
var name = "";
if ui.show(ui.field("name", name)) == "name" { name = ui.text_value(); }
print(f"[{name}]");   // []
ui.float_value() → float

小数を持つ部品が動いたときの、新しい値。

ui.value() の小数版。float で受け取る部品(ui.slider / ui.drag の float の形)で使う。 ref で受けている形なら、変数が直に書き換わるので、こちらは読まなくてよい。

ui.open(120, 40);
var mix = 0.25;
if ui.show(ui.slider("mix", mix, 0.0, 1.0)) == "mix" { mix = ui.float_value(); }
print(mix);   // 0.25
ui.chosen() → list<int>

いくつも選べる一覧が動いたときの、**新しい番号の並び**。

ui.value() は押された番号を返すが、こちらは押したあとに選ばれているものぜんぶ。 ref で受けている形なら、変数が直に書き換わるので、こちらは読まなくてよい。

ui.open(160, 100);
var sakana = ["さめ", "まぐろ"];
var picked: list<int> = [];
if ui.show(ui.listbox("s", sakana, picked)) == "s" { picked = ui.chosen(); }
print(picked);   // []
ui.theme(bg: int, fg: int, accent: int) → void

部品の色を決める。

3色だけ決めれば、部品の色はそこから作られる。

bgint下地の色(部品の中の塗りに使う)
fgint文字の色
accentint縁と、選ばれているところの色
ui.open(80, 40);
ui.theme(ui.rgb(20, 20, 28), ui.rgb(230, 230, 230), ui.rgb(76, 155, 232));
_ = ui.show(ui.button("押す", "go"));
print(ui.get(5, 5) != 0);   // true

Canvas

絵(画素の面)。ui.canvas() か ui.load_png() で作る。

**ふつうの値**なので、代入すればコピーになる。中身は書き換えるまで写さないので、「直す前に控えを取る」が安く書ける。

描くメソッドは画面に描く ui.* と同じ名前・同じ引数で、描き先だけが違う。切り抜き(clip)は絵ごとに覚えている。

Canvas.width() → int

横の画素数。

print(ui.canvas(12, 5).width());   // 12
Canvas.height() → int

縦の画素数。

print(ui.canvas(12, 5).height());   // 5
Canvas.get(x: int, y: int) → int

点の色を読む。外を尋ねたら 0。

var e = ui.canvas(4, 4, ui.rgb(0, 255, 0));
print(e.get(1, 1));   // 65280
Canvas.set(x: int, y: int, color: int) → void

点を打つ。半透明の色なら、下にある色と混ざる。

絵を直す道具の「筆」は、これ1つで書ける。 ui.rgba(0, 0, 0, 0) を打てば「消しゴム」になる。

var e = ui.canvas(4, 4, ui.rgb(0, 0, 255));
e.set(1, 1, ui.rgba(0, 0, 0, 0));   // 消す
print(ui.alpha(e.get(1, 1)));       // 0
Canvas.clear(color: int) → void

ぜんぶ1色で塗る。切り抜きの中だけを塗る。

var e = ui.canvas(4, 4);
e.clear(ui.rgb(255, 0, 0));
print(e.get(2, 2));   // 16711680
Canvas.hline(x: int, y: int, w: int, color: int) → void

横の線。

var e = ui.canvas(8, 8, ui.rgb(0, 0, 0));
e.hline(1, 2, 4, ui.rgb(255, 0, 0));
print(f"{e.get(1, 2)} {e.get(5, 2)}");   // 16711680 0
Canvas.vline(x: int, y: int, h: int, color: int) → void

縦の線。

var e = ui.canvas(8, 8, ui.rgb(0, 0, 0));
e.vline(2, 1, 4, ui.rgb(255, 0, 0));
print(f"{e.get(2, 1)} {e.get(2, 5)}");   // 16711680 0
Canvas.line(x0: int, y0: int, x1: int, y1: int, color: int) → void

2点をつなぐ線。

var e = ui.canvas(8, 8);
e.line(0, 0, 7, 7, ui.rgb(255, 0, 0));
print(e.get(3, 3));   // 16711680
Canvas.rect(x: int, y: int, w: int, h: int, color: int) → void

四角の枠。

var e = ui.canvas(8, 8, ui.rgb(0, 0, 0));
e.rect(1, 1, 4, 4, ui.rgb(255, 0, 0));
print(f"{e.get(1, 1)} {e.get(2, 2)}");   // 16711680 0
Canvas.fill_rect(x: int, y: int, w: int, h: int, color: int) → void

中まで塗った四角。

var e = ui.canvas(8, 8);
e.fill_rect(1, 1, 4, 4, ui.rgb(255, 0, 0));
print(e.get(2, 2));   // 16711680
Canvas.circle(x: int, y: int, r: int, color: int) → void

円の枠。

var e = ui.canvas(9, 9, ui.rgb(0, 0, 0));
e.circle(4, 4, 3, ui.rgb(255, 0, 0));
print(f"{e.get(4, 1)} {e.get(4, 4)}");   // 16711680 0
Canvas.fill_circle(x: int, y: int, r: int, color: int) → void

中まで塗った円。

var e = ui.canvas(9, 9);
e.fill_circle(4, 4, 3, ui.rgb(255, 0, 0));
print(e.get(4, 4));   // 16711680
Canvas.blit(x: int, y: int, w: int, pixels: list<int>) → void

画素の並びをそのまま置く。並べ方は ui.blit と同じ。

var e = ui.canvas(4, 4);
e.blit(1, 1, 2, [ui.rgb(255, 0, 0), ui.rgb(0, 255, 0),
                 ui.rgb(0, 0, 255), ui.rgb(255, 255, 255)]);
print(e.get(2, 2));   // 16777215
Canvas.text(x: int, y: int, s: string, color: int) → void

文字を描画する。使用フォントは画面描画と共通(ui.font() で設定)。

オーバーロード: Canvas.text(x: int, y: int, s: string, color: int, scale: int) -> void

ui.font_builtin();
var e = ui.canvas(40, 12);
e.text(1, 1, "Hi", ui.rgb(255, 255, 255));
print(e.get(1, 1));   // 16777215
Canvas.draw(image: Canvas, x: int, y: int) → void

別の Canvas の画像をこの Canvas に描画する。透明部分は下地とアルファブレンドされる。

幅と高さを指定するとスケーリング描画(最近傍補間)される(ui.draw と同様)。

オーバーロード: Canvas.draw(image: Canvas, x: int, y: int, w: int, h: int) -> void

var a = ui.canvas(8, 8);
var b = ui.canvas(2, 2, ui.rgb(0, 255, 0));
a.draw(b, 1, 1);
print(a.get(2, 2));   // 65280
Canvas.tri(x0: int, y0: int, z0: int, x1: int, y1: int, z1: int, x2: int, y2: int, z2: int, color: int) → void

三角形を単色で塗りつぶす(ラスタライズ)。ui.tri と同様だが、描画先がこの Canvas になる。

深度バッファ(ui.depth)は共通のバッファを使用する。

var e = ui.canvas(8, 8, ui.rgb(0, 0, 0));
e.tri(0, 0, 0, 7, 0, 0, 0, 7, 0, ui.rgb(255, 0, 0));
print(f"{e.get(1, 1)} {e.get(6, 6)}");   // 16711680 0
Canvas.clear_depth() → void

Z バッファ(深度バッファ)を初期値(最遠)にクリアする。**この Canvas の解像度に合わせて**バッファを確保・クリアする。

深度バッファは共通のメモリ領域を再利用するため、画面と異なる解像度の Canvas に 3D 描画を行う場合は、毎フレーム ui.clear_depth() ではなく**こちら**を呼び出す。

ui.open(8, 8);
ui.depth(true);
var e = ui.canvas(16, 16);
e.clear_depth();
e.tri(0, 0, 0, 15, 0, 0, 0, 15, 0, ui.rgb(255, 0, 0));
print(e.get(1, 1));   // 16711680
ui.depth(false);
Canvas.clip(x: int, y: int, w: int, h: int) → void

クリッピング矩形を設定し、指定領域内のみに描画を制限する。

var e = ui.canvas(8, 8, ui.rgb(0, 0, 0));
e.clip(0, 0, 4, 4);
e.clear(ui.rgb(255, 0, 0));
print(f"{e.get(2, 2)} {e.get(6, 6)}");   // 16711680 0
Canvas.clip_off() → void

クリッピング矩形を解除し、Canvas 全体を描画対象に戻す。

var e = ui.canvas(8, 8);
e.clip(0, 0, 4, 4);
e.clip_off();
e.clear(ui.rgb(255, 0, 0));
print(e.get(6, 6));   // 16711680
Canvas.to_png() → bytes

この Canvas の内容を PNG 画像データ(バイト列)にエンコードして返す。

透過ピクセルを含む場合は RGBA、完全不透明のみの場合は RGB で出力される。 ui.load_png() で復元可能。

var e = ui.canvas(4, 4, ui.rgb(255, 0, 0));
var png = e.to_png();
print(png.to_hex().starts_with("89504e47"));   // true

Widget

画面に出す部品。ui.label() などで作り、ui.col / ui.row / ui.grid で組んで ui.show() に渡す。

見た目を変えるメソッドは、**変えたものを返す**(元は変わらない)。だから鎖のようにつないで書ける。

Widget.color(c: int) → Widget

文字の色を変える。

ui.open(80, 40);
_ = ui.show(ui.label("l").color(ui.rgb(255, 0, 0)), 0, 0);
print(ui.get(1, 0));   // 16711680
Widget.background(c: int) → Widget

下地の色を変える。部品の後ろを塗る。

ui.open(80, 40);
_ = ui.show(ui.label("l").background(ui.rgb(0, 0, 255)), 0, 0);
print(ui.get(0, 0));   // 255
Widget.padding(n: int) → Widget

内側の余白(画素)。部品の周りに空きを作る。

ui.open(80, 40);
_ = ui.show(ui.label("l").padding(4).background(ui.rgb(0, 0, 255)), 0, 0);
print(ui.get(0, 0));   // 255
Widget.width(n: int) → Widget

幅を決める。**int なら画素、float なら取り分**(fr)。

決めなければ中身から決まる。取り分は、横に並んだ仲間との比。大きさの決まっている部品を除いた**余り**を、書いた数の比で分け合う。 `float.infinity()` は「余りぜんぶ」。

縦に並んでいる部品に取り分を書くと、置ける幅いっぱいになる。

オーバーロード: Widget.width(fr: float) -> Widget

ui.open(80, 40);
_ = ui.show(ui.label("l").width(50).background(ui.rgb(0, 255, 0)), 0, 0);
print(f"{ui.get(49, 0) != 0} {ui.get(50, 0) != 0}");   // true false
ui.clear(0);
// 幅 80 を 1:1 で分ける(間は 6 画素)
_ = ui.show(ui.row([ui.label("a").width(1.0).background(ui.rgb(255, 0, 0)),
                    ui.label("b").width(1.0).background(ui.rgb(0, 0, 255))]), 0, 0);
print(f"{ui.get(36, 0)} {ui.get(79, 0)}");   // 16711680 255
Widget.height(n: int) → Widget

高さを決める。**int なら画素、float なら取り分**(fr)。

取り分の分け方は width と同じで、こちらは縦に並んだ仲間との比。横に並んでいる部品に書くと、その並びの高さいっぱいになる。

取り分は**親がくれた場所の余り**を分けるもの。入れ物(ui.row / ui.col / ui.grid)がその向きの大きさを決めていなければ、中の部品の取り分は**入れ物ごしに外まで効く**。だから row の中のボタンに書くだけで、その row が col の余りを取り、ボタンが下まで伸びる。入れ物に画素の大きさ(.height(n))か入れ物自身の取り分を書くと、そちらが勝つ。

``` ui.col([ ui.label("上"), ui.row([ui.button("a", "a"), ui.button("b", "b").height(float.infinity())]), // ← row も一緒に、余りまで広がる ]) ```

オーバーロード: Widget.height(fr: float) -> Widget

ui.open(80, 40);
_ = ui.show(ui.label("l").height(20).background(ui.rgb(0, 255, 0)), 0, 0);
print(f"{ui.get(0, 19) != 0} {ui.get(0, 20) != 0}");   // true false
ui.clear(0);
// 高さ 40 を 1:1 で分ける(間は 4 画素)
_ = ui.show(ui.col([ui.label("a").width(1.0).height(1.0).background(ui.rgb(255, 0, 0)),
                    ui.label("b").width(1.0).height(1.0).background(ui.rgb(0, 0, 255))]), 0, 0);
print(f"{ui.get(0, 17)} {ui.get(0, 39)}");   // 16711680 255
ui.clear(0);
// row の中の b に書くだけで、row が col の余りを取り、b が下まで伸びる
var b = ui.label("b").height(float.infinity()).background(ui.rgb(0, 0, 255));
_ = ui.show(ui.col([ui.label("a"), ui.row([ui.label("x"), b])]), 0, 0);
print(ui.get(12, 39));   // 255
ui.clear(0);
// 入れ物が大きさを決めていれば、そこで止まる(row は 12 のまま)
_ = ui.show(ui.col([ui.label("a"), ui.row([ui.label("x"), b]).height(12)]), 0, 0);
print(f"{ui.get(12, 23) != 0} {ui.get(12, 39)}");   // true 0
Widget.align(a: string) → Widget

親がくれた幅の中で、どこに寄せるか。`"left"` `"center"` `"right"`。

ui.open(80, 40);
_ = ui.show(ui.label("l").align("right"), 0, 0);
print(ui.get(1, 1) != 0);   // false
Widget.valign(a: string) → Widget

親がくれた高さの中で、縦にどこへ寄せるか。`"top"` `"middle"` `"bottom"`。

書かなければ、置く側の決めた既定になる(横並び・格子ではまんなか、重ね置き(ui.stack)では上)。

ui.open(80, 40);
_ = ui.show(ui.stack([ui.label("").width(40).height(30),
                      ui.label("l").valign("bottom").color(ui.rgb(255, 0, 0))]), 0, 0);
print(ui.get(1, 1) != 0);   // false
Widget.border(c: int) → Widget

縁(ボーダー)を引く。太さを省くと 1 画素。

中身の上から引くので、細い線でも隠れない。`.radius()` と一緒に書けば、角の丸みに沿って曲がる。

オーバーロード: Widget.border(c: int, w: int) -> Widget

ui.open(80, 40);
_ = ui.show(ui.label("l").width(40).height(20).border(ui.rgb(255, 0, 0)), 0, 0);
print(f"{ui.get(0, 0)} {ui.get(20, 10)}");   // 16711680 0
Widget.radius(n: int) → Widget

角の丸み(画素)。下地(`.background`)と縁(`.border`)の両方に効く。

0 を渡すと四角に戻る。縦横の半分より大きい数を渡しても、そこで止まる。

ui.open(80, 40);
_ = ui.show(ui.label("l").width(40).height(20).radius(8)
              .background(ui.rgb(0, 0, 255)), 0, 0);
print(f"{ui.get(0, 0)} {ui.get(20, 10)}");   // 0 255
Widget.decimals(n: int) → Widget

小数を出すときの桁(0〜9)。ui.slider / ui.drag の float の形に効く。

書かなければ、限りの広さから決まる(狭いほど細かく出す)。

ui.open(120, 40);
var t = 0.5;
_ = ui.show(ui.drag(ref t, 0.0, 1.0).decimals(3), 0, 0);
print(t);   // 0.5
Widget.filter(spec: string) → Widget

入力欄(ui.field / ui.textarea)に**入れてよい字**を決める。

通らない字は入らない。1字ずつ見るので、打っている途中の「まだ形になっていない」中身でも困らない。書き方は3つ。

| 書き方 | | |---|---| | 決まった呼び名 | `"digit"` `"bin"` `"oct"` `"hex"` `"alpha"` `"alnum"` `"ascii"` | | 正規表現の字の組 | `"[0-9A-F]"` `"[^ ]"`(`^` で「その字以外」、`-` で範囲) | | 並べた字そのもの | `"0123456789.-"` |

改行はいつでも通る(複数行の入力欄のため)。

ui.open(160, 40);
var hex = "1F";
_ = ui.show(ui.field(ref hex).filter("hex"), 0, 0);
print(hex);   // 1F
Widget.tooltip(text: string) → Widget

ツールチップ。カーソルを合わせたときに出す説明。

どの部品にも付けられる。少しのあいだ合わせ続けると、カーソルのそばに出る(すぐ出すと、通りすがりで点滅して読みにくいため)。長い文は折り返す。

入れ子にしたときは、**細かいほう**(中身)の説明が出る。

ui.open(120, 40);
_ = ui.show(ui.button("消す", "del").tooltip("押すと、ぜんぶ消えます"));
print(ui.width());   // 120
Widget.placeholder(text: string) → Widget

プレースホルダ。何も入っていないときに、うすく出しておく字。

入力欄(ui.field / ui.textarea / ui.password / ui.number)で、 **そこに何を入れるところか**を出しておくのに使う。1字でも打てば消える。

出しているだけなので、中身にはならない。ui.text_value() や ref で渡した変数は空のまま。伏せ字の欄(ui.password)でも、この字は伏せない。

ui.open(160, 40);
var name = "";
_ = ui.show(ui.field("name", name).placeholder("なまえ"));
print(f"[{name}]");   // [](うすい字は中身にならない)
Widget.font(px: int) → Widget

字の大きさ(画素)。**中に入れた部品にも効く。**

部品の寸法(並べたときの間、ボタンや入力欄の余白、チェックの四角、つまみの長さ)は、ぜんぶ字の大きさから決まっている(下の「見た目の既定値」)。だからここを変えると、字だけでなく**まわりごと釣り合ったまま**大きくなる。見出しは大きく、注記は小さく、を1つ書くだけで済む。

ui.col([ ui.label("設定").font(24), // 見出し ui.label("あとから変えられます").font(10), ui.button("閉じる", "close"), // ここは元の大きさのまま ])

入れ物に書けば、その中の部品ぜんぶが大きくなる。中の部品がさらに書けば、そちらが勝つ。0 は「決めていない」で、外から受け継いだ大きさのままになる。

システムフォント(ui.font())がロードされていれば、指定されたピクセルサイズで滑らかにラスタライズされる。内蔵の 5×7 ビットマップフォントは**整数倍のスケーリングのみ対応**するため、最も近い倍率にフォールバックする(8・16・24px 等)。

ui.open(120, 60);
ui.clear(0);
_ = ui.show(ui.label("あ").font(24).background(ui.rgb(0, 0, 255)), 0, 0);
var big = ui.get(2, 16) != 0;
ui.clear(0);
_ = ui.show(ui.label("あ").font(8).background(ui.rgb(0, 0, 255)), 0, 0);
print(f"{big} {ui.get(2, 16) != 0}");   // true false
Widget.disabled(on: bool) → Widget

使えなくする。**中に入れた部品にもぜんぶ効く。**

うすい色になり、押しても・引いても・打っても何も起きない。カーソルの形も変わらなくなる。入れ物に書けば、その中の部品がまとめて止まるので、「同意するまで先へ進めない」のような画面が1行で書ける。

ui.col([ ui.checkbox("同意する", ref ok), ui.col([...]).disabled(!ok), // ok が false のあいだ、中はぜんぶ止まる ])

止まっているあいだも**場所は同じだけ取る**ので、出し入れで画面が動かない。消したいときは、そもそも配列に入れない。

入れ物で止めたものは、中の部品に false を書いても戻らない(外で止めているものが勝つ)。

ui.open(200, 60);
var ok = false;
_ = ui.show(ui.col([
  ui.checkbox("同意する", ref ok),
  ui.button("進む", "go").disabled(!ok),
]));
print(ok);   // false