makepad/examples/uizoo/src/app.rs
Admin 3b4d6a4ff7 platform+widgets: sploded 3D inspect view, the tweaker design-feedback suite, script math-AOT and docs channel, pointer-pin and pixel-probe, modal layout, map warp
Squashed from work; the fine-grained history is under tag archive/work-2026-08-29:
- mp* wave: mpwm window manager + the mp app family, WM API, theme bridge, PDF engine fix
- mpwm polish wave: terminal key focus, focus-history close order, pop-back-to-origin, occupied-workspace cycling, demo
- mpwm: warm-instance pool, flat-luminance opens, flicker-free CEF resize
- work: land the sources the last commits reference
- kenney: catalogue all 50 free 3D kits; Modal dismissed() never fired
- platform: windows check green again — SetWindowTextW binding
- map: exact warp-aware inverse projections — pointer ops work folded
- mpwm: quick-look gap fixes; image cache eviction on preview unload
- tweaker: material thumbnails + vibecode popup + ctrl-space notes, undo/redo over the edit ledger, capture-semantics pi
- tweaker: vibe popup card chrome + dispatch order, ctrl-space notes verified, sploded design v2 chapter
- tweaker: tabbed side panel (Props/Shader/Tree) - shader tab with checkerboard material well + prompt, complete widget-
- sploded v2: nesting-depth z, hairline scope frames, body pass
- sploded: pin the depth convention with a test, kill the draw_depth residue
- sploded: real body-pass split (scene-pass capture, panel flat) + y-convention source of truth with anti-flip gate test
- sploded: hollow outlines, flat-band input, ray-pick unprojection
- tweaker: shader tab defaults to the selection's first draw layer, stale hint trimmed
- sploded: outlines become clipped, antialiased strips; tighter deck
- sploded: merge the lane's v2 (nesting-depth z, clipped AA strip outlines, flat-band input, unproject, SplodedStack bod
- sploded: the exploded view is a LIVE view — pointer events route through the inverse explode transform (ray -> plane -
- tweaker: tabs are real widgets (uid, tree node under the dock, own plane in 3D) and pickable; navigation-class clicks
- sploded: pinned/hover outlines render on the widget's own plane in 3D — per-widget nesting depth lives on the platform
- tweaker: the material well renders the pinned widget's actual shader — the swatch byte-copies the widget's live draw c
- tweaker: the Shader tab shows the shader as written — the layer's pixel/vertex fn source (nearest definition up the co
- sploded: I = true isometric preset (yaw 45°, pitch atan(1/√2))
- tweaker: eyedropper — the colour popover's pick button arms a pixel probe; the next press in the app samples that devi
- tweaker: the shader loop closes — /tweak/apply resolves the pinned widget by uid (anonymous path segments never round-
- vj: responsive DJ mixer + Windows drag-and-drop, cherry-picked from PR #1199 (vjroger)
- tweaker: the material well is a magnifier — the mirrored instance draws at the widget's native size in the well's own
- tweaker: per-layer material thumbnails — the Widget derive emits WidgetNode::layer_areas() (every #[live] Draw… field
- tweaker: the shader source view is the real CodeView (syntax highlighting, selection, editing) when the app registers
- tweaker: Ctrl+Enter sends on every platform (TextInput treated only Cmd as primary on macOS, so Ctrl+Enter inserted a
- Modal claims no layout slot: the DJ page fills its window again
- tweaker: every fn apply recompiles (eval_chunk ran every chunk under ONE synthetic callsite, so the script body — and
- tweaker: an apply whose draw shader fails to compile is rejected — the layer goes back (last live fns / the fn as writ
- tweaker: the Shader tab's source view owns its scrolling (the ScrollYView around the CodeView double-scrolled the care
- widgets: set_visible belongs to every widget, not just View (#1194)
- script: a dead heap's resource handles must not outlive it (#1195)
- Resources: search the executable's directory, not only the working directory (#1196)
- Windows: fit a restored window to the displays that are actually attached (#1197)
- d3d11: a failing GPU call reports the loss instead of killing the process (#1198)

Co-authored-by: Kevin Boos <1139460+kevinaboos@users.noreply.github.com>
2026-08-29 09:26:24 +02:00

408 lines
15 KiB
Rust

use makepad_widgets::*;
app_main!(App);
script_mod! {
use mod.prelude.widgets.*
use mod.widgets.*
let UIZooTab = RectView{
height: Fill width: Fill
flow: Down
padding: 0
spacing: 0.
}
let AppDock = Dock{
height: Fill width: Fill
root := DockSplitter{
axis: SplitterAxis.Horizontal
align: SplitterAlign.FromA(0.0)
a: @tab_set_1
b: @tab_set_2
}
tab_set_1 := DockTabs{
tabs: [@tab_a]
selected: 0
closable: false
}
tab_set_2 := DockTabs{
tabs: [
@tOverview
@tLayoutDemos
@tButton
@tCheckBox
@tDropDown
@tFiletree
@tSpinner
@tHTML
@tIcon
@tIconSet
@tImage
@tImageBlend
@tGlassPanel
@tLabel
@tSlug
@tLinkLabel
@tMarkdown
@tPageFlip
@tPortalList
@tRadioButton
@tRotary
@tRotatedImage
@tScrollbar
@tSlider
@tSlidesView
@tStackNavigation
@tAdaptiveView
@tTextInput
@tVideo
@tView
@tAlignScroll
]
selected: 0
closable: false
}
tab_a := DockTab{
name: "Welcome"
template: @PermanentTab
kind: @TabOverview
}
tOverview := DockTab{name: "Intro" template: @PermanentTab kind: @TabOverview}
tLayoutDemos := DockTab{name: "Layout Demos" template: @PermanentTab kind: @TabLayoutDemos}
tButton := DockTab{name: "Button" template: @PermanentTab kind: @TabButton}
tCheckBox := DockTab{name: "CheckBox" template: @PermanentTab kind: @TabCheckBox}
tDropDown := DockTab{name: "DropDown" template: @PermanentTab kind: @TabDropDown}
tFiletree := DockTab{name: "FileTree" template: @PermanentTab kind: @TabFiletree}
tSpinner := DockTab{name: "Spinner" template: @PermanentTab kind: @TabSpinner}
tHTML := DockTab{name: "HTML" template: @PermanentTab kind: @TabHTML}
tIcon := DockTab{name: "Icon" template: @PermanentTab kind: @TabIcon}
tIconSet := DockTab{name: "IconSet" template: @PermanentTab kind: @TabIconSet}
tImage := DockTab{name: "Image" template: @PermanentTab kind: @TabImage}
tImageBlend := DockTab{name: "ImageBlend" template: @PermanentTab kind: @TabImageBlend}
tGlassPanel := DockTab{name: "GlassPanel" template: @PermanentTab kind: @TabGlassPanel}
tLabel := DockTab{name: "Label" template: @PermanentTab kind: @TabLabel}
tSlug := DockTab{name: "SLUG" template: @PermanentTab kind: @TabSlug}
tLinkLabel := DockTab{name: "LinkLabel" template: @PermanentTab kind: @TabLinkLabel}
tMarkdown := DockTab{name: "Markdown" template: @PermanentTab kind: @TabMarkdown}
tPageFlip := DockTab{name: "PageFlip" template: @PermanentTab kind: @TabPageFlip}
tPortalList := DockTab{name: "PortalList" template: @PermanentTab kind: @TabPortalList}
tRadioButton := DockTab{name: "RadioButton" template: @PermanentTab kind: @TabRadioButton}
tRotary := DockTab{name: "Rotary" template: @PermanentTab kind: @TabRotary}
tRotatedImage := DockTab{name: "RotatedImage" template: @PermanentTab kind: @TabRotatedImage}
tScrollbar := DockTab{name: "Scrollbar" template: @PermanentTab kind: @TabScrollbar}
tSlider := DockTab{name: "Slider" template: @PermanentTab kind: @TabSlider}
tSlidesView := DockTab{name: "SlidesView" template: @PermanentTab kind: @TabSlidesView}
tStackNavigation := DockTab{name: "StackNavigation" template: @PermanentTab kind: @TabStackNavigation}
tAdaptiveView := DockTab{name: "AdaptiveView" template: @PermanentTab kind: @TabAdaptiveView}
tTextInput := DockTab{name: "TextInput" template: @PermanentTab kind: @TabTextInput}
tVideo := DockTab{name: "Video" template: @PermanentTab kind: @TabVideo}
tView := DockTab{name: "View" template: @PermanentTab kind: @TabView}
tAlignScroll := DockTab{name: "Align+Scroll" template: @PermanentTab kind: @TabAlignScroll}
TabOverview := UIZooTab{WidgetsOverview{}}
TabLayoutDemos := UIZooTab{DemoLayout{}}
TabButton := UIZooTab{DemoButton{}}
TabCheckBox := UIZooTab{DemoCheckBox{}}
TabDropDown := UIZooTab{DemoDropdown{}}
TabFiletree := UIZooTab{DemoFT{}}
TabSpinner := UIZooTab{DemoSpinner{}}
TabHTML := UIZooTab{DemoHtml{}}
TabIcon := UIZooTab{DemoIcon{}}
TabIconSet := UIZooTab{DemoIconSet{}}
TabImage := UIZooTab{DemoImage{}}
TabImageBlend := UIZooTab{DemoImageBlend{}}
TabGlassPanel := UIZooTab{DemoGlassPanel{}}
TabLabel := UIZooTab{DemoLabel{}}
TabSlug := UIZooTab{DemoSlug{}}
TabLinkLabel := UIZooTab{DemoLinkLabel{}}
TabMarkdown := UIZooTab{DemoMarkdown{}}
TabPageFlip := UIZooTab{DemoPageFlip{}}
TabPortalList := UIZooTab{DemoPortalList{}}
TabRadioButton := UIZooTab{DemoRadioButton{}}
TabRotary := UIZooTab{DemoRotary{}}
TabRotatedImage := UIZooTab{DemoRotatedImage{}}
TabScrollbar := UIZooTab{DemoScrollBar{}}
TabSlider := UIZooTab{DemoSlider{}}
TabSlidesView := UIZooTab{DemoSlidesView{}}
TabStackNavigation := UIZooTab{DemoStackNavigation{}}
TabAdaptiveView := UIZooTab{DemoAdaptiveView{}}
TabTextInput := UIZooTab{DemoTextInput{}}
TabVideo := UIZooTab{DemoVideo{}}
TabView := UIZooTab{DemoView{}}
TabAlignScroll := UIZooTab{DemoAlignScroll{}}
}
mod.gc.set_static(AppDock)
mod.gc.run()
startup() do #(App::script_component(vm)){
ui: Root{
main_window := Window{
window.inner_size: vec2(1200 800)
body +: {
flow: Down
spacing: 0.
margin: 0.
dock := AppDock{}
}
}
}
}
}
#[derive(Script, ScriptHook)]
pub struct App {
#[live]
ui: WidgetRef,
#[rust]
counter: usize,
}
impl MatchEvent for App {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
let ui = self.ui.clone();
ui.radio_button_set(
cx,
ids_array!(
radios_demo_1.radio1,
radios_demo_1.radio2,
radios_demo_1.radio3,
radios_demo_1.radio4
),
)
.selected(cx, actions);
ui.radio_button_set(
cx,
ids_array!(
radios_demo_2.radio1,
radios_demo_2.radio2,
radios_demo_2.radio3,
radios_demo_2.radio4
),
)
.selected(cx, actions);
ui.radio_button_set(
cx,
ids_array!(
radios_demo_3.radio1,
radios_demo_3.radio2,
radios_demo_3.radio3,
radios_demo_3.radio4
),
)
.selected(cx, actions);
ui.radio_button_set(
cx,
ids_array!(
radios_demo_11.radio1,
radios_demo_11.radio2,
radios_demo_11.radio3,
radios_demo_11.radio4
),
)
.selected(cx, actions);
ui.radio_button_set(
cx,
ids_array!(
radios_demo_12.radio1,
radios_demo_12.radio2,
radios_demo_12.radio3,
radios_demo_12.radio4
),
)
.selected(cx, actions);
if let Some(txt) = self
.ui
.text_input(cx, ids!(simpletextinput))
.changed(&actions)
{
log!("TEXTBOX CHANGED {}", self.counter);
self.counter += 1;
let lbl = self.ui.label(cx, ids!(simpletextinput_outputbox));
lbl.set_text(cx, &format!("{} {}", self.counter, txt));
}
if let Some(is_multiline) = self
.ui
.check_box(cx, ids!(multiline_toggle))
.changed(actions)
{
let ti = self.ui.text_input(cx, ids!(multiline_toggleable));
ti.set_is_multiline(cx, is_multiline);
}
if self.ui.button(cx, ids!(basicbutton)).clicked(&actions) {
log!("BASIC BUTTON CLICKED {}", self.counter);
self.counter += 1;
let btn = self.ui.button(cx, ids!(basicbutton));
btn.set_text(cx, &format!("Clicky clicky! {}", self.counter));
}
if self.ui.button(cx, ids!(blendbutton)).clicked(&actions) {
self.ui.image_blend(cx, ids!(blendimage)).switch_image(cx);
}
if self.ui.button(cx, ids!(pageflipbutton_a)).clicked(&actions) {
self.ui
.page_flip(cx, ids!(page_flip))
.set_active_page(cx, live_id!(page_a));
}
if self.ui.button(cx, ids!(pageflipbutton_b)).clicked(&actions) {
self.ui
.page_flip(cx, ids!(page_flip))
.set_active_page(cx, live_id!(page_b));
}
if self.ui.button(cx, ids!(pageflipbutton_c)).clicked(&actions) {
self.ui
.page_flip(cx, ids!(page_flip))
.set_active_page(cx, live_id!(page_c));
}
if self.ui.button(cx, ids!(iconbutton)).clicked(&actions) {
log!("ICON BUTTON CLICKED {}", self.counter);
self.counter += 1;
let btn = self.ui.button(cx, ids!(iconbutton));
btn.set_text(cx, &format!("Icon button clicked: {}", self.counter));
}
if let Some(check) = self.ui.check_box(cx, ids!(simplecheckbox)).changed(actions) {
log!("CHECK BUTTON CLICKED {} {}", self.counter, check);
self.counter += 1;
let lbl = self.ui.label(cx, ids!(simplecheckbox_output));
lbl.set_text(cx, &format!("{} {}", self.counter, check));
}
// StackNavigation demo handlers
let stack_nav = self.ui.stack_navigation(cx, ids!(stack_nav_demo));
if self.ui.button(cx, ids!(push_view_a)).clicked(&actions) {
stack_nav.push(cx, live_id!(stack_view_a));
}
if self.ui.button(cx, ids!(push_view_b)).clicked(&actions) {
stack_nav.push(cx, live_id!(stack_view_b));
}
if self.ui.button(cx, ids!(push_view_c)).clicked(&actions) {
stack_nav.push(cx, live_id!(stack_view_c));
}
if self
.ui
.button(cx, ids!(push_nested_from_a))
.clicked(&actions)
{
stack_nav.push(cx, live_id!(stack_view_b));
}
if self
.ui
.button(cx, ids!(push_nested_from_b))
.clicked(&actions)
{
stack_nav.push(cx, live_id!(stack_view_c));
}
if self.ui.button(cx, ids!(pop_to_root_btn)).clicked(&actions) {
stack_nav.pop_to_root(cx);
}
// Responsive Nav demo: desktop click handlers
let desktop_items = [
(ids!(desktop_item_1), "Settings"),
(ids!(desktop_item_2), "Profile"),
(ids!(desktop_item_3), "Notifications"),
];
for (item_id, title) in desktop_items {
if let Some(_) = self.ui.view(cx, item_id).finger_down(actions) {
self.ui
.label(cx, ids!(desktop_detail_title))
.set_text(cx, title);
self.ui.label(cx, ids!(desktop_detail_body)).set_text(
cx,
&format!("{} detail content.\n\nOn Desktop, the list and detail are visible side-by-side.", title),
);
}
}
// Responsive Nav demo: mobile StackNavigation handlers
let mobile_nav = self.ui.stack_navigation(cx, ids!(mobile_nav));
let mobile_items = [
(ids!(mobile_item_1), "Settings"),
(ids!(mobile_item_2), "Profile"),
(ids!(mobile_item_3), "Notifications"),
];
for (item_id, title) in mobile_items {
if let Some(_) = self.ui.view(cx, item_id).finger_down(actions) {
mobile_nav.set_title(cx, live_id!(mobile_detail_view), title);
self.ui
.label(cx, ids!(mobile_detail_title))
.set_text(cx, title);
self.ui.label(cx, ids!(mobile_detail_body)).set_text(
cx,
&format!("{} detail content.\n\nOn Mobile, this was pushed onto the navigation stack.", title),
);
mobile_nav.push(cx, live_id!(mobile_detail_view));
}
}
}
}
impl AppMain for App {
fn script_mod(vm: &mut ScriptVm) -> ScriptValue {
crate::makepad_widgets::script_mod(vm);
// The design tweaker's shader view uses the real code editor when
// the app registers it.
makepad_code_editor::script_mod(vm);
crate::layout_templates::script_mod(vm);
crate::demofiletree::script_mod(vm);
crate::tab_button::script_mod(vm);
crate::tab_checkbox::script_mod(vm);
crate::tab_dropdown::script_mod(vm);
crate::tab_filetree::script_mod(vm);
crate::tab_spinner::script_mod(vm);
crate::tab_html::script_mod(vm);
crate::tab_icon::script_mod(vm);
crate::tab_iconset::script_mod(vm);
crate::tab_image::script_mod(vm);
crate::tab_imageblend::script_mod(vm);
crate::tab_glasspanel::script_mod(vm);
crate::tab_label::script_mod(vm);
crate::tab_layout::script_mod(vm);
crate::tab_linklabel::script_mod(vm);
crate::tab_markdown::script_mod(vm);
crate::tab_pageflip::script_mod(vm);
crate::tab_portallist::script_mod(vm);
crate::tab_radiobutton::script_mod(vm);
crate::tab_rotary::script_mod(vm);
crate::tab_rotatedimage::script_mod(vm);
crate::tab_scrollbar::script_mod(vm);
crate::tab_slider::script_mod(vm);
crate::tab_slug::script_mod(vm);
crate::tab_slidesview::script_mod(vm);
crate::tab_stacknavigation::script_mod(vm);
crate::tab_adaptiveview::script_mod(vm);
crate::tab_textinput::script_mod(vm);
crate::tab_video::script_mod(vm);
crate::tab_view::script_mod(vm);
crate::tab_align_scroll::script_mod(vm);
crate::tab_widgetsoverview::script_mod(vm);
self::script_mod(vm)
}
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
self.match_event(cx, event);
self.ui.handle_event(cx, event, &mut Scope::empty());
}
}