mcpField
M
makepad-2.0-troubleshooting

makepad-2.0-troubleshooting

github.com/zhanghandong開発75254 回インストール29 週間前に更新
リポジトリ

makepad-2.0-troubleshooting について

Makepad 2.0 と Splash を使う開発者向けのトラブルシューティングスキルです。症状、原因、修正方法を例とともに整理し、レイアウト、描画、構文解析、ウィジェット登録の問題を扱います。

主な機能

  1. 01症状別の問題整理
  2. 02根本原因の説明
  3. 03修正コード例
  4. 04レイアウトと描画の注意点
  5. 05Splash の解析・登録問題

活用シーン

  1. 01コンテナの高さによる UI 非表示の診断
  2. 02背景によるテキスト隠れや切れの修正
  3. 03Splash の上書きや色解析エラーの調査
  4. 04ウィジェット登録順の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/zhanghandong/makepad-skills --skill makepad-2.0-troubleshooting

インストール後、Claude Code で/makepad-2.0-troubleshootingを入力して読み込まれたか確認します。

同梱ファイルreferences/error-solutions.md

詳細

Makepad 2.0 のよくある落とし穴とトラブルシューティングガイド

このスキルでは、Makepad 2.0 と Splash スクリプト言語を使用して開発する際によくある間違いを取り上げます。各落とし穴について、次の内容を説明します。

  • ユーザーから見える現象
  • 発生する理由(根本原因)
  • 修正方法(正しいコード)

参考ドキュメント:AGENTS.md、splash.md


落とし穴 #1:コンテナの高さが 0px になり、UI が表示されない

現象: UI 全体、またはその一部が表示されません。コンテナの高さが 0 になり、すべての子要素が見えなくなります。

根本原因: View ベースのコンテナ(View、SolidView、RoundedView など)は、すべてデフォルトで height: Fill です。Fill コンテナを Fit の親の中(または利用可能な高さが子要素によって決まる状況)に配置すると、循環依存によって高さが 0px になります。親が子に高さを尋ね、子が「親と同じ高さ」と答えるため、結果として高さが 0 になります。

修正方法: 内容に合わせて高さを縮めるコンテナには、必ず height: Fit を設定します。

// WRONG -- height defaults to Fill, resolves to 0px in a Fit context
View{
    flow: Down
    Label{text: "Hello"}
}

// CORRECT -- height: Fit makes the container wrap its children
View{
    height: Fit
    flow: Down
    Label{text: "Hello"}
}

経験則: 固定の高さを持つ親がある場合、または固定サイズの祖先要素の中で明示的に height: Fill を指定したい場合を除き、すべてのコンテナの開始波括弧の直後に height: Fit を記述してください。

例外: 高さが固定された親の中では、height: Fill を使用できます。

View{
    height: 300
    View{
        height: Fill
        Label{text: "I fill the 300px"}
    }
}

落とし穴 #2:色付き背景の上でテキストが見えない — new_batch がない

現象: 背景色のある RoundedView または SolidView の中に Label を追加しても、テキストが見えません。コンテナは正しく着色されていますが、draw_text.color にコントラストのある色を設定していても、テキストが表示されません。

根本原因: Makepad は GPU のパフォーマンスを高めるため、シェーダーの種類ごとに描画呼び出しをバッチ処理します。同じテキストシェーダーを使用するすべての Label ウィジェットは 1 つの描画呼び出しにまとめられ、背景も別の描画呼び出しにまとめられます。new_batch: true がないと、テキストの描画呼び出しが背景の描画呼び出しより先に実行され、テキストが不透明な背景の奥に隠れることがあります。

修正方法: 表示可能な背景(show_bg: true または SolidView、RoundedView などのスタイルがあらかじめ設定されたビュー)を持ち、テキストの子要素を含む View ベースのコンテナには、new_batch: true を追加します。

// WRONG -- text is drawn behind the background due to batching
RoundedView{
    height: Fit
    draw_bg.color: #333
    Label{text: "Can't see me"}
}

