吉里吉里Z (kirikiri Z) 本体クラス API のリファレンス。TJS2 で吉里吉里Z 上のスクリプトを書く、レビューする、デバッグするときに使う。Layer / Window / Bitmap / System / Storages / Font / Plugins / Timer / Debug / AsyncTrigger / Scripts / BinaryStream / Matrix32 / Matrix44 / Rect / ImageFunction のコア API、サウンド系 (WaveSoundBuffer / SoundBuffer / VideoOverlay)、DrawDevice (BasicDrawDevice / SDLDrawDevice / OGLDrawDevice / NullDrawDevice)、OpenGL 描画系 (Canvas / Texture / ShaderProgram / Offscreen / VertexBinder / VertexBuffer)、および主要プラグイン提供クラス (HttpRequest / GdiPlus.* / WIN32Dialog / CSVParser / LineParser / Process / Pad / MenuItem / Unzip / Zip / PSD / SimpleHTTPServer) を網羅。**呼び出されたら必ず「共通パターン」と「クロスカッティング概念」を確認し、必要な詳細クラスは doc/reference/*.md を Read しに行くこと。** TJS2 言語そのものや組み込みクラス (Array / Dictionary / Math 等) は別 skill (`tjs2`) を参照。エンジン内部構造 (C++ 実装、レンダリングパイプライン詳細) は対象外。
elements
吉里吉里Z 上の Elements ベース汎用ダイアログ/画面 UI (cycfi/elements + elements_modal) の作り方リファレンス。TJS で JSON / Dictionary 定義のダイアログを作る・出す・イベントを受ける・複数画面フロー(navigator)や常駐メニューを組む・入力/フォーカス/モーダルを制御する・Agent で動作検証する、といった場面で使う。基本的なダイアログ画面の作り方(最小例・レイアウト JSON スキーマ・ウィジェット一覧)から、モーダル/非モーダル/独立ウィンドウ/常駐フロー、複数インスタンス/z-order、DrawDevice 登録タイミング等のハマりどころまで網羅。Win32 ネイティブの WIN32Dialog(win32dialog プラグイン)とは別物。TJS2 言語仕様は skill `tjs2`、本体クラス API は `krkrz`、REPL/Agent 駆動は `krkrz-repl` を参照。
インストール方法を見る含まれるファイル(2)
- SKILL.md43.2 KB
- references/krkr_integration.md12.9 KB
SKILL.md(原文)
インストールする前に、エージェントに与えられる指示の中身を確認できます。
パスの基点: 本文の相対パスは engine ルート基準 (krkrz_dev では
src/core/を前置。下記参照)。作業ディレクトリが krkrz_dev 以外 (krkrz_android / krkrz_ios などの外枠や案件フォルダ) のときは${KRKRZ_BASE}/krkrz_dev/を前置して読む (echo $KRKRZ_BASEで実パスを確認。マシンごとに値が違うので絶対パスは書き込まない)。
Elements ベース ダイアログ / 画面 UI (krkrz)
吉里吉里Z に埋め込んだ Elements (ThorVG/cycfi ベースの C++ GUI) で、JSON / TJS Dictionary 定義のダイアログや画面を出す仕組み。ElementsDialog クラス経由で使う。全デスクトップ変種 (SDL3 / WINVER / OGL) で動作する。
Win32 の
WIN32Dialog(win32dialog プラグイン)とは別物。あちらは Win32 ネイティブダイアログ。こちらは全 PF 共通の Elements UI。
パスの読み方
本文のエンジンパスは engine ルート相対 (common/..., data/..., doc/..., external/...)。
本スキルが置かれている umbrella (krkrz_dev) では src/core/ を前置して読む
(例: src/core/doc/ElementsDialog.md、src/core/data/ui/、src/core/external/elements/)。
engine 単独チェックアウトならそのまま。
ゲーム (KAG/kag) へ組み込むときは
references/krkr_integration.md を読む — 案件へ載せるときの統合知識: 層構成 (基盤 TJS + 画面ドライバ) / 表示経路の選択と engine 側の制約 (overlay のサイズ上限・showFile が autopath に乗らない・サブクラスの super.ElementsDialog()) / KAG の openDialog 枠へ乗せるホストレイヤ方式と z オーダー / ドライバの型とライフサイクルの落とし穴 (常駐画面の再表示・タブ切替) / 値と動的画像の注入 / 資材探索とフォント一括登録 / 遷移エフェクト / Agent 検証の癖 / 未解決の engine TODO。
最初に読むべき一次資料 (SSOT)
doc/ElementsDialog.md— 機構全体・API 経路・入力ルーティング・複数インスタンス・フェーズ状態。まずこれ。external/elements/external/elements_modal/README.md— JSON レイアウトの全ウィジェット/属性カタログ(下の一覧は要約)。- 実例:
data/ui/about.json/data/ui/menu/*.json+data/ui/menu/app.jsonc(navigator フロー) /data/elements_gallery//data/transition_demo//data/startup.tjs(FlowMenuDialog常駐メニュー)。
1. 基本的なダイアログの作り方 (最小例)
ElementsDialog を継承し、onAction でボタン等に反応、showJson(または showDict)で表示、close() で閉じる。非モーダル (オーバーレイ) が基本形。
class MyDialog extends ElementsDialog {
function onAction(id, payload) {
// state widget の値変化 / button click で発火 (payload: 値、button は void)
switch (id) {
case "ok": System.inform("OK が押されました"); close(); break;
case "cancel": close(); break;
}
}
}
var dlg = new MyDialog();
dlg.showJson('{
"size": [360, 200],
"background": [30, 30, 60, 245],
"input": { "arrow_focus_nav": true },
"content": { "type": "margin", "padding": 20, "child": {
"type": "vtile", "children": [
{ "type": "align_center",
"child": { "type": "label", "text": "こんにちは", "size_scale": 1.3 } },
{ "type": "vspacer", "height": 20 },
{ "type": "htile", "children": [
{ "type": "button", "id": "ok", "text": "OK", "initial_focus": true },
{ "type": "hspacer", "width": 12 },
{ "type": "button", "id": "cancel", "text": "キャンセル" }
] }
]
} }
}');
Dictionary で書く (showDict) — JSON 文字列を書かなくて済む
common/visual/elements/VariantJsonUtil.cpp が Dictionary→JSON へ変換してから同じ経路に流す。JSON をエスケープせずに書けて可読性が高い。
dlg.showDict(%[
size: [360, 200], background: [30, 30, 60, 245],
input: %[ arrow_focus_nav: true ],
content: %[ type: "margin", padding: 20, child: %[
type: "vtile", children: [
%[ type: "label", text: "こんにちは", size_scale: 1.3 ],
%[ type: "vsize", height: 40,
child: %[ type: "button", id: "ok", text: "OK", close_on_click: true ] ],
] ] ]
]);
ElementsDialog.dictToJson(value)で変換結果 JSON を取得できる (デバッグ / 資材書き出し)。- TJS Dictionary の制約 2 点(重要):
- bool が無い:
true=整数1。close_on_click: trueは JSON の1になる (elements 側は 0/非0 を真偽として受ける)。 - 空文字キー不可: navigator の既定遷移
"": "<exit>"は Dict で書けない → 未定義 action フォールバック(entry=exit / 子画面=pop)で足りるか、その画面だけ JSON 文字列にする。
- bool が無い:
2. レイアウト JSON スキーマ
トップレベル
| キー | 型 | 説明 |
|---|---|---|
size | [w,h] | 希望論理サイズ(上限)。実サイズは content の自然サイズにフィット縮小(上余白対策) |
background | [r,g,b,a] | content 描画前の塗り(省略=透明・縁なし) |
align | "center"(既定)/top/bottom/left/right/top_left/bottom_right 等 | overlay 上の配置。入力座標補正も同じなのでクリックずれ無し。全 overlay 経路で有効 |
margin | int | 非中央側のサーフェス端からの余白 px(既定 0)。例: 左上メニュー = "align":"top_left","margin":24 |
locale | "ja-JP" 等 | label の locale 未指定時の既定(CJK 同形字の出し分け) |
content | element | ルート要素 |
input | object | キー/パッドナビ設定(§6) |
vars | {name:string} | 変数 store 初期値(label.text_var / vars_on_focus が参照) |
transitions | {action:target} | navigator の遷移定義(§5)。object 形式で画面切替エフェクト(effect/duration/rule/vague)も宣言可 |
pad_theme | xbox/ps/switch/keyboard/none | pad_icon の名前解決 |
atlases | {name:spec} | テクスチャアトラス事前ロード(atlas_* が参照) |
font_scale | number | 明示 size を持たない button/toggle/check_box/label の既定フォント倍率(既定 1.0) |
style | object | レイアウト密度の一括指定: %[font_scale, tile_gap(gap未指定タイルの既定), row_height(button系/input_box/selection_menu の既定最小高), padding(content外側余白)]。全て省略=従来一致。既定の「詰まった」見た目を spacer なしで解消できる |
ウィジェット ("type") 要約 (全リストは elements_modal/README.md)
レイアウト: vtile/htile(children,gap=子間隙間px・省略時 style.tile_gap→0) / margin(padding,child) / hsize/vsize(width|height,child) / hspacer/vspacer(width|height) / spacer(size) / align_center/align_left/align_right/align_top/align_middle/align_bottom/align_center_middle(child) / box(color) / band(color+任意child) / layer(children,先頭=最前) / group(title,child) / scroller(child) / filler / floating(at:[x,y,w,h],child) / canvas(children に at 付き、PSD 絶対配置向け。choice_nav:true で子 choice 群を 1 フォーカスの左右トグル化、子の at_var で配置を変数駆動)。
注意: hsize/vsize の指定値は child の limits に clamp される (cycfi fixed_size 仕様)。label 等の固定 max を持つ child を直接包んでも希望サイズまで広がらない → 全体サイズを確定したい画面は各パーツ幅を明示して自然サイズ=希望値にするのが確実。
長文テキスト: text_box — 複数行・自動折返しの静的テキスト (text,size|size_scale,color,mono=等幅,text_var=setVar で本文丸ごと差替え)。幅は親 (hsize) が決め、高さは折返しに追従。長文は親に scroller。行 label 大量生成より軽い。実例=data/ui/license_dialog.tjs (showLicenseDialog: 左=一覧/右=text_box の 2 ペイン全画面モーダル、モーダル中も onAction→setVar が同期で効く)。
矩形テキスト/字幕: text_area — 矩形へ流し込む静的テキスト (text/text_id/text_var/text_list_id+index_var は label と同規約, size|size_scale, color, font=comma区切りfamilies, align=left/center/right, line_spacing=行間追加px, base=auto/ltr/rtl, count_var=文字送り (-1=全部))。折返し・行頭行末禁則・count が本体 Layer.drawShapedTextArea と同一ロジックなので同じ本文・同じ幅なら改行位置が一致する (glyphware layoutBlock)。折返しは全文で確定してから count を適用=送ってもリフローしない。数える単位はクラスタ (Layer.shapedTextCount と同じ)。text_box は従来互換 (素朴な折返し・禁則なし) で据置なので既存画面の改行は変わらない。⚠floating の絶対座標で置くなら top-level "size":[w,h] を必ず書く (省略するとダイアログが内容最小サイズまで縮み何も見えない)。仕様=elements docs/block-text.md。
入力/state: label(text,size=px絶対 or size_scale=倍率,color,text_var,text_id=i18n,text_list+index_var=指定番号表示、index+index_offset_var=«N 行の窓»〔行ごと固定 index + 行共有の先頭位置。引く位置=index+offset、窓モードの範囲外は空文字〕、text_list_var=一覧データ自体の差替え) / button(text,id) / checkbox|check_box(text,id,value) / toggle_button / input_box(placeholder,id,size,max_chars=最大文字数〔codepoint 単位、別名 maxlength、0/省略=無制限。満杯打鍵は無視・paste は収まる分だけ〕,text/value=初期値〔build 時全選択=そのまま打つと置き換え〕) / selection_menu(id,options,selected) / slider(0..1 の素のスライダ。id,initial,vertical,thumb_color/track_color〔既定=テーマ予約色 @slider_thumb 白 / @slider_track 黒〕,value_var,display/display_var) / invert_button / ring_button / labeled_row(label,label_width,child) / tab_view(tabs,initial) / picker 系 cycle_picker/framed_cycle_picker/segmented_picker(options|options_id=i18n,initial,font_size,font=表示テキストの family[#axes]・省略でテーマ既定,index_var=選択indexを変数へ)。
装飾/画像: pad_icon(コントローラアイコン) / sprite_button / atlas_image(rect or rect_list+index_var=変数で矩形切替、focus_link=リンク先 id のフォーカスで frames.normal/frames.hilite を切替える飾り〔normal 省略可=非フォーカス時は何も描かない・素材1枚のフォーカスインジケータ用。既定で「飾りに hover→リンク先へフォーカス」プロキシが付きクリックも奪うので、コントロールに重なる配置は "hover_focus_link": false〕)/atlas_button/atlas_toggle/atlas_choice(排他)/atlas_slider(thumb 形式 or fill+fill_at ゲージ形式、value_var。両端の増減矢印=dec/inc+dec_at/inc_at〔+track_at/step/repeat*/flash_ms、名前は減/増の意味で幾何名 left/right/up/down はエイリアス。atlas_scrollbar も同キー〕)/atlas_progress/atlas_cycle_picker(画像矢印ピッカー、フォーカス=矢印hilite、font 指定可)/animated_sprite / gizmo_image(9patch)。
- 色は
[r,g,b,a]配列(0–255)。フォントサイズはsize=px絶対 /size_scale=倍率(テーマ既定≒14px 比)。JSON/JSONC(コメント+末尾カンマ)可。 - i18n: top-level
strings({id:{lang:str}}) +lang。text_id/options_id/text_list_idが現在言語で解決される。TJS からの実行中切替はElementsDialog.language = "en"(表示中の全ダイアログへ即時反映・開き直し不要、picker は選択 index 維持。以後開く画面の既定にもなる)。 - 変数連動: picker
index_var(双方向: 選択変更で書き + setVar で quiet 追従) ↔text_list/rect_listのindex_var(読み) を同名にすると選択連動(機種選択→SPEC/スクショ等)。value_var=10進小数、at_var="x,y[,w,h]"。pickerenabled_var=選択肢の有効/無効 mask('0'/'1'文字列、step/click が無効 index をスキップ。未開放機種の出し分け)。choice (atlas_choice/radio_button) のselected_var+selected_value=ラジオグループ変数(グループ全員同じ var + 異なる value、var==value の1個が選択、双方向)。TJS からはdlg.setVar(name, value)で駆動。 - 画面をまたいで保つ変数: top-level
"shared_vars": ["cfg_*","ui_lang"](完全一致 or 末尾*の前方一致)。一致した変数はセッション共有ストアと双方向になり、次の画面へ引き継がれる(共有側の値が画面の"vars"既定より優先)。ホスト実装不要。セーブ/ロードは static APIElementsDialog.setSharedVar(name,value)/getSharedVars()(辞書)/clearSharedVars()。共有側へ出るのは「変化として書かれた」値だけで widget の初期値(initial/value)は出ない。設定画面をタブで渡り歩いてスライダーが戻る、を防ぐのが本来の用途。 - 2 値トグルの
value_var:checkbox/check_box/toggle_button/slide_switchが変数 store と双方向(""/"0"/"false"=off)。未設定なら"value"を種まき、既にあればそれで初期状態を上書き。setVarでの追従ではon_clickは発火しない。設定 ON/OFF がホストのコールバック無しで書ける。 - 絵を変数で差す
image_var:imageの絵そのものを変数で差し替える。値がそのまま画像パス("resources/x.png"/"mem://thumb_3"/ 空=無描画)。構築時は変数値が静的"image"より優先、未設定なら"image"を種まき。セーブ一覧のページ送りでサムネが変わる / CG ビュワーの絵を送るが画面再構築なしで書ける。空や読めないパスでも widget は残るので、正しいパスを入れれば戻る。 - 差し替え可能アトラス: top-level
"atlases": { "cg": { "path": "atlas/cg_g0.png", "swappable": true } }と宣言すると、ElementsDialog.setAtlasImage("cg","atlas/cg_g1.png")(static、path は画面のresource_base起点、戻り値=差し替えたか)で画面はそのまま絵の束だけ入れ替わる。widget は作り直さないのでレイアウト/フォーカス維持。⚠同じ矩形割りであること(frames/rect は変わらないので位置がずれると別の絵が出る)。一覧はElementsDialog.swappableAtlases()。swappable はパス単位キャッシュに乗らない(他画面を巻き添えにしないため)。 - モーダルへの初期 vars 注入:
dlg.showModalFile(path, %[name=>value,...])(showModalJson/Dict も同様、第2引数 Dictionary)。build 直後・pump 前に変数 store へ流し込む(モーダル中は TJS がブロックされ setVar 不可のため)。モーダル中も onAction は同期で届く。 - pad_icon/フォント setup (static):
ElementsDialog.setPadIconBase(dir)(Kenney SVG のベース storage パス。未設定だと灰色プレースホルダ)/ElementsDialog.setPadTheme("xbox"|"ps"|"switch"|"keyboard"|"auto")("auto"=接続パッドの系統〔System.padStyle〕から自動選択。接続数/系統の変化をその場で検出して決め直すので抜き差しに即追従〔表示中の画面も再描画〕。系統不明のプラットフォームはパッド 1 台以上で"xbox"、0 台で"keyboard")/ElementsDialog.setPadIconAlias(theme, name, basename)(論理名→Kenney basename の既定表〔a=Enter/b=Esc/dpad=矢印〕をテーマ単位で上書き。例: キャンセルが BackSpace ならsetPadIconAlias("keyboard","b","keyboard_backspace")。basename 空で個別解除、name 空でテーマ全解除)/ElementsDialog.registerFontDir(dir)/ElementsDialog.defaultFontFamily = "Open Sans, Roboto, Noto Sans JP, ..."(明示設定は自動 theme 並びに上書きされない。Emoji 系は必ず末尾に)。 - 要素の有効/無効を変数連動: button 系(
button/atlas_button/invert_button/ring_button)のenabled_var。値"0"で無効、それ以外(既定)で有効。無効中はクリック/キー決定が効かず、描画はdisabledframe があればそれ、無ければ半透明。進行で開放されるメニュー項目(未クリアなら「おまけ」を灰色)等に。
static 設定一覧 (クラス全体に効く。ElementsDialog.xxx)
⚠ ElementsDialog を継承したクラスのメソッド内から触るときは global.ElementsDialog.xxx。素の ElementsDialog は親クラス参照になり、static プロパティへの代入が「メンバが見つかりません」になる。
| プロパティ | 既定 | 用途 |
|---|---|---|
language | "" | i18n 表示言語。代入で表示中の全画面へ即時反映(上記 i18n 参照) |
focusRing | false | フォーカス中要素に描かれる汎用の枠(青い角丸)。krkrz ホスト初期化で OFF が既定(authored 画面は focused frame / focus_link 装飾で表現する方針)。使いたい場合に true(起動時の明示設定は初期化に上書きされない)。button/slider/dial/thumbwheel が対象。OFF でもフォーカス自体は生きるのでキー/パッド操作と hilite 切替は不変 |
renderCache | true | 変化の無いフレームの再ラスタ+再アップロードを省略。アイドルがゼロコストになる。false は負荷比較用 |
partialRedraw | true | 変化した矩形だけ描き直す(ダーティ矩形)。false は全面 |
baseSize | 未設定 | UI の author 基準面サイズ [w,h](overlay 拡縮 fit の分母)。void で既定=ゲーム基準面(primaryLayer)へ戻る。ゲーム画面と別解像度で UI を author しているときに設定すると、部分パネルの拡縮が author 基準になりゲーム側の基準面変更に巻き込まれない |
renderScale | 0 | ラスタライズ密度。0=auto(present サイズで直接)/>0=authored×倍率で描いて拡縮 |
renderStats / renderStatsReset() | — | 描画パイプラインの区間計測(frames/rasters/partials/updateUs/rasterUs/uploadUs/presentUs 等)。累積値なので2回読んで差分を取る。計測画面=data/elements_bench(-benchauto で無操作スイープ) |
renderCount | — | 累計ラスタライズ回数。アイドルで増えなければ renderCache が効いている |
atlasCacheStats | — | アトラスのデコードキャッシュの常駐量 %[bytes, count, budget]。画面を閉じても手放さない設計なので、場面の切れ目で抱え込み量を見る用 |
trimAtlasCache(budget=0) | — | アトラスキャッシュを切り詰める(0=使われていない分を全部)。戻り値=解放バイト数。⚠表示中の画面が使っている分は参照が残るので落ちない→画面を閉じた後に呼ぶ |
atlasCacheBudget | 192MB | アトラスキャッシュの予算。代入で恒久変更(下げたらその場で切り詰め)。0 でキャッシュ無効 |
interactive 属性 (focusable widget 共通)
"id"—onAction/result.values/ shortcut / setVar の参照キー。"initial_focus": true— 起動時フォーカス候補。複数指定可で、先頭候補がenabled_varで無効なら次の有効候補へ落ちる。数値を書くと明示優先度(小さいほど優先・true=0、同値は build 順)。候補確定は表示直後の idle まで遅延するので、show 後にsetVarで有効/無効を注入する運用でも注入後の状態で判定される。"close_on_click": true— 既定 false。true の button だけが click で「閉じて確定」する(result.action=id)。false はonActionを発火するだけで閉じない → OK/Cancel 等の「閉じるボタン」にだけ付ける。
3. 表示経路の使い分け (ElementsDialog メソッド)
| メソッド | モード | 用途 |
|---|---|---|
showJson(json[, grabFocus=true[, modal=grabFocus]]) / showFile(path, ...) | overlay・非ブロッキング | 通常のダイアログ。onAction 逐次、close() で終了。showJson(json, true, false)=非モーダル+フォーカスあり(操作パネル推奨形) |
showDict(dict, ...) | 同上の Dictionary 版 | JSON を書かずに |
showModalJson(json) / showModalDict(dict) | overlay モーダル・ブロッキング | ゲーム画面上に nested ループ。戻り値 %[action, values] |
showModalJson(json, title, w, h) | 独立 OS ウィンドウモーダル・ブロッキング | 別ウィンドウ。SDL=run_modal / WINVER=専用 Win32 窓 |
showFlow(manifest) / showFlowScreens(dict, entry) | overlay・ブロッキング・複数画面 | navigator フロー(§5) |
startFlow(manifest) / startFlowScreens(dict, entry) | overlay・非ブロッキング・常駐 | 出しっぱなしメニュー。背景動作と併存(§5) |
- ブロッキング(showModal/showFlow)* は
resultを返す:result.action=閉じた button id(Esc/× は"")、result.values=state widget 最終値マップ。 showFile/showFlowのパスは Storages 解決だが autopath 検索には乗らない — ファイル名だけだと失敗するのでパス付き("system/foo.jsonc")で渡す。画面ファイルの相対資材はその画面ファイルのディレクトリ起点。
ホストのレイヤへ描く: ElementsPanel (overlay と別枠)
new ElementsPanel(layer) + showFile/showJson/showDict で、同じ画面 JSON を Layer のビットマップへ描く。z 順・[trans]・piledCopy・入力の帰属がレイヤの仕組みに従うので、本文の上に被らない HUD / トランジションに乗せたい画面 / KAG レイヤ z 順に混ぜたい画面はこちら (overlay は常に最前面)。
var lay = new Layer(win, win.primaryLayer);
lay.setImageSize(400, 300); lay.setSizeToImageSize();
lay.type = ltAlpha; lay.hitThreshold = 0; lay.visible = true;
var panel = new ElementsPanel(lay);
panel.onAction = function(id, payload) { ... };
panel.showFile("ui/hud.jsonc");
// 入力はレイヤから手で流す (座標はレイヤ local をそのまま)
lay.onMouseDown = function(x, y, b, f) { panel.mouseDown(x, y, b, f); };
lay.onMouseUp = function(x, y, b, f) { panel.mouseUp(x, y, b, f); };
lay.onMouseMove = function(x, y, f) { panel.mouseMove(x, y, f); };
- イベント / 変数 API は ElementsDialog と同形 (
onAction/onDrag/onVar/onClose/setVar/getVar/listVars/watchVars/focus/activate)。既存ドライバがほぼそのまま載る。 - 持っていないもの: モーダル結果 / navigator フロー / 仮想キーボード / ホストホットキー表 /
Agent.dialogs()にも現れない。1 パネル=1 画面、切替は showJson 呼び直し (クロスフェードはレイヤ 2 枚+[trans])。 - キー/パッドは既定で受けない。効かせたい画面だけ
keyDown/keyUp/textを流す (戻り値=消費したか)。 - レイヤの画像サイズを変えたら
notifyResized()。invalidate()は明示全面再描画 (通常不要)。 - テーマ / 表示言語 /
registerImageの mem:// ストアはプロセス全体で共有 (パネルにも即時反映)。
4. イベント (ElementsDialog をサブクラスして override)
class D extends ElementsDialog {
function onAction(id, payload) { /* 値変化 / click。payload=値(buttonはvoid) */ }
function onClose(action) { /* teardown 完了。action=閉じた button id(外部要因は空) */ }
function onScreen(name) { /* フロー: 画面 enter */ }
function onScreenLeave(name, act) { /* フロー: 画面 leave */ }
function onDrag(e) { /* ドラッグ。e=%[id,phase,x,y,dx,dy,startX,startY,modifiers] */ }
}
onActionは 全 button click と state widget 値変化で発火(TVPPostEvent 経由)。onDragは画面 JSON で"drag_events": trueを書いた widget の 押下→移動→離す で発火。e.phaseは"begin"/"move"/"end"(TJS Dictionary に bool が無いので文字列)。 座標は画面 JSON の座標系。溜まったmoveは最新 1 件へ畳まれる(begin/endは畳まない)。 ⚠ 掴んだ絵をついてこさせるだけならonDragは要らない: widget に"drag_at_var": "名前"を書くと位置が"x,y"で変数へ書かれ、canvas 子の"at_var"に 同じ変数を挿すだけで追従する(C++ 内で完結しフレーム同期)。"drag_bounds": [x,y,w,h]で 可動域も制限できる。onDragは「どこで離したか」等の判断用。- サブクラスは必ず
super.ElementsDialog()を呼ぶ(コンストラクタ)。呼ばないと native インスタンスが未初期化になる。 - ホスト→UI の値反映:
dlg.setVar(name, value)(vars/text_varを subscribe した label が次フレームで更新)。ソフトキーボードの入力表示等。 - フォーカスをプログラムで移す:
dlg.focus(id)(input_box は編集フォーカス=キャレット+text 受理。at_varの park/unpark で画面を組み替えた後の入力先移動用。id の存在確認はしない/非アクティブなら false)。initial_focus / focus_by_id が input_box に「一度クリックするまで」効かなかった問題は修正済み。
5. 複数画面フロー (navigator) と 常駐メニュー
1 つの overlay 上で複数画面 (JSON) を遷移。各画面の top-level "transitions" が「閉じトリガ action id → 次手」を定義:
- target 語彙:
"name"(push・山括弧不要)/"<back>"(pop)/"<replace:name>"/"<stay>"/"<exit>"(または空)。未定義 action は entry=exit / 子=pop にフォールバック。 - 画面遷移する button は
close_on_click:true+transitions。その場で動く(閉じない) button はclose_on_click無し(onActionのみ)。 - 画面切替エフェクト (krkrz overlay 配線済・CPU 合成で全 DrawDevice 同一動作): entry を object 形式にして
%[target:"s2", effect:"fade", duration:300]/%[target:"<back>", effect:"universal", rule:"rule.png", vague:64, duration:500]。ruleはグレースケール画像(値が小さい画素から先に次画面へ)、解決順=宣言した画面の相対→Storages→autopath。未対応 effect / rule 不在は警告+即切替(rule 不在は fade フォールバック)。デモ:src/core/data/elements_flow/。 - 退場(exit)演出: 要素の
"animate"に"on":"exit"を付けると、閉じ/遷移時に演出を再生してから finish する(session 内自動協調)。TJSElementsDialog.close()でも発火(演出完了後に閉じ、transitions は解決せずフロー終了)。"animate"は move/scale/rotate/fade をfrom/to/frames/easingで指定するパーツ演出(トリガon: enter(既定)/focus/select/exit/hover/change)。
// ブロッキング複数画面: マニフェスト or インライン辞書
var r = dlg.showFlow("ui/app.jsonc");
var r = dlg.showFlowScreens(%[ "menu": '{...}', "settings": '{...}' ], "menu");
// 常駐(非ブロッキング)メニュー: 出しっぱなしで背景動作と併存
dlg.startFlow("ui/menu/app.jsonc"); // 即 return(戻り値=起動成否)
// dlg.active … この dlg が今アクティブか(getter) dlg.close() … 閉じる
実例: data/startup.tjs の FlowMenuDialog + data/ui/menu/*.json。
6. 入力・フォーカス・モーダル・複数インスタンス
- 配送優先順位:
最上位ホットキー(System.registerHotKey・ポンプ入口) > モーダル(全消費) > ホストホットキー(ElementsDialog.registerHotKey・バイパス) > フォーカスパネル(handled素通し) > ゲーム。 - 複数インスタンス同時表示 OK(z-order。先頭=最背面/末尾=最前面)。各インスタンスは
modalフラグを持つ。modal=true(showJson 既定/showModal*/showFlow): 全入力を独占(下・ゲームに通さない)。modal=false(startFlow/startFlowScreens、showJson 系は第3引数で指定可): ヒットしない入力は下/ゲームへ素通し。
- 用途 3 態: モーダル
showJson(json)/ 操作パネルshowJson(json, true, false)(キー/パッドがパネルへ届き、未処理分はホストへ素通し。パッド十字=フォーカスナビ/A=決定) / 表示専用 HUDshowJson(json, false)(キーを一切受けない)。 - キーボードフォーカス: modal または
wants_focusの最前面が保持。後から開いた focus-grab が自然に前面、閉じると直前へ戻る。テキスト入力ウィジェット focus 中は grabFocus=false でもキー/テキストが届く(focus_consumes_text フォールバック)。 - 最上位ホットキー
System.registerHotKey(key, mods, callback)/System.unregisterHotKey(key, mods): イベントポンプ入口で照合するので モーダル表示中・テキスト入力中でも効く唯一の層(下の ElementsDialog.registerHotKey より上流)。callback がfalseを返せば消費せず通常 dispatch へ素通し。リピートは消費のみ・up は key のみ照合。モーダルの有無はElementsDialog.modalActive(読取専用・常駐オーバレイは含まない)で判定。WINVER / SDL3 の両ビルドで動く(WINVER は 2026-09-26 に配線)。 - ホストホットキー
ElementsDialog.registerHotKey(key, shift=0, duringTextInput=false)/unregisterHotKey/clearHotKeys: 登録キー(VK_PAD*・VK_RBUTTON 等マウスも同じ空間)はパネルへ渡らずWindow.onKeyDown/onMouseDownへ直行(バイパス方式・専用イベント無し)。テキスト入力中は既定抑止(duringTextInput=trueで有効)。モーダル中は無効。ESC/PgUp/PgDn 等「シェルが必ず受けたいキー」の確保に使う(実例=demolib DemoShell)。 input(top-level)で矢印/パッドナビ(arrow_focus_nav/dpad_mode/shortcuts〔key/pad→id〕/pad_bindings)を設定。既定 bind: A=Enter / B=Esc / X=Shift+Tab / Y=Tab / D-Pad=矢印。"bindings": [{key|pad|mouse|wheel, action}]で named-action を差替("none"=消費して無効化、"passthrough"=消費せずホストへ素通し=常駐オーバレイが「この入力は下のゲームのもの」と宣言する用)。pad 名はフェイス以外に"lb"("l1")/"rb"("r1")/"lt"("l2"/"lt_click")/"rt"("r2"/"rt_click")/"l3"/"r3"/"back"/"start"/"dpad_*"が使える(トリガ 2 つは krkrz 側の VK_PAD7/VK_PAD8 変換が入って初めて届くようになった)。フェイスボタンは刻印("a"/"b"/"x"/"y")と位置("face_south"/"face_east"/"face_west"/"face_north")の 2 系統で、1 押下で両方届く。表示側pad_iconの name も同 2 系統を持つので割り当てと表示は同じ基準で組にする(任天堂系は X/Y の位置が Xbox と逆)。- 一覧は
listで組む(行テンプレート):"rows": N+"row": {…木…}+"row_size"/"pitch"。文字列値の#indexが行番号へ展開され("id": "row#index"/"visible_var": "rhov#index")、text_list_var等を持つ要素には"index"と"index_offset_var"が自動で挿さる(= «窓» になる)。行の中の widget が先にクリックを受け、誰も受けなければ行クリックとしてonAction(id, データindex)。count_varを渡すとデータが無い行は描画も当たりも消える。hover/選択は行ごとフラグ(row_hover_var/row_select_var→visible_varで受ける)か、ホスト側で拾うhover_var/select_var(onVar)。 - スクロールバーは
atlas_scrollbar:"thumb"(9-slice 可)+"track"(省略可)をindex_offset_var(+count/count_var、見え行数はvisible数値 orvisible_count_var〔⚠旧visible_varから改名: 全 widget 共通の表示/非表示キーと衝突するため〕)かvalue_varへ直結。つまみ長は「見えている行数 ÷ 総件数」に比例し、溝クリックでページ送り・ホイール・ドラッグまで内蔵。listと同じindex_offset_varを挿すだけで連動する。両端の増減ボタンはdec/inc(atlas_slider と同キー、送り量=ホイールと同じ)。 - つまみ資材の 9-slice / 1 変数で複数枚:
atlas_slider/atlas_scrollbarの"thumb"は{"rect":…,"insets":[l,t,r,b],"size":[w,h]}でキャップを潰さず伸ばせる。canvas 子は"at_var_offset": [dx,dy,dw,dh]で 1 本のat_varから複数枚(上キャップ+胴+下キャップ等)を動かせる。 - 変数は読める / 変化も拾える:
setVarの逆向きにgetVar(name)(未知は void)、一覧はlistVars()(name/value/usedBy=%[id,kind])。変化通知はonVar(name, value)を実装したダイアログだけに届く(未実装ならコスト無し)。vars_on_hover/vars_on_focus/ slider のvalue_var/drag_at_var/ 一覧のindex_offset_varなど画面側が書いた値も同じ storeなので全部読める → 「絵はホスト側のレイヤ、当たり判定だけ elements」構成で hover をホスト処理へ繋げる。通知は 1 フレーム遅延+同名は最新 1 件へ畳まれる(いまの値が要るならgetVar)。対象を絞るならdlg.watchVars = ["a","b"]("*"=全部 /[]=止める /void=既定)。 - named action の通知形:
bindingsに組込以外の名前を書いたときの通知は、onAction(id, payload)のidが"<action>"固定・付けた名前はpayload(onAction("<action>", "prev_page"))。組込名(accept/cancel/nav_*/focus_*/page_*/scroll_*/none/passthrough)はセッション内で処理されホストへ来ない。id に自分の名前が来ると思って書くとその入力だけ黙って無反応になる(ホイール差し替えで踏みやすい)。 - フォーカス無しから方向キーで入る位置:
input.arrow_focus_enter="first"(既定・収集順の先頭)/"directional"(押した方向の端。右キーなら一番右)。initial_focusを置かない確認ダイアログ向け。 - 複数 ElementsDialog の ownership:
close()は自分のインスタンスだけ閉じる(IsHandlerActive(this)ゲート)。ブロッキング pump も自分の handler で終了判定。
6.5 読み上げ (スクリーンリーダー)
表示中の画面は OS のスクリーンリーダー (ナレーター / VoiceOver / Orca) から何もしなくても読まれる (名前は text / labeled_row.label / group.title 等から、役割は type から自動。入力欄は文字・単語・行単位)。使い方の全体は umbrella の doc/guide/Accessibility.md。
- 画面 JSON の
"a11y"キー (全ウィジェット共通): 文字列=名前の短縮形、辞書=label/label_id(名前) /description/description_id/role("heading"等で上書き) /value_var/live("polite"/"assertive"、text_var の変化を読む) /hidden(子ごと外す)。トップレベル"a11y": {"title"|"title_id"}= 画面名。絵だけのボタン (sprite_button/atlas_*) は文字を持たないので必ず名前を付ける。TJS 辞書から渡すと true が 1 になるが、bool キーは数値も受ける。 - static (クラス全体):
announce(text[, assertive])(任意の文を読ませる。AT 未接続でも受け付ける) /a11yActive(読み取り専用。Windows は窓が前面になるまで偽) /onA11yActiveChanged(active)(クラスへ関数を代入) /a11yMode("auto"/"off") /a11yLabel(根の名前)。 - ゲーム本体 (Layer に描いた UI):
setGameA11y(nodes[, focus])(ノード表を丸ごと渡す。%[id, role, name, value, states, rect=primary 座標, parent, num_*]) +onGameA11yAction(id, action, arg)(本体はフォーカスを動かさない → 状態を変えて setGameA11y を呼び直す) /clearGameA11y()/a11yLayers = true(フォーカス連鎖の Layer を自動で載せる。名前=hint、Layer にa11yName/a11yRole/a11yValue/a11yStates/a11yHidden/onA11yActionを生やして補う)。ゲームのノードはダイアログより下・モーダル中は隠れる。 - ⚠ どれも static なので場面を抜けるときに戻す (
clearGameA11y()/a11yLayers = false/onGameA11yAction = void)。対象はメインウィンドウだけ (showModalJsonの専用窓・2 枚目以降の Window は未対応)。 - 実例:
data/a11y(選択肢を setGameA11y、ボタン類を a11yLayers、パネルの"a11y"キー)。検証は §8 の.a11y/.a11ylog/.a11ydo。
7. ハマりどころ (memory 由来・重要)
- DrawDevice 登録タイミング: 起動直後(初回フレーム前)は overlay renderer 未登録で
showJsonが false を返すことがある。初回表示はWindow.onContinuousHandler等へ遅延し、成功するまでリトライする(data/demolib参照)。[[reference_elements_dialog_drawdevice_timing]] - GL DrawDevice 上で出ない: 提示中デバイスへ host 解決が追従していないと GL 画面上でパネルが出ない/操作不可。エンジン修正済だが、デモ側の「パネル再試行が自壊(shellClosePanel が pending を消す)」に注意。[[reference_elements_on_gl_drawdevice]]
- サイズ:
sizeは上限でfit-to-content。全画面化はできない(BeginScreen 実装のきめうち有り)。size の peek が widget "size" 属性に誤爆する既知点あり。[[reference_elements_overlay_dialog_sizing]] - フォーカス奪取 / 共通ホットキー: ✅解決済(2026-08-11)。操作パネルは
showJson(json, true, false)+ホスト必須キーをElementsDialog.registerHotKeyで確保(§6)。旧回避策の grabFocus=false は表示専用 HUD 用。[[project_elements_global_shortcut]] - サブクラスは
super.ElementsDialog()必須。showFileは autopath 未対応な場面あり(相対解決に注意)。 - サブクラス内から static を触るときは
global.ElementsDialog.xxx。素のElementsDialogは親クラス参照になりElementsDialog.focusRing = false等が「メンバが見つかりません」で落ちる。 - 入力/ナビ/部分再描画の切り分けには
-navlog(指定するだけで有効)。フォーカス移動・cursor-warp・パッド方向キーの到着を ms 付きで、100ms 超フレームの段別内訳(slow frame)、ラスタ 1 回ごとの「部分にできたか/できなかった理由」(raster partial=/no-partial:)が出る。計測画面=data/elements_audit(-audittestで自動巡回、-ignoremouse=yesを併用すると実マウスに邪魔されない)。 - 描画が重いと感じたらまず
ElementsDialog.renderStatsの差分を見る(renderCountがアイドルで増えていないかも)。data/elements_benchに更新パターン別の計測画面がある。overlay 描画の支配項はテキストのラスタライズで、同内容のテキストは自動でビットマップキャッシュされる(毎フレーム内容が変わる HUD カウンタは意図的に載らない)。 - case-name 規約: 共有/公開リポ(krkrz_dev/elements)に案件固有名を書かない。elements リポのコメント等はホストを「SDL を使うホストアプリ」等の汎用表現で。[[feedback_elements_repo_project_agnostic]] [[feedback_no_case_names_in_shared_repo]]
8. 動作検証 (Agent / REPL)
GUI は krkrz-repl skill のファイルチャネル + Agent API で駆動・目視できる:
Agent.dialogs()— アクティブダイアログ配列(index/modal/active/screen/rect)。Agent.dialogClick(i, id)— id 指定でボタン起動(座標不要・確実)。Agent.click(x,y)は座標。Agent.text(str)— アクティブダイアログへテキスト入力(input_box)。Agent.captureScreen(path)— overlay 込み実画面 PNG(次フレーム)。→ Read で目視。- ドットコマンド:
.dlg/.dlgclose/.click X Y/.cap。 - 読み上げ:
.a11y(読み上げツリー JSON =Agent.a11yTree()) /.a11ylog [N](おおよそ何と読むか) /.a11ydo <node> <action> [arg](AT と同じ経路で操作 =Agent.a11yAction) /.say <text>。スクリーンリーダー無しで確かめられる。 - 検証時の注意: REPL eval は式評価。永続変数は
global.x = ...。多重 primary layer は不可(2枚目以降はnew Layer(win, primary))。詳細は skillkrkrz-repl。
9. 実装場所 (深掘り時)
- TJS
ElementsDialogバインド:common/visual/elements/DialogIntf.cpp(showJson/showDict/showFlow/startFlow/close/onAction/onClose/setVar/registerFont/active/defaultFontFamily/language/focusRing/renderCache/partialRedraw/renderScale/renderStats等)。 - マネージャ:
common/visual/elements/(tTVPElementsDialogManager= z-order インスタンスリスト)。 - レンダラ提供口:
iTVPDialogRenderer/iTVPDialogRendererHost(tp_stub 公開)。SDL=SDLDialogRenderer/ OGL=OGLDialogRenderer/ WINVER=D3D11DialogRenderer/ 独立窓=WinElementsModalRunner。 - JSON→UI: elements_modal(
json_layout.cpp/navigator/overlay_session)。プラグイン向け C ABI:tp_dialog_service.h。 - 関連 doc:
doc/Gamepad.md(pad↔key)、doc/D3D11Migration.md(DestRect)、external/elements/external/elements_modal/README.md。
関連 skill
tjs2(TJS 言語)/krkrz(本体クラス API)/krkrz-repl(REPL・Agent 駆動・captureScreen)。
レビュー
まだレビューはありません。使ってみた感想をお寄せください。
同じリポジトリのスキル
概要と使いどころ
吉里吉里Z (krkrz) の SDL3 / WINVER ビルドを REPL 経由でエージェントから駆動するためのリファレンス。krkrz を起動して TJS スクリプトを評価・検証・デバッグする、startup.tjs を介さず明示的に処理を開始する、入力イベント (キー/マウス) を注入する、画面をキャプチャして目視確認する、Elements ダイアログを観測・操作する、例外やダイアログ表示をコンソールで観測する、コアデモ全シーンを自動巡回してキャプチャで表示確認する (-demotest / -demotestcap)、といった場面で使う。**外部エージェントは console(CONIN$) に打てないので -replfile ファイルチャネルが本命**。起動フラグ (-repl / -replfile / -nostartup / -loglevel / -display / -ignoremouse)、ファイルチャネルのプロトコル、Agent API (入力注入 / captureScreen / dialogs / dialogClick)、ドットコマンド (.cap/.dlg/.click/.mem 等)、REPL 駆動時の挙動変更 (例外で即終了しない / inform と例外ダイアログがコンソールに出る) を網羅。TJS2 言語仕様そのものは skill `tjs2`、本体クラス API は skill `krkrz` を参照。
吉里吉里Z (krkrz) の -replweb HTTP+SSE サーバ (WebServer クラス) にブラウザ UI を載せて、本体アプリの操作/編集/観測パネルをブラウザ側に組み込む方法論。ゲーム本体は 3D 表示やゲーム内 UI に専念させ、編集ツール・インスペクター・ダッシュボード・REPL コンソールをブラウザ (別ウィンドウ/別PC) から使う構成を作るときに読む。プラグインや TJS がサーバへエンドポイントを追加公開する手順 (WebServer.register / serveStatic / broadcast)、ハンドラ呼び出し規約 (req %[method,path,query,body,bytes] → 文字列/octet/整数/辞書)、状態同期パターン (fetch POST + SSE /sub push + throttle + 差分/tick 配信)、既存 REPL コンソール (/events + /cmd) の UI 埋め込み、ブラウザのアプリモード起動 (Chromium --app / -webui)、json.dll 連携、TJS2 由来のハマりどころ (ローカル関数クロージャ不在→クラス化 / ブロックコメントのネスト誤爆 / startup 例外の致命性 / ハンドラは必ずメインスレッド実行) を網羅。エンジン側 WebServer クラスそのものの仕様は krkrz core doc/REPL.md、REPL/Agent 駆動は skill krkrz-repl、Elements ネイティブ UI は skill elements、TJS2 言語は skill tjs2、本体 API は skill krkrz を参照。
TJS2 (吉里吉里Z 内蔵スクリプト言語) の言語仕様と組み込みクラスのリファレンス。.tjs ファイル / *.ks (KAG) 内の埋め込みスクリプト / TJS2 コード断片 を扱う、書く、レビューする、デバッグするときに使う。JavaScript / TypeScript に似ているが文法と意味論が違うので、JS の感覚で書くと壊れる場合が多い。組み込みクラス (Array / Dictionary / Date / Math / RegExp / Exception) の API もここに集約。**呼び出されたら必ず「JS との主な違い」セクションを最初に確認し、その上で必要な詳細リファレンスを Read で取りに行くこと。** 吉里吉里Z 本体のクラス API (Window / Layer / System / Storages / Bitmap 等) や engine 内部はこのスキルの対象外。