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>
408 lines
15 KiB
Rust
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());
|
|
}
|
|
}
|