// CORRECT -- new_batch forces background to draw before children's text
RoundedView{
    height: Fit
    new_batch: true
    draw_bg.color: #333
    Label{text: "Now visible" draw_text.color: #fff}
}

new_batch: true を必ず使用する場合:

  • テキストを含む、show_bg: true(または SolidView、RoundedView などのスタイルがあらかじめ設定されたビュー)のコンテナ
  • 背景アニメーターを持つホバー可能な項目。設定しないと、ホバー時にテキストが消えます
  • それぞれが独自の背景を持つ項目を繰り返し配置する親コンテナ

落とし穴 #3:名前付き子要素のオーバーライドが機能しない — := ではなく : を使用している

現象: let でテンプレートを定義し、インスタンスごとに子要素のプロパティをオーバーライドしようとしても、変更が黙って無視されます。常にデフォルトのテキストが表示されます。

根本原因: Splash では、: は静的プロパティを作成し、:= は参照やオーバーライドが可能な名前付き/動的な子要素を作成します。label: Label{...}(: を使用)と宣言した場合、その子要素には参照可能な名前がないため、オーバーライドのパス label.text: から見つけることができません。

修正方法: 後から参照またはオーバーライドする子要素には、:= を使用します。

// WRONG -- static child, override fails silently
let Card = View{
    height: Fit
    title: Label{text: "default"}
}
Card{title.text: "new text"}  // Fails! title is not addressable

// CORRECT -- named child with :=, override works
let Card = View{
    height: Fit
    title := Label{text: "default"}
}
Card{title.text: "new text"}  // Works! title is a named child

追加ルール: 匿名コンテナ内の名前付き子要素にはアクセスできません。ルートから子要素までのパス上にあるすべてのコンテナにも名前を付ける必要があります。

// WRONG -- label is inside an anonymous View, unreachable
let Item = View{
    height: Fit
    View{
        flow: Down
        label := Label{text: "default"}
    }
}
Item{label.text: "new"}  // Fails! No path to label through anonymous View

// CORRECT -- full named path
let Item = View{
    height: Fit
    texts := View{
        flow: Down
        label := Label{text: "default"}
    }
}
Item{texts.label.text: "new"}  // Works! Full dot-path through named containers

落とし穴 #4:文字「e」を含む 16 進数カラーが正しく表示されない、または解析エラーになる

現象: #2ecc71 のような 16 進数カラーを指定すると、expected at least one digit in exponent のような不可解な解析エラーが発生したり、色が正しく表示されなかったりします。

根本原因: script_mod!{} 内の Rust トークナイザーは、数字の後に続く e を科学表記数値の開始として解釈します(たとえば、2e は 2 * 10^... のように解釈されます)。そのため、数字の隣に文字 e を含む 16 進数カラーの解析が失敗します。

修正方法: 文字 e または E を含む 16 進数カラーには、#x プレフィックスを使用します。

// WRONG -- parser reads '2e' as scientific notation exponent
draw_bg.color: #2ecc71
draw_bg.color: #1e1e2e
draw_bg.color: #4466ee

// CORRECT -- #x prefix escapes the hex literal
draw_bg.color: #x2ecc71
draw_bg.color: #x1e1e2e
draw_bg.color: #x4466ee

#x が不要なのはどんな場合ですか? 文字 e を含まないカラーは、通常の # で問題なく動作します。

draw_bg.color: #ff4444    // OK -- no 'e'
draw_bg.color: #44cc44    // OK -- no 'e'
draw_bg.color: #333       // OK -- no 'e'

Pitfall #5:border_radius の型が違います — Inset ではなく float が必要です

症状: Inset を使って角ごとに境界線の半径を設定しようとすると、解析エラーが発生するか、レイアウトが気付かないうちに崩れます。角丸が表示されません。

根本原因: 境界線の半径は、すべての角に一様に適用される単一の f32 値です。角ごとの値を持つ Inset のような構造体ではありません。Inset やオブジェクトを渡すと、レイアウト全体が気付かないうちに崩れます。

重要: プロパティ名はコンテキストによって異なります。

  • Canvas Splash(POST /splash): 末尾にドットが付いた float を使う draw_bg.radius
  • script_mod! マクロ: draw_bg.border_radius を使います

修正方法: 正しいプロパティ名で、単純な float 値を使います。

// WRONG -- border_radius is not an Inset
draw_bg.border_radius: Inset{top_left: 10 top_right: 10}

// WRONG -- not an object
draw_bg.border_radius: {top: 10 bottom: 0}

// CORRECT (Canvas Splash context) -- use draw_bg.radius with trailing dot
draw_bg.radius: 10.

// CORRECT (script_mod! context) -- use draw_bg.border_radius
draw_bg.border_radius: 10.0

// For per-corner radii, use RoundedAllView with a vec4
// (top-left, top-right, right-bottom, left-bottom)
RoundedAllView{
    height: Fit
    draw_bg.border_radius: vec4(10.0 10.0 0.0 0.0)
}

Pitfall #6:Rust からウィジェットが見つからない — 登録順が間違っています

症状: 実行時にウィジェットの型が見つからない、またはウィジェットが登録されていないというスクリプトエラーが発生します。アプリがパニックするか、何も表示されないことがあります。

根本原因: Makepad 2.0 では、ウィジェットモジュールを正しい順序で script_mod(vm) 呼び出しによって登録する必要があります。カスタムウィジェットより先に基本ウィジェットを登録し、それらを使用する UI より先にカスタムウィジェットを登録する必要があります。順序が間違っていると、モジュールが未登録のウィジェット型を使用しようとします。

修正方法: App::run() で正しい登録順に従います。

impl App {
    fn run(vm: &mut ScriptVm) -> Self {
        // 1. Register base widget library (theme + all standard widgets)
        crate::makepad_widgets::script_mod(vm);

        // 2. Register your custom widget modules (if any)
        crate::my_custom_widgets::script_mod(vm);

        // 3. Register your app UI module (uses widgets from steps 1 and 2)
        crate::app_ui::script_mod(vm);

        // 4. Create the app from its own script_mod
        App::from_script_mod(vm, self::script_mod)
    }
}

重要なルール: ウィジェットモジュールは、それを使用する UI モジュールより先に登録する必要があります。必ず app_ui::script_mod より先に lib.rs::script_mod を呼び出してください。


Pitfall #7:Filler によってテキストが切れます — width: Fill の兄弟要素と併用しています

症状: 水平方向のレイアウトでテキストが途中で切れます。テキストラベルに割り当てられた幅が、利用可能な幅の半分しかないように見えます。

根本原因: Filler{} は View{width: Fill height: Fill} と定義されています。width: Fill を使う兄弟要素の隣に置くと、両方が残りの水平スペースを取り合い、50/50 に分割します。その結果、テキストラベルの幅が半分になり、テキストが切れます。

修正方法: 兄弟要素がすでに width: Fill を使っている場合は、Filler{} を削除します。Fill の兄弟要素が自然に残りのスペースをすべて占め、Fit サイズの兄弟要素を端に押し出します。

// WRONG -- Filler splits space with Fill sibling, text is clipped
View{
    flow: Right height: Fit
    Label{width: Fill text: "Long text that gets clipped"}
    Filler{}
    Button{text: "OK"}
}

// CORRECT -- width: Fill on label pushes button to the right edge
View{
    flow: Right height: Fit
    Label{width: Fill text: "Long text now has full space"}
    Button{text: "OK"}
}

// CORRECT use of Filler -- between Fit-sized siblings
View{
    flow: Right height: Fit
    Label{text: "left"}
    Filler{}
    Label{text: "right"}
}

Pitfall #8:ホバー時にテキストが消えます — new_batch のないアニメーション付き View

症状: リスト項目やボタンにホバー効果があります。上にカーソルを置くと背景色は変わりますが、テキストが完全に消えます。カーソルを離すとテキストが戻ります。

根本原因: ホバーアニメーターによって、View の背景が透明(#0000)から不透明または半透明の色に変わります。new_batch: true がない場合、背景とテキストは同じ描画バッチに含まれます。背景が不透明になると、同じバッチ内で描画されたテキストを覆ってしまいます。

修正方法: show_bg: true を設定し、ホバーアニメーターがあり、テキストを含む View には new_batch: true を追加します。

// WRONG -- text disappears when hover activates the background
View{
    width: Fill height: Fit
    show_bg: true
    draw_bg +: {
        color: uniform(#0000)
        color_hover: uniform(#fff2)
        hover: instance(0.0)
        pixel: fn(){
            return Pal.premul(self.color.mix(self.color_hover, self.hover))
        }
    }
    animator: Animator{
        hover: {
            default: @off
            off: AnimatorState{
                from: {all: Forward {duration: 0.15}}
                apply: {draw_bg: {hover: 0.0}}
            }
            on: AnimatorState{
                from: {all: Forward {duration: 0.15}}
                apply: {draw_bg: {hover: 1.0}}
            }
        }
    }
    Label{text: "Vanishes on hover!" draw_text.color: #fff}
}

// CORRECT -- add new_batch: true
View{
    width: Fill height: Fit
    new_batch: true
    show_bg: true
    draw_bg +: {
        color: uniform(#0000)
        color_hover: uniform(#fff2)
        hover: instance(0.0)
        pixel: fn(){
            return Pal.premul(self.color.mix(self.color_hover, self.hover))
        }
    }
    animator: Animator{
        hover: {
            default: @off
            off: AnimatorState{
                from: {all: Forward {duration: 0.15}}
                apply: {draw_bg: {hover: 0.0}}
            }
            on: AnimatorState{
                from: {all: Forward {duration: 0.15}}
                apply: {draw_bg: {hover: 1.0}}
            }
        }
    }
    Label{text: "Stays visible on hover!" draw_text.color: #fff}
}

Pitfall #9:Splash のコンマ — 許容されますが、必須ではありません

症状: script_mod! ブロック内のプロパティ間にコンマを使えるかどうかが分かりません。

根本原因: Splash では、主に空白で区切ります。ただし、Splash トークナイザーはコンマを空白として扱うため、コンマはそのまま読み飛ばされ、解析エラーにはなりません。Robrix を含む多くの Makepad プロジェクトでは、コンマが必須だった Makepad 1.x の live_design! 構文を引き継ぎ、script_mod! ブロック内でコンマを多用しています。

指針: どちらのスタイルも有効です。周囲のコードのスタイルに合わせてください。

// Style A: with commas (common in codebases migrated from 1.x)
View{
    flow: Down,
    height: Fit,
    spacing: 10,
    padding: Inset{top: 5, bottom: 5, left: 10, right: 10}
}

// Style B: without commas (pure Splash style)
View{
    flow: Down
    height: Fit
    spacing: 10
    padding: Inset{top: 5 bottom: 5 left: 10 right: 10}
}

注: どちらも同じようにコンパイルおよび実行されます。トークナイザーはコンマを破棄します。既存のコードからコンマを取り除くことに時間を使わないでください — 機能上の変更がないのに差分が増えるだけです。


Pitfall #10:セミコロンの使用 — Splash では無効です

症状: 解析エラーや予期しない動作が発生します。セミコロンがプロパティ値の一部として扱われたり、トークナイザーのエラーを引き起こしたりします。

根本原因: Splash では文の終わりにセミコロンを使いません。CSS、JavaScript、Rust の経験がある開発者によくある間違いです。

修正方法: すべてのセミコロンを削除します。

// WRONG -- semicolons are not valid
View{
    flow: Down;
    height: Fit;
    Label{text: "Hello";};
}

// CORRECT -- no semicolons needed
View{
    flow: Down
    height: Fit
    Label{text: "Hello"}
}

落とし穴 #11:ルートコンテナに width: Fill がない — 幅が狭い、またはレイアウトが崩れる

症状: UI がウィンドウの片側に細長く表示されるか、レイアウト全体が崩れます。コンテンツがウィンドウの利用可能な幅いっぱいに広がりません。

根本原因: 最外部のコンテナに固定ピクセル幅(例:width: 400)を指定すると、ウィンドウの利用可能なスペースに合わせて調整されません。ウィンドウが広い場合、コンテンツは細い帯状に表示されます。ウィンドウが狭い場合、コンテンツが切り取られます。

修正方法: ルートコンテナには必ず width: Fill を使用してください。内部要素には固定ピクセル幅を指定しても問題ありません。

// WRONG -- fixed width on root, does not adapt to window
RoundedView{
    width: 400
    height: Fit
    flow: Down
    Label{text: "Narrow!"}
}

// CORRECT -- root fills available width
RoundedView{
    width: Fill
    height: Fit
    flow: Down
    Label{text: "Full width!"}
}

落とし穴 #12:Label は Animator に対応していない — 設定が黙って無視される

症状: Label に animator: Animator{...} と cursor: MouseCursor.Hand を追加しても、ホバー時に何も起こりません。エラーも効果もありません。

根本原因: Label(および H1~H4、P、TextBox、Image、Icon、Markdown、Html、Slider、DropDown、Splitter、Hr、Filler)には animator フィールドがありません。追加しても黙って無視されます。

修正方法: Label を、animator に対応している View でラップしてください。

// WRONG -- animator on Label is silently ignored
Label{
    animator: Animator{
        hover: {
            default: @off
            off: AnimatorState{ from: {all: Forward{duration: 0.15}} apply: {draw_text: {hover: 0.0}} }
            on: AnimatorState{ from: {all: Forward{duration: 0.15}} apply: {draw_text: {hover: 1.0}} }
        }
    }
    text: "Hover me"
}

// CORRECT -- animate the wrapping View
View{
    width: Fill height: Fit
    new_batch: true
    cursor: MouseCursor.Hand
    show_bg: true
    draw_bg +: {
        color: uniform(#0000)
        color_hover: uniform(#fff2)
        hover: instance(0.0)
        pixel: fn(){ return Pal.premul(self.color.mix(self.color_hover, self.hover)) }
    }
    animator: Animator{
        hover: {
            default: @off
            off: AnimatorState{ from: {all: Forward{duration: 0.15}} apply: {draw_bg: {hover: 0.0}} }
            on: AnimatorState{ from: {all: Forward{duration: 0.15}} apply: {draw_bg: {hover: 1.0}} }
        }
    }
    Label{text: "Hover me" draw_text.color: #fff}
}

animator に対応しているウィジェット: View、SolidView、RoundedView、ScrollXView、ScrollYView、ScrollXYView、Button、ButtonFlat、ButtonFlatter、CheckBox、Toggle、RadioButton、LinkLabel、TextInput


落とし穴 #13:デフォルトのテキスト色が白 — 明るい背景では見えない

症状: ウィジェットツリーにはテキストが存在するのに、見えません。白または明るい色の背景では、テキストがまったく見えません。テキストを選択するか、背景を暗い色に変更すると表示される場合があります。

根本原因: すべてのテキストウィジェット(Label、H1~H4、P、Button のテキストなど)は、デフォルトでテキスト色が白(#fff)です。明るい背景では、白いテキストは見えません。

修正方法: 明るい背景上のテキスト要素には、draw_text.color を明示的に暗い色に設定してください。

// WRONG -- white text on white background
View{
    height: Fit
    show_bg: true
    draw_bg.color: #fff
    Label{text: "Can't see this"}
}

// CORRECT -- explicit dark text color
View{
    height: Fit
    new_batch: true
    show_bg: true
    draw_bg.color: #fff
    Label{text: "Visible!" draw_text.color: #333}
}

落とし穴 #14:シェーダーのピクセル関数に Pal.premul() がない — 色の描画が正しくない

症状: 半透明の色が明るく、不透明に、または白っぽく描画されます。#ffffff08 のような淡い色合いが、ほぼ透明ではなく明るい白に見えます。

根本原因: Makepad は GPU 描画にプリマルチプライドアルファを使用します。pixel: fn() から色を返す際、アルファをプリマルチプライしないと、アルファブレンディングの結果が正しくなりません。

修正方法: sdf.result を返す場合を除き、最終的に返す色は必ず Pal.premul() でラップしてください。sdf.result はすでに sdf.fill()/sdf.stroke() によってプリマルチプライされています。

// WRONG -- alpha not premultiplied, renders incorrectly
draw_bg +: {
    pixel: fn() {
        return vec4(1.0 0.0 0.0 0.5)
    }
}

// CORRECT -- premultiply alpha
draw_bg +: {
    pixel: fn() {
        return Pal.premul(vec4(1.0 0.0 0.0 0.5))
    }
}

// ALSO CORRECT -- sdf.result is already premultiplied
draw_bg +: {
    pixel: fn() {
        let sdf = Sdf2d.viewport(self.pos * self.rect_size)
        sdf.box(0.0 0.0 self.rect_size.x self.rect_size.y 4.0)
        sdf.fill(#f00)
        return sdf.result
    }
}

// CORRECT -- color mixing with premultiply
draw_bg +: {
    pixel: fn() {
        return Pal.premul(self.color.mix(self.color_hover, self.hover))
    }
}

落とし穴 #15:CSS のプロパティ名を使用する — Splash では無効

症状: プロパティが認識されず、解析エラーが発生したり、プロパティが黙って無視されたりします。

根本原因: Splash は CSS ではありません。プロパティ名には Makepad 独自の命名規則を使用し、ネストされた構造体フィールドにはドット区切りでアクセスします。

修正方法: Makepad のプロパティ名を使用してください。

CSS 名Splash 名
background-colordraw_bg.color
colordraw_text.color
font-sizedraw_text.text_style.font_size
border-radiusdraw_bg.border_radius
border-colordraw_bg.border_color
border-widthdraw_bg.border_size
paddingpadding(同じ)
marginmargin(同じ)
gapspacing
display: flexflow: Right または flow: Down
flex-direction: columnflow: Down
flex-direction: rowflow: Right
justify-content: centeralign: Center
width: 100%width: Fill
width: autowidth: Fit
// WRONG -- CSS property names
View{
    background-color: #333
    font-size: 14
    border-radius: 8px
    gap: 10
}

// CORRECT -- Splash property names
RoundedView{
    height: Fit
    draw_bg.color: #333
    draw_bg.border_radius: 8.0
    spacing: 10
    Label{text: "Hello" draw_text.text_style.font_size: 14}
}

Pitfall #16: 存在しないプロパティを指定する — エラーなしのまま機能しない

症状: 記述したプロパティが反映されません。エラーは発生しませんが、ウィジェットが期待どおりに動作しません。

根本原因: Splash は未知のプロパティを黙って無視します。プロパティ名を推測して(例:background:、font_size:、text_color:、border:、opacity:)指定しても、単に破棄されます。

対処方法: ドキュメントに記載されたプロパティのみを使用してください。不明な場合は、splash.md を確認するか、widgets/src/ で使用箇所を grep してください。

よくある誤ったプロパティと正しい代替表記:

誤ったプロパティ正しい表記
background:draw_bg.color:
font_size:draw_text.text_style.font_size:
text_color:draw_text.color:
border:draw_bg.border_size: + draw_bg.border_color:
opacity:色にアルファ値を使用:#fff8
on_click:Rust の handle_actions() で処理
class:再利用可能なスタイルには let バインディングを使用
id:名前付きインスタンスには := を使用

Pitfall #17: スクリプトの出力に Root{} や Window{} を追加する — ホストレベルのラッパー

症状: ストリーミング/splash コンテキスト用の UI を生成すると、出力が表示されなかったり、エラーが発生したりします。Window ラッパーが重複すると、予期しない動作が起きることがあります。

根本原因: Root{} と Window{} はホストレベルのラッパーで、アプリの script_mod! ブロックで定義されています。body +: {} ブロック内に描画するスクリプトの内容に、これらのラッパーを含めてはいけません。内容は body の中に記述します。

対処方法: スクリプトの出力は、Root{} や Window{} のラッパーを付けず、UI の内容から直接始めてください。

// WRONG -- do not add host-level wrappers in script output
Root{
    Window{
        body +: {
            View{ height: Fit Label{text: "Hello"} }
        }
    }
}

// CORRECT -- just the content
View{
    height: Fit
    Label{text: "Hello"}
}

アプリのコード内(Root と Window を使用するのが正しい場合):

script_mod!{
    use mod.prelude.widgets.*
    load_all_resources() do #(App::script_component(vm)){
        ui: Root{
            main_window := Window{
                window.inner_size: vec2(800 600)
                body +: {
                    // UI content goes HERE
                    View{ height: Fit Label{text: "Hello"} }
                }
            }
        }
    }
}

Pitfall #18: on_render が呼び出されない — render() を明示的に呼び出す必要がある

症状: ウィジェットに on_render を実装しても、呼び出されません。ウィジェットの描画ロジックが実行されません。

根本原因: Makepad は、すべての状況で on_render を自動的に呼び出すわけではありません。ui.widget.render() を呼び出すか、ウィジェットがアクティブな描画ツリーに含まれるようにして、描画を明示的にトリガーする必要があります。

対処方法: ウィジェットに対して render() が呼び出されるようにしてください。通常は、親の描画パスから呼び出します。ウィジェット階層にウィジェットが正しく含まれていること、および handle_event と draw_walk が正しく実装されていることを確認してください。

// Make sure your widget is in the draw tree and draw_walk is implemented
impl Widget for MyWidget {
    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
        cx.begin_turtle(walk, self.layout);
        // ... drawing code ...
        cx.end_turtle_with_area(&mut self.area);
        DrawStep::done()
    }

    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
        // ... event handling ...
    }
}

Pitfall #19: MapView に height: Fill または Fit を指定する — 固定ピクセル高を使用する必要がある

症状: MapView の高さがゼロで描画されたり、不規則に動作したりします。マップ領域が表示されません。

根本原因: MapView には固有のコンテンツ高がありません。height: Fit を指定すると、合わせるコンテンツがないため 0px になります。height: Fill も、特定のコンテキストでは 0px になることがあります(Pitfall #1 と同じ循環依存です)。

対処方法: MapView には必ず固定ピクセル高を指定してください。

// WRONG -- no intrinsic height, resolves to 0px
MapView{
    width: Fill
    height: Fit
}

// WRONG -- may resolve to 0px in a Fit parent
MapView{
    width: Fill
    height: Fill
}

// CORRECT -- fixed pixel height
MapView{
    width: Fill
    height: 500
}

Pitfall #20: Modal が表示されない — Rust から .open(cx) を呼び出す必要がある

症状: スクリプトで Modal を定義しても、表示されません。ダイアログやオーバーレイが見えません。

根本原因: モーダルはデフォルトで非表示です。モーダルウィジェットの参照に対して .open(cx) を呼び出し、Rust のコードからプログラムで開く必要があります。

対処方法: モーダルを表示するタイミングで、Rust のイベントハンドラーから .open(cx) を呼び出してください。

// In script_mod! -- define the modal
my_modal := Modal{
    content +: {
        width: 300 height: Fit
        RoundedView{
            height: Fit
            new_batch: true
            padding: 20 flow: Down spacing: 10
            draw_bg.color: #333
            draw_bg.border_radius: 8.0
            Label{text: "Are you sure?" draw_text.color: #fff}
            confirm_btn := Button{text: "Confirm"}
            cancel_btn := ButtonFlat{text: "Cancel"}
        }
    }
}
// In Rust -- open the modal when a button is clicked
impl MatchEvent for App {
    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        if self.ui.button(ids!(open_modal_btn)).clicked(actions) {
            self.ui.modal(ids!(my_modal)).open(cx);
        }
        if self.ui.button(ids!(cancel_btn)).clicked(actions) {
            self.ui.modal(ids!(my_modal)).close(cx);
        }
    }
}

Pitfall #21: apply_over と script_eval — 2.0 では script_apply_eval! を使用する

症状: apply_over(cx, live!{...}) がコンパイルできない、または動作しません。以前の 1.x ランタイムプロパティ更新の仕組みはなくなっています。

根本原因: Makepad 2.0 では、live! マクロと apply_over が script_apply_eval! マクロに置き換えられました。live_design! システムの古いマクロはもう存在しません。

対処方法: ランタイムのプロパティ更新には script_apply_eval! を使用してください。マクロ内で Rust の式を埋め込むには #(expr) を使用してください。

// OLD 1.x way (WRONG in 2.0)
item.apply_over(cx, live!{
    height: (height)
    draw_bg: {is_even: (if is_even {1.0} else {0.0})}
});

// NEW 2.0 way (CORRECT)
script_apply_eval!(cx, item, {
    height: #(height)
    draw_bg: {is_even: #(if is_even {1.0} else {0.0})}
});

// For colors
let color = self.color_focus;
script_apply_eval!(cx, item, {
    draw_bg: {
        color: #(color)
    }
});

落とし穴 #22:古い derive マクロ -- Live → Script、LiveHook → ScriptHook

症状: derive マクロ Live、LiveHook が不明というコンパイルエラー、またはトレイトが解決できないエラーが発生します。

根本原因: Makepad 2.0 では derive マクロの名前が変更されました。Live は Script に、LiveHook は ScriptHook になりました。古い名前はもう存在しません。

解決方法: すべての derive マクロを新しい名前に更新してください。

// OLD 1.x (WRONG in 2.0)
#[derive(Live, LiveHook)]
pub struct App {
    #[live] ui: WidgetRef,
}

// NEW 2.0 (CORRECT)
#[derive(Script, ScriptHook)]
pub struct App {
    #[live] ui: WidgetRef,
}

// For widgets:
// OLD
#[derive(Live, LiveHook, Widget)]
// NEW
#[derive(Script, ScriptHook, Widget)]

// For animated widgets:
// OLD
#[derive(Live, LiveHook, Widget, Animator)]
// NEW
#[derive(Script, ScriptHook, Widget, Animator)]

こちらも変更されています: DefaultNone derive は廃止されました。None バリアントに #[default] 属性を付けて、標準の #[derive(Default)] を使用してください。

// OLD (WRONG)
#[derive(DefaultNone)]
pub enum MyAction {
    Clicked,
    None,
}

// NEW (CORRECT)
#[derive(Clone, Default)]
pub enum MyAction {
    Clicked,
    #[default]
    None,
}

落とし穴 #23:THEME_COLOR と theme.color -- 古い構文は置き換えられています

症状: (THEME_COLOR_X) でパースエラーが発生するか、認識されません。テーマの値が適用されません。

根本原因: Makepad 2.0 では、括弧付き定数構文 (THEME_COLOR_X) が、theme.color_x によるドットパスアクセスに置き換えられました。同様に、<THEME_FONT> は theme.font_regular になり、テーマへのアクセスにはすべて theme. プレフィックスを使用するようになりました。

解決方法: 古いテーマ参照をすべて theme. プレフィックスを使う構文に置き換えてください。

// OLD 1.x (WRONG in 2.0)
draw_bg.color: (THEME_COLOR_BG_APP)
draw_text.text_style: <THEME_FONT_REGULAR>
padding: (THEME_SPACE_2)

// NEW 2.0 (CORRECT)
draw_bg.color: theme.color_bg_app
draw_text.text_style: theme.font_regular
padding: theme.space_2
古い構文新しい構文
(THEME_COLOR_X)theme.color_x
<THEME_FONT_X>theme.font_x
(THEME_SPACE_X)theme.space_x
(THEME_FONT_SIZE_X)theme.font_size_x

落とし穴 #24:self.match_event(cx, event) の呼び出し忘れ -- MatchEvent が実行されません

症状: MatchEvent の実装にある handle_actions() が一度も呼び出されません。ボタンのクリックやその他のウィジェットアクションが処理されません。

根本原因: MatchEvent トレイトの handle_actions メソッドは、内部で match_event() によって呼び出されます。AppMain::handle_event で self.match_event(cx, event) を呼び出さないと、トレイトのディスパッチが行われません。

解決方法: handle_event で必ず self.match_event(cx, event) を呼び出してください。

// WRONG -- match_event not called, handle_actions never fires
impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        self.ui.handle_event(cx, event, &mut Scope::empty());
    }
}

// CORRECT -- call match_event before or after ui.handle_event
impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        self.match_event(cx, event);  // THIS IS REQUIRED
        self.ui.handle_event(cx, event, &mut Scope::empty());
    }
}

impl MatchEvent for App {
    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        // Now this actually gets called!
        if self.ui.button(ids!(my_button)).clicked(actions) {
            log!("Button clicked!");
        }
    }
}

落とし穴 #25:レンダー間で状態が保持されない -- 適切な状態管理を使用する

症状: あるレンダーパスで設定した変数が、次のフレームでリセットされます。カウンターの値がゼロに戻り、リストの状態が失われ、切り替えた状態が元に戻ります。

根本原因: draw_walk やイベントハンドラー内のローカル変数は、呼び出されるたびに再初期化されます。Splash の let バインディングはテンプレート定義であり、実行時に変更可能な状態ではありません。状態を保持するには、ウィジェット構造体の #[rust] フィールドを使うか、アプリ構造体で状態を管理する必要があります。

解決方法: 状態を保持するには、ウィジェットまたはアプリ構造体に #[rust] フィールドを使用してください。

// WRONG -- local variable resets every frame
impl Widget for Counter {
    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
        let count = 0;  // Resets to 0 every frame!
        // ...
        DrawStep::done()
    }
}

// CORRECT -- persistent state in the struct
#[derive(Script, ScriptHook, Widget)]
pub struct Counter {
    #[source] source: ScriptObjectRef,
    #[walk] walk: Walk,
    #[layout] layout: Layout,
    #[rust] count: i32,  // Persists between frames
    #[deref] view: View,
}

impl MatchEvent for App {
    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        if self.ui.button(ids!(increment)).clicked(actions) {
            // State persists because it lives on the App struct
            self.count += 1;
            let label = self.ui.label(ids!(count_label));
            label.set_text(cx, &format!("{}", self.count));
        }
    }
}

クイックリファレンス:診断用の決定木

Problem: UI is invisible / blank screen
  |
  +-- Are containers missing height: Fit?              --> Pitfall #1
  +-- Is text on a colored background?                 --> Pitfall #2 (new_batch)
  +-- Is text white on white?                          --> Pitfall #13
  +-- Is root container using width: Fill?             --> Pitfall #11
  +-- Did you add Root{} or Window{} in script output? --> Pitfall #17

Problem: Text disappears
  |
  +-- On hover?                                        --> Pitfall #8 (new_batch)
  +-- On colored background?                           --> Pitfall #2 (new_batch)
  +-- On light background?                             --> Pitfall #13 (text color)

Problem: Override does not work
  |
  +-- Used : instead of :=?                            --> Pitfall #3
  +-- Named child inside anonymous container?          --> Pitfall #3 (path issue)

Problem: Parse error
  |
  +-- Hex color with letter 'e'?                       --> Pitfall #4 (use #x)
  +-- Using commas?                                    --> Pitfall #9
  +-- Using semicolons?                                --> Pitfall #10
  +-- border_radius with Inset?                        --> Pitfall #5

Problem: Widget behavior not working
  |
  +-- Animator on Label?                               --> Pitfall #12
  +-- Modal not showing?                               --> Pitfall #20
  +-- MatchEvent not firing?                           --> Pitfall #24
  +-- State resetting between frames?                  --> Pitfall #25

Problem: Compilation error
  |
  +-- Derive macro not found?                          --> Pitfall #22 (Script/ScriptHook)
  +-- apply_over / live! not found?                    --> Pitfall #21 (script_apply_eval!)
  +-- Theme constant not found?                        --> Pitfall #23 (theme.xxx)
  +-- Widget type not found at runtime?                --> Pitfall #6 (registration order)

追加の注意事項

Splash 構文のクイックルール
  1. プロパティ間のカンマは省略できます(トークナイザーは空白として扱います)
  2. セミコロンは使用できません
  3. 名前付き/アドレス指定可能な子要素には :=、静的プロパティには : を使用します
  4. 親を置き換えるのではなく親とマージするには +: を使用します
  5. 文字 e を含む16進数カラーには #x プレフィックスを使用します
  6. すべてのコンテナーに height: Fit を指定します(固定高さの親の中にある場合を除く)
  7. ルートコンテナーに width: Fill を指定します
  8. テキストの子要素を持つ背景コンテナーには new_batch: true を指定します
App::run() での登録順序
  1. crate::makepad_widgets::script_mod(vm) -- 基本ウィジェット
  2. カスタムウィジェットモジュール
  3. アプリの UI モジュール
  4. App::from_script_mod(vm, self::script_mod)
Animator をサポートするウィジェット

View、SolidView、RoundedView、ScrollXView、ScrollYView、ScrollXYView、Button、ButtonFlat、ButtonFlatter、CheckBox、Toggle、RadioButton、LinkLabel、TextInput

Animator をサポートしないウィジェット

Label、H1~H4、P、TextBox、Image、Icon、Markdown、Html、Slider、DropDown、Splitter、Hr、Filler


落とし穴 #26:ホットリロードが機能しない -- --hot フラグの指定漏れ、またはファイルウォッチャーの問題

症状: Rust のソースファイル内の script_mod! コードを編集しても、実行中のアプリが更新されません。ホットリロードが発生しません。

根本原因: ホットリロードでは、ファイルウォッチャーを起動するために --hot フラグが必要です。このフラグがないと、アプリは通常どおり実行され、ファイルの変更を監視しません。また、ウォッチャーはパスを正規化し、コンテンツの重複を排除します。変更後に抽出された script_mod のコンテンツが同一の場合、リロードは発生しません。

修正方法:

# Run with hot reload enabled
cargo run -p my-app -- --hot

追加の確認事項:

  • script_mod! ブロックが正しい形式であることを確認します(構文エラーがあると抽出できません)
  • script_mod! 内の生文字列、バイト文字列、コメントが正しく閉じられていることを確認します
  • ウォッチャーは #(...) 内の Rust プレースホルダーの数を追跡します。プレースホルダーの追加や削除には、完全な再ビルドが必要です
  • ファイルパスはプラットフォーム間で正規化されます(Windows のバックスラッシュやシンボリックリンクに対応しています)

落とし穴 #27:WASM サーバーのポート競合 -- 古いロックファイル

症状: cargo makepad wasm run がポート使用中エラーで失敗するか、起動中にハングします。複数の WASM サーバーが競合することがあります。

根本原因: WASM サーバーマネージャーは、PID とポートを記録するワークスペースごとのロックファイルを使用します。クラッシュしたプロセスの古いロックが、新しいサーバーの起動を妨げることがあります。

修正方法:

  • サーバーマネージャーは古いロックを自動的に検出して削除します(PID の生存確認とポートの所有者確認を行います)
  • 自動検出に失敗した場合は、ワークスペース内のロックファイルを手動で削除します
  • 起動ミューテックスにより、設定可能なタイムアウトを使って wasm run の同時起動を防ぎます

診断: サーバーマネージャーは、lsof(Unix)または netstat(Windows)を使用して、ポートの使用状況に関する詳細情報を報告します。


落とし穴 #28:TextInput にフォーカスがないのに IME ポップアップが表示される

症状: どの TextInput にもフォーカスがないのに IME(Input Method Editor)ポップアップウィンドウが表示される、または誤った位置に表示されます。

根本原因: Linux X11 では、適切なフォーカス追跡が行われずに IME イベントがディスパッチされることがありました。この問題は、TextInput ウィジェットがキーボードフォーカスを持っている場合にのみ IME ポップアップを表示するよう修正されました。

修正方法: FingerDown で TextInput が cx.set_key_focus(area) を呼び出すようにします。プラットフォーム層では、フォーカス状態に基づいて IME ポップアップの表示が適切に制御されるようになりました。


落とし穴 #29:Wayland のコンテキストメニューがクラッシュする -- 繰り返しの開閉

症状: Wayland でコンテキストメニュー(ポップアップウィンドウ)を素早く開閉すると、クラッシュまたはプロトコルエラーが発生します。

根本原因: xdg_popup プロトコルでは、適切なグラブセマンティクスが必要です。コンポジターの確認を待たずにポップアップを繰り返し開閉すると、プロトコル違反が発生していました。

修正方法: この問題はプラットフォーム層で修正されています。ポップアップウィンドウはコンポジター主導のグラブを使って xdg_popup を使用し、PopupDismissed イベントを発行するようになりました。アプリでは、明示的なクローズのセマンティクスを実現するために PopupDismissed を処理する必要があります(dismiss ハンドラー内ですぐに再オープンしないでください)。


クイックリファレンスの更新:診断用デシジョンツリー

Problem: Hot reload not working
  |
  +-- Missing --hot flag?                               --> Pitfall #26
  +-- script_mod! has syntax errors?                    --> Pitfall #26
  +-- Content unchanged (same extracted script)?        --> Pitfall #26

Problem: WASM build/server issues
  |
  +-- Port conflict?                                    --> Pitfall #27
  +-- Server hangs on startup?                          --> Pitfall #27
  +-- Binary too large?                                 --> Use --wasm-opt, --split-functions

Problem: Platform-specific issues
  |
  +-- IME popup at wrong time?                          --> Pitfall #28
  +-- Wayland popup crash?                              --> Pitfall #29
  +-- Selection handles not showing (mobile)?           --> Check iOS 16+ / Android JNI bridge

Problem: Splash eval issues (POST /splash, set_text)
  |
  +-- ScrollYView renders blank in eval?                --> Pitfall #30
  +-- Code block has no syntax highlighting?            --> Pitfall #31
  +-- CJK characters overlap in CodeView?              --> Pitfall #32
  +-- Type default properties not inherited?            --> Pitfall #33
  +-- on_after_apply not called for eval widgets?      --> Pitfall #33

落とし穴 #30:Splash eval で ScrollYView が空白になる

症状: POST /splash 経由で ScrollYView{...} を送信すると、空白または空のパネルが描画されます。同じコードは、コンパイル済みの script_mod! では正常に動作します。

根本原因: ScrollYView には内部初期化が必要ですが、Splash ランタイム eval 中には正しく完了しません。スクロールビュー自体は作成されますが、eval コンテキストではビューポートの寸法が正しく設定されません。

修正方法: 代わりに height: Fit を指定した通常の View を使用します。Canvas が Splash パネルを独自のスクロールコンテナーで囲みます。

// WRONG -- blank in Splash eval
ScrollYView{ width: Fill height: Fill flow: Down
    Label{ text: "content" }
}

// CORRECT -- works in eval
View{ width: Fill height: Fit flow: Down
    Label{ text: "content" }
}

落とし穴 #31:Splash eval では Markdown コードブロックに構文ハイライトが適用されない

症状: Markdown{body: "...```rust\n...\n```..."} を POST /splash 経由で送信すると、コードブロックが構文色分けされず、単なる等幅テキストとして表示されます。同じ Markdown をコンパイル済みの script_mod! で使用すると、CodeView によって完全な構文ハイライトが適用されます。

根本原因: Canvas は set_type_default() で mod.widgets.Markdown をオーバーライドし、code_block := View{CodeView{...}} と use_code_block_widget: true を追加しています。しかし、Splash eval 経由で作成されたインスタンスにはこれらが継承されません。理由は、(1) eval のパスでは on_after_apply が呼び出されない、(2) 型デフォルトの vec エントリー(名前付き子要素)がインスタンスに完全にはコピーされないためです。

修正(2026-03-23 適用): widgets/src/markdown.rs が draw_walk 内で vm.bx.heap.type_default_for_object() を介して型デフォルトを遅延検索し、不足しているテンプレートを登録するようになりました。また、code_block テンプレートが見つかった場合は、use_code_block_widget を自動的に有効化します。


落とし穴 #32:CodeView / CodeEditor で CJK 文字が重なる

症状: コードブロック内の中国語、日本語、または韓国語の文字が隣接する文字と重なります。等幅グリッドが全角 CJK グリフを考慮していません。

根本原因: code_editor/src/char.rs の column_count() は、すべての文字に対して 1 を返していました。等幅グリッドでは CJK 文字に 2 列が必要です。

修正(2026-03-23 適用): char.rs が、CJK 統合漢字、ひらがな、カタカナ、ハングル、全角ラテン文字、および関連する Unicode 範囲に対して 2 を返すようになりました。また、3 つすべてのテーマファイルの font_code に、中国語フォールバックとして LXGWWenKaiRegular.ttf を追加しました。


落とし穴 #33:Splash eval では型デフォルトのオーバーライドが継承されない

症状: set_type_default() でウィジェット型をオーバーライドした場合(例:Canvas が Markdown を追加のテンプレートでオーバーライドした場合)、Splash eval(POST /splash)経由で作成されたインスタンスに、オーバーライドのプロパティや名前付き子要素が反映されません。

根本原因: Splash eval のパスでは、ScriptHook::on_after_apply を実行せず、eval 値(例:{body: "..."})だけを適用してウィジェットを作成します。型デフォルト値の代わりに、#[live] フィールドのデフォルト値が使用されます。型デフォルトの vec エントリー(テンプレートなどの名前付き子要素)も、proto のコピー処理中に失われる場合があります。

回避策: draw_walk に遅延初期化を実装し、不足している状態がないか確認したうえで、vm.bx.heap.type_default_for_object(self.source.as_object()) を介して型デフォルトを検索します。これは Markdown が code_block テンプレートに使用しているパターンです。


落とし穴 #34:draw_bg +: 内の instance で「cannot push to frozen vec」エラーが発生する

症状: draw_bg +: { } 内に instance my_var: 0.5 を追加すると、「cannot push to frozen vec」というランタイムクラッシュが発生します。

根本原因: Splash の +: ブロックでは、pixel: fn() をオーバーライドしたり、既存の変数を設定したりできますが、新しいインスタンスフィールドや uniform フィールドを追加することはできません。GPU レイアウトはコンパイル時に決まります。

修正: 組み込み変数(self.draw_pass.time、self.pos、self.rect_size)を使用するか、カスタム Rust DrawQuad 型を作成してください。


落とし穴 #35:透明なウィンドウでも不透明な背景が表示される

症状: window.transparent: true を設定しても、ウィンドウに灰色や暗色の背景が表示されます。

根本原因: window.transparent は、ウィンドウが透明であることを OS に伝えるだけです。レンダーパスは、デフォルトでは不透明色でクリアされます。

修正: 次の両方を設定する必要があります。

window.transparent: true
pass.clear_color: #x00000000    // CRITICAL: fully transparent clear color

また、body +: {} に draw_bg.color を設定しないでください。透明設定が上書きされます。

参考:Makepad ソースの tools/canvas/src/app.rs。


落とし穴 #36:sdf.box() で大きな border_radius を指定すると、ひし形やトゲ状になる

症状: 高さ 56px の RoundedView に border_radius: 28.0 を指定し、width: Fit を設定すると、丸みのあるカプセル形状の端ではなく、先のとがったひし形状の端になります。

根本原因: sdf.box() の式 size.xy - vec2(2*r, 2*r) は、r >= min(w,h)/2 の場合に負の値となり、SDF が破綻します。

修正: カスタムのカプセル SDF を使用してください。let cx = clamp(px, r, max(r, w-r)) を参照してください。makepad-2.0-shaders スキルの「SDF Capsule Pattern」に記載されています。


落とし穴 #37:子ウィジェットが親の SDF マスクからはみ出す

症状: LoadingSpinner やその他の子ウィジェットが、親 View のカスタム SDF カプセル形状の外側に描画され、端にアーティファクトが見えることがあります。

根本原因: 各ウィジェットは独自の描画呼び出しを持ちます。子ウィジェットは親の SDF シェーダーでクリッピングされず、それぞれ独自の矩形内に個別に描画されます。

修正方法: 子ウィジェットを使わず、アニメーションを親の pixel: fn() シェーダーに直接埋め込みます。または、clip_x: true と clip_y: true を使用します(角丸形状では完全には解決しない場合があります)。


落とし穴 #38:show_in_dock(false) によって NSStatusBar 項目が非表示になる

症状: cx.show_in_dock(false) を呼び出すと、アプリは Dock に表示されなくなりますが、NSStatusBar 項目(メニューバーアイコン)も非表示になります。

根本原因: show_in_dock(false) は NSApplicationActivationPolicyAccessory を設定するため、アプリがステータスバー項目を所有できなくなります。

修正方法: 実行時に show_in_dock(false) を呼び出さないでください。代わりに、.app バンドルでは Info.plist に LSUIElement=true を使用します。cargo run の実行中は、Dock アイコンを表示したままにしてください。


落とし穴 #39:絵文字が文字化けしたり、四角形で表示されたりする

症状: 一部の絵文字(✨ ⏳)が Label のテキスト内で四角形や文字化けした文字として表示されます。

根本原因: Makepad のフォントアトラスにはすべての絵文字のグリフが含まれているわけではありません。サポートされるのは基本的な絵文字の一部のみです。

修正方法: 絵文字を一つずつテストしてください。動作確認済み:🎙 🔍 🔄。動作しないもの:✨ ⏳。代替として ASCII 文字を使用してください。


落とし穴 #40:script_apply_eval! が動的に作成されたウィジェットに反映されない

症状: widget_ref_from_live_ptr(cx, Some(ptr))(または cx.with_vm(|vm| WidgetRef::script_from_value(vm, value)))を使ってウィジェットを作成し、script_apply_eval!(cx, item, { draw_bg.color: #(color) }) で実行時にそのプロパティを変更します。呼び出しはエラーなく実行されますが、ウィジェットの外観は変わりません。

根本原因: script_apply_eval! は内部で self.script_source() を呼び出してウィジェットの ScriptObject を取得し、その後、そのオブジェクトに対してスクリプトコードを評価します。LivePtr/ScriptValue のテンプレートから動的に作成されたウィジェットの場合、script_source() はデフォルトの ScriptObject::ZERO を返します。つまり、評価スコープは空であり、有効な対象オブジェクトがないため、プロパティの代入は何も起こらずに失敗します。

これは次のすべての方法に当てはまります:

  • script_apply_eval!(cx, item, { draw_bg.color: #(color) }) — 反映されません
  • script_apply_eval!(cx, item, { draw_bg +: { color: #(color) } }) — 反映されません
  • script_apply_eval!(cx, item, { show_bg: true }) — 反映されません
  • ViewRef.set_uniform(cx, live_id!(color), &[r,g,b,a]) — これも反映されません

修正方法: 代わりに、シェーダーのインスタンス変数とAnimator の状態を使用します。

  1. DSL テンプレートでインスタンス変数とピクセルシェーダーを定義します:
mod.widgets.MyItem = View {
    show_bg: true
    draw_bg +: {
        color: (COLOR_PRIMARY)
        selected: instance(0.0)

        pixel: fn() {
            let sdf = Sdf2d.viewport(self.pos * self.rect_size)
            sdf.box(0. 0. self.rect_size.x self.rect_size.y self.border_radius)
            let highlight = #x1E90FF30
            sdf.fill(Pal.premul(self.color.mix(highlight self.selected)))
            return sdf.result
        }
    }

    animator: Animator {
        highlight: {
            default: @off
            off: AnimatorState {
                from: { all: Forward { duration: 0.12 } }
                apply: { draw_bg: { selected: 0.0 } }
            }
            on: AnimatorState {
                from: { all: Forward { duration: 0.08 } }
                apply: { draw_bg: { selected: 1.0 } }
            }
        }
    }
}
  1. Rust から状態を切り替えます:
let view = item.as_view();
if is_highlighted {
    view.animator_cut(cx, ids!(highlight.on));
} else {
    view.animator_cut(cx, ids!(highlight.off));
}

重要なポイント: Animator の状態が機能するのは、Makepad のアニメーションシステムを介して動作し、GPU バッファ内のシェーダーインスタンス変数を直接変更するためです。これにより、ScriptObject/評価の仕組みを完全に回避できます。


落とし穴 #41:script_apply_eval! では実行時に DSL 定数を使用できません

症状: script_apply_eval! の使用時に、variable Right not found in scope、variable Fit not found in scope、variable Align not found in scope、variable MouseCursor not found in scope などの実行時エラーが発生します。

根本原因: script_apply_eval! は、Right、Down、Fit、Fill、Align、Inset、MouseCursor などの DSL 定数を含まない、制限された実行時スコープでコードを評価します。これらの定数を使用できるのは script_mod! の評価時(コンパイル/初期化時)のみで、実行時には使用できません。

修正方法: レイアウトを変更する場合は、定義時に DSL テンプレートでプロパティを設定します。実行時にレイアウトを変更する必要がある場合は、Rust API を直接使用してください。

// WRONG — DSL constants not available at runtime
script_apply_eval!(cx, item, {
    flow: Right
    height: Fit
    align: Align{y: 0.5}
});

// CORRECT — Use Rust values with #(expr) interpolation
let height = 36.0_f64;
script_apply_eval!(cx, item, {
    height: #(height)
});

// CORRECT — For colors (hex literals work)
let color = vec4(0.1, 0.2, 0.3, 1.0);
script_apply_eval!(cx, item, {
    draw_bg +: { color: #(color) }
});

// CORRECT — Bake layout into DSL template instead
mod.widgets.MyItem = View {
    flow: Right
    height: 36
    align: Align{y: 0.5}
}

script_apply_eval! で使用できるもの: 数値、16 進カラー(#fff)、#(rust_expr) による補間、プロパティパス(draw_bg +: { ... })。

使用できないもの: 型コンストラクター(Align{}、Inset{})、列挙型のバリアント(Right、Down、Fit、Fill)、ウィジェット型の参照(MouseCursor.Hand)。


落とし穴 #42:Dock.load_state() が DrawList の参照を破損させ、描画が真っ白になります

症状: 永続化された状態からドックレイアウトを復元した後(アプリの再起動時など)、ドック領域全体が真っ白または灰色で描画されます。コンソールには Drawlist id generation wrong index: N current gen:1 in pointer:0 というエラーが大量に表示されます。

根本原因: Dock.load_state() はイベント処理中に self.tab_bars.clear() を呼び出し、DrawList2d を含む TabBarWrap インスタンスを破棄します。これにより DrawList プールのエントリーが解放され、世代番号がインクリメントされます。しかし、Makepad の描画パイプラインには、前の描画フレームの世代番号 0 の DrawListId 参照がキャッシュされたままです。次のフレームでこれらの古い参照にアクセスすると、世代番号の不一致が発生します。

修正方法: 永続化された状態の復元に dock.load_state() を呼び出さないでください。代わりに、タブをプログラムで再作成します。

// WRONG — corrupts DrawList references
dock.borrow_mut().load_state(cx, saved_dock_items.clone());

// CORRECT — use normal Dock API
self.close_all_tabs(cx);
for room in &saved_room_order {
    self.focus_or_create_tab(cx, room.clone());
}

トレードオフ: この方法では、カスタムのスプリッター位置や複数ペインのレイアウトは復元されません。これらの機能には、Makepad の Dock ウィジェットへの上流での修正が必要です。


落とし穴 #43:名前付き子要素では、1.0 の DSL テンプレートで =、2.0 では := を使用します

症状: live_design! から script_mod! に移行する際、name = Widget { } で定義された名前付き子要素が、variable name not found in scope のような実行時エラーを引き起こします。子要素はウィジェットツリー内に存在しますが、Rust から ids!(name) で参照できません。

根本原因: Makepad 1.0 の live_design! では、= と := のどちらでも名前付き子要素が作成されます。Makepad 2.0 の script_mod! では、名前付きで参照可能な子要素を作成するのは := のみです。2.0 で = を使用すると、名前付き子ウィジェットではなく、ローカル変数への代入になります。

修正方法:

// 1.0 (live_design!) — both work
avatar = <Avatar> { width: 24, height: 24 }
avatar := <Avatar> { width: 24, height: 24 }

// 2.0 (script_mod!) — ONLY := works for named children
avatar := Avatar { width: 24 height: 24 }  // CORRECT
avatar = Avatar { width: 24 height: 24 }   // WRONG — local variable, not child

落とし穴 #44:DSL と script_apply_eval! では、draw_bg: は置き換え、draw_bg +: はマージします

症状: DSL テンプレートまたは script_apply_eval! で draw_bg: { color: #f00 } を設定すると、ほかのすべての draw_bg プロパティ(border_radius、border_color、ホバー状態、シェーダーコード)が失われます。

根本原因: : 演算子はプロパティ値全体を置き換えます。+: 演算子は既存の値とマージし、指定したサブプロパティのみを上書きします。

修正方法: draw_bg、draw_text、またはその他のネストされた構造体のサブプロパティを変更する場合は、必ず +: を使用してください。

// WRONG — replaces ALL of draw_bg, losing border_radius, shader, etc.
draw_bg: { color: #f00 }

// CORRECT — only changes color, keeps everything else
draw_bg +: { color: #f00 }

// In script_apply_eval!, same rule applies:
// WRONG
script_apply_eval!(cx, item, { draw_bg: { color: #(color) } });

// CORRECT
script_apply_eval!(cx, item, { draw_bg +: { color: #(color) } });

// Dot-path shorthand also works (equivalent to +: merge):
draw_bg.color: #f00
draw_bg.border_radius: 8.0

落とし穴 #45:ポップアップ/メニューが正しくない位置に開く — レイアウトの余白をオーバーレイの位置指定として扱う

症状: ポップアップメニューや言語セレクターをボタンの上に表示したいのに、ボタンの下に表示されたり、間違った角に固定されたり、Y 位置の計算が無視されているように動作したりします。

根本原因: ポップアップをローカルなレイアウトツリー内の通常の子要素として作成し、margin.top/margin.left などのレイアウトオフセットで位置を指定しようとしています。Makepad では、これはあくまでレイアウト上の調整であり、真のオーバーレイアンカーではありません。実際のポップアップ動作を実現するには、通常、ポップアップをトップレベルのオーバーレイ所有者(Modal、ツールチップレイヤー、ポップアップ所有者)に配置し、walk.abs_pos で位置を指定する必要があります。

修正方法: ポップアップの所有権を共通の祖先またはオーバーレイ所有者に移します。button.area().clipped_rect(cx) でトリガーとなるボタンの位置を取得し、オーバーレイの座標空間でポップアップの絶対位置を計算して、popup.walk.abs_pos に設定します。

// WRONG -- local child + layout spacing fights the turtle
script_apply_eval!(cx, popup, {
    margin.left: #(x)
    margin.top: #(y)
});

// CORRECT -- top-level overlay popup with explicit absolute anchor
let button_rect = button.area().clipped_rect(cx);
let popup_pos = dvec2(button_rect.pos.x, button_rect.pos.y - 294.0);

if let Some(mut popup) = self.view(cx, ids!(popup)).borrow_mut() {
    popup.walk.abs_pos = Some(popup_pos);
}

重要な違い:

  • clip_x: false/clip_y: false を指定すると、ローカルな子要素を親要素の外側にも描画できます。
  • ただし、これによって子要素が真のオーバーレイとして動作するわけではありません。

経験則: ポップアップを、それを開いたコンポーネントの外側に表示する必要がある場合は、ポップアップの状態を共通の祖先に移し、その祖先にオーバーレイを所有させます。


診断判断ツリーの更新(追加項目)

Problem: script_apply_eval! has no effect on widget
  |
  +-- Widget created via widget_ref_from_live_ptr()?    --> Pitfall #40 (use Animator)
  +-- Using DSL constants (Right, Fit, Align)?          --> Pitfall #41 (use Rust values)
  +-- Using draw_bg: instead of draw_bg +: ?            --> Pitfall #44 (use merge operator)

Problem: Dock renders blank after state restore
  |
  +-- Called Dock.load_state() in event handler?         --> Pitfall #42 (use tab recreation)

Problem: Named child not addressable from Rust
  |
  +-- Used = instead of := in script_mod!?               --> Pitfall #43 (use :=)

Problem: Popup/menu anchored to button but renders in wrong place
  |
  +-- Built as normal child and moved with margin?       --> Pitfall #45 (use top-level overlay + walk.abs_pos)
  +-- Only disabled clip_x/clip_y on local parent?       --> Pitfall #45 (overflow is not a true overlay)

コメント