The examples/counter manifest referenced makepad-native-glue via an
out-of-repo relative path (../../../) — a local nigig glue crate not
present in upstream makepad. The dependency was never used by counter
code, so remove it to keep the fork tree self-contained.
- Updated tile.rs with baked fills/faces support
- Enhanced 3D building rendering
- Improved road geometry and elevation
- Better theme matching and styling
- Advanced overlay composition
This brings the fork up to date with makepad dev branch map capabilities.
11375214 fixed the misplaced dependency block by adding a correct copy
inside [dependencies], but the misplaced copy below [features] was still
present -- it had already been moved back by 5eda8056, the commit
11375214 is built on. The result is three dependencies declared twice:
error: duplicate key
so widgets/Cargo.toml still does not parse and every consumer of the
fork is still blocked, just with a different message.
Two people fixed the same bug in parallel. Removing the second, now
redundant, copy; the surviving declaration sits with the other optional
sibling crates in [dependencies].
Verified:
cargo metadata --manifest-path widgets/Cargo.toml \
--features maps,csg,gltf,test
resolves.
The makepad-gltf, makepad-csg, and makepad-test dependencies were incorrectly
placed after the [features] section, causing TOML parsing errors. Moved them
to the [dependencies] section where they belong.
The "Update fork to upstream dev 5d4483f" merge relocated this block:
makepad-gltf = { path = "../libs/gltf", optional = true }
makepad-csg = { path = "../libs/csg/csg", optional = true }
makepad-test = { path = "../libs/makepad_test", optional = true }
from the end of [dependencies] to below the [features] header. TOML has
no way to know these are dependencies once they sit under [features], so
cargo parses each as a feature definition whose value should be an array
of strings and fails:
error: invalid type: map, expected a sequence
--> widgets/Cargo.toml:47:16
Every consumer of this fork is broken as a result. The three crates stop
being dependencies at all, so the features that gate them --
gltf/csg/test, and maps via i_overlay -- no longer exist:
package `nigig-map` depends on `makepad-widgets` with feature `maps`
but `makepad-widgets` does not have that feature.
help: available features: default, serde
At the previous rev (2c5cd97) the same three lines are inside
[dependencies], which is why that rev resolves and this one does not.
This is a pure relocation -- the six moved lines are byte-identical, no
version, path or flag changed. Verified with
cargo metadata --manifest-path widgets/Cargo.toml \
--features maps,csg,gltf,test
which fails on d6d1f99c and succeeds with this commit.
- Add drape.rs for terrain hillshade landcover draping
- Add overlay.rs for route polylines, markers, and position puck
- Add icons.rs and icons/ directory for map icon management
- Update geometry.rs with 3D road elevation and join improvements
- Update tile.rs with unified road mesh rendering
- Update view.rs with seamless 2D/3D mode transitions
- Update style.rs with night themes and emissive roads
- Add i_overlay dependency for polygon boolean operations
- Update maps feature to include i_overlay
Key improvements:
- 3D road elevation and seam continuity
- Building shadow geometry and terrain cast shadows
- Route assistant and navigation layer support
- Clickable themes and night mode
- Water, grass, and shrub rendering
- Optimized road geometry and mode transitions
* PortalList: add `set_flow(cx, flow)` to switch a list between vertical and
horizontal layout flow at runtime. Much faster than using `script_apply_eval`,
and always fully correct because it updates the `vec_index` axis.
It also avoids a full ScriptReapply sequence, which is potentially expensive
across all of an app's widgets.
* AdaptiveView: add `active_variant()` getter so that widgets using AdaptiveView do not have to separately track which variant it should be in. Removes all ambiguity and possibility of divergence... finally!
* Also fixes long-standing TODO item in AdaptiveView about properly handling
weird window geom events, e.g., on macOS sometimes it spits out a 0-width
window update which is total b.s.
* DisplayContext: add `is_desktop_width()` helper.
* Window: ignore spurious zero-size window geom events.
Replace the abandoned arc2 colour-stub with a real path primitive: the
distance to a bare circular-arc CENTERLINE (no baked thickness), so it
chains after move_to/line_to and is covered by a single stroke(w) -- unlike
arc_round_caps/arc_flat_caps which bake their own width. Angles in radians,
0 = +x axis, counter-clockwise positive; last_pos advances to the arc end so
a following line_to connects.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(draw): add sdf.arc_to circular-arc path segment
A `Fill` child under Flow::Right (or `height: Fill` under Flow::Down) is
deferred and given the row's full slack up front, so end_turtle's align
step takes the deferred branch, which distributes width/height to fills
and drops main-axis align entirely. That's correct when a fill consumes
its whole slot, but a fill that draws narrower than its slot (e.g. an
Image that aspect-fits, or `Fill{max: N}` capped below the container)
leaves real slack that then anchors to the start regardless of align.
Add row_align_x_shift / col_align_y_shift: reclaim align * (inner -
actually_drawn) in each deferred branch. They early-return 0 when align
is 0, the inner size is unknown, or the content fills, so the only
behavior change is deferred-flow containers that set main-axis align and
hold an under-filling Fill child.
Add examples to the uizoo "Layout Demos" tab that proves they boht work.
redrawing is significantly more expensive than text comparisons,
and doing this outside of each widget is difficult and less efficient.
this helped avoid a bunch of redraw cycles in robrix,
so it's probably worth doing for all widgets, especially since
setting text or another basic property now does an auto-redraw
(it didn't used to be like that)
* Fix rendering, gradient, sampling, etc issues on older GPUs
* Fix bug in `box_y` sdf function, which caused gradients to be split
into two bands incorrectly. Mostly a problem on lower-res screens.
* Use per-texture filtering instead of GL sampler objects on Linux,
especially for Mesa drivers that ignore min filter samplers.
This should help prevent blocky/pixellated things like emoji/avatars
* Improve rendering sharpness on low-DPI screens (icons, emoji, images)
On 1.0-DPI screens, emoji/SVG-icons/avatars were minified without
adequate sampling and SVG AA was sub-pixel, producing blocky/aliased
output. This reworks each path and adds optional full-window SSAA.
SVG icons (device-aware AA + round caps):
- draw_svg.rs/draw_vector.rs/render.rs: size the fill & stroke AA
fringe and the curve-flatten tolerance in DEVICE pixels (≈constant
regardless of icon size), so edges resolve via the analytic
d/fwidth coverage and curves stay smooth at any scale; re-tessellate
on scale change.
- triangulate.rs: thread the flatten tolerance through path fill/stroke.
- tessellate.rs: emit round caps as a solid disc (u=0.5) instead of a
radial fade that collapsed to a square at small sizes.
- widgets/icon.rs: don't clip the Icon to its Fit bounds, so round
caps that extend past the box render fully instead of being sheared.
Emoji:
- glyph_raster_image.rs/rasterizer.rs: rasterize color emoji near the
on-screen size with an alpha-weighted box downscale (geometric-mean
scale factor) instead of the font's native PNG strike.
Images / avatars (mipmaps):
- image_cache.rs/texture.rs/draw_list.rs/lib.rs: optionally emit a CPU
mipmap chain (VecMipBGRAu8_32) for non-animated images so minified
avatars/thumbnails sample cleanly. Env-gated MAKEPAD_IMAGE_MIPMAPS;
default on for GL on Linux.
- metal.rs: real per-level mip upload. d3d11.rs/vulkan.rs/web_gl.rs:
safe single-level fallback (no crash; real mips TODO).
Full-window supersampling (optional):
- window.rs: render the whole UI into an offscreen target at
MAKEPAD_SUPERSAMPLE× device resolution and downscale-resolve into the
window. Default 2×, env-tunable, 1× disables. Modeled on the existing
GaussStack render-to-texture path.
* don't unconditionally enable supersampling SSAA of 2x by default
it's too expensive and too slow for most older devices
* cleanup, reduce comment verbosity
* improve SVG anti aliasing
* Windows: fix laggy/juddery scroll performance
- Pace the render loop to the display refresh using a DXGI frame-latency
waitable object and present with vsync, replacing the free-spinning,
uncapped Poll loop that caused uneven scroll cadence.
- Coalesce consecutive WM_MOUSEMOVE messages and paint once per loop pass
to stop the judder when moving the mouse during fling deceleration.
- Cache get_dpi_factor() and the WM_NCHITTEST WindowDragQuery result to
avoid per-mouse-move GetDeviceCaps syscalls and widget-tree hit-tests.
- Throttle XInput/DirectInput polling of empty/disconnected controller
slots, which was stalling the UI thread.
- Rework the momentum fling to a native exponential model with a
frame-interval EMA, and stop the tail auto-scroll from fighting an
active fling/drag.
- D3D11: update the glyph atlas and image textures in place via
UpdateSubresource instead of recreating them on every change, and
spread D3D11 shader-object creation across frames.
- Slug atlas: only force a full re-layout on a width change; append rows
on height growth.
* Windows: correctness fixes from review (off the scroll hot path)
- d3d11: close the DXGI frame-latency waitable HANDLE in Drop (it was
leaked once per main-window lifecycle and the field comment was wrong);
keep popup swap chains at frame-latency 1; track the waitable-swapchain
flag for ResizeBuffers instead of inferring it from the handle; present
without the vsync interval during a live resize.
- windows.rs: poll game input on the idle signal tick so a gamepad button
can be serviced while the app is otherwise idle.
- win32_window / window: invalidate the WM_NCHITTEST / WindowDragQuery
caches on window move and on a caption relayout, with a generation
counter guarding against a reentrant invalidation being clobbered.
- windows_game_input: detect a controller already plugged in at launch via
a one-shot full scan on the first poll, probe slot 0 (Player 1) first,
and offset the DirectInput enumeration so it never stacks with the
XInput probe.
- comment/doc corrections.
* Image cache: accept any Arc<D: AsRef<[u8]> + ?Sized> for async image data
The load_image_from_data_async family required Arc<Vec<u8>>, forcing callers
that already hold the bytes as Arc<[u8]> (e.g. a content-addressed media cache)
to copy the whole buffer via .to_vec() just to satisfy the type. Generalize the
data parameter to Arc<D> where D: AsRef<[u8]> + ?Sized, so those callers can pass
their existing Arc by refcount-clone with no byte copy. The decode path only ever
borrowed the bytes (&[u8]), so this is purely a signature relaxation; existing
Arc<Vec<u8>> callers are unaffected (D = Vec<u8>).
* Linux: GL glyph-atlas in-place texture update + X11/Wayland mouse-move coalescing
* Scroll: unified fling model + native trackpad momentum deceleration
Share one kinetic-scroll model between PortalList and ScrollBar (and thus
ScrollXView/ScrollYView/ScrollXYView) via a new widgets/src/scroll_motion.rs:
- Touch-drag flicks use an iOS-style exponential self-decay, frame-rate
independent via a per-frame integrator with dt smoothing.
- Trackpad scrolling applies the OS momentum directly while fast (responsive,
full native speed), then hands off to a gentler self-decaying tail once it
slows past a threshold, so the deceleration is longer and smoother than the
OS's short, choppy tail. Handoff is seeded at the current speed for a
continuous transition; the seed is clamped against degenerate event timing.
- Add ScrollPhase to scroll events, mapped from NSEventPhase/momentumPhase on
macOS and wl_pointer AxisStop on Wayland; None elsewhere (wheels/X11/Windows
behave as before). MAKEPAD_RAW_TRACKPAD_MOMENTUM=1 bypasses the smoothed tail.
- A press catches an in-progress fling (stops the scroll, consumes the press so
it doesn't also activate a child), matching iOS/Android/macOS.
* Shader codegen: prefix Metal/WGSL locals to avoid reserved-word collisions
The Metal/WGSL backends emitted user-declared shader locals verbatim, so a
local named after a reserved type keyword (e.g. `half`) produced invalid
shader source and failed to compile at runtime. Prefix them with `l_` like the
HLSL/GLSL backends already do.
* TextFlow: don't panic on unbalanced HTML close tags
end_code/end_quote unwrapped the area stack, so a stray `</pre>` or
`</blockquote>` in untrusted content (e.g. a chat message) panicked. Return
early instead, and drop a vestigial per-list-item area-stack push that leaked
an entry and could hand a stray close tag the wrong block's area.
* Scroll: expose fling decel, handoff threshold, & tail-decel as `#[live]` fields
This allows app devs to override the scroll feel per-widget in the DSL,
or globally by overriding the base widget's defaults — verified that a DSL
override takes effect.
* Windows: fix frame pacing, paste crash, and wheel input backlog
- Wait on the frame-latency waitable right before each window's vsync
present instead of on every Paint, so input no longer stalls behind
waits that have no matching present. Drain leftover credits after a
live resize.
- Pasting when the clipboard has no text no longer panics.
- The poll loop now handles up to 32 messages (2 ms) per frame and
merges consecutive mouse-wheel messages, so fast wheels can't build a
backlog that keeps scrolling after the gesture ends. Sleep 1 ms when a
frame presents nothing so animation polling doesn't spin a core.
* Image: don't build unused mip chains; fix stale images in recycled widgets
- Only build the CPU mip chain when a backend actually uploads it
(Metal, behind its env var), and build it on the decode thread instead
of the UI thread. Linux GL still gets its mipmaps via glGenerateMipmap
and now retains less CPU memory per image.
- Recycled Image widgets no longer show the previous item's image or
apply an old decode result. Placeholder textures set via set_texture
(like blurhashes) stay visible while the real image decodes, and a
failed load clears the widget instead of leaving old content up.
* Widgets: avoid needless caption redraws; cheaper PortalList height tracking
- Label::set_text does nothing when the text hasn't changed, and the
window caption title is only synced when it actually changes, so mouse
moves and animation ticks no longer redraw the whole window every
event. The caption centering padding requests its own redraw now.
- PortalList records item heights only when new or changed, and only
re-applies the default height after it drifts by half a pixel, so big
lists don't walk every unmeasured item on every scroll frame.
* Text: cache layouts of long texts; stop cloning glyph outlines every frame
- The layout cache now accepts texts of any length (long messages and
code blocks used to re-layout on every scroll frame). It is a real LRU
with a byte budget on top of the entry cap, and texts drawn in the
current frame are never evicted, so one heavy frame can't thrash the
cache into a permanent miss cycle. The shaper cache is LRU now too.
- Glyph outlines are shared via Rc, so drawing a cached glyph no longer
copies its command list, and outline complexity is computed once when
the outline is built instead of every frame.
* Html: fix stale links/spans in recycled widgets and <details> renumbering
- set_text only rebuilds when the content actually changed, so a
recycled link can't open the previous message's URL, and re-setting
identical content keeps the user's <details> open/closed state.
- Custom widgets and <details> are keyed by their node index instead of
a visit-order counter, so toggling a collapsed <details> can't
renumber the widgets after it and rebind them to the wrong nodes.
item_with_scope also recreates its widget when the template changes.
* Linux: fixed-distance wheel scrolling; Wayland frame-callback pacing
- Wheel scrolling moves a fixed 60 px per detent on X11 and Wayland
instead of a timing-based guess that flipped between 12 px and 240 px
depending on how events batched. Wayland reads real detent counts via
AxisValue120 (wl_seat v9, with AxisDiscrete as the older fallback) and
maps keymaps MAP_PRIVATE as v7+ requires. Touchpads are unchanged.
- Wayland frames are paced with wl_surface frame callbacks and swap
interval 0, so redrawing a hidden or minimized window can no longer
hang the whole app inside eglSwapBuffers (compositors withhold frame
callbacks for hidden windows). Windows with a callback in flight skip
presenting and stay dirty; X11 keeps vsync exactly as before.
* Text: bigger layout cache budget, reclaimed at the end of each frame
A maximal ~60 KB message lays out to roughly 4 MB of glyphs, so the 4 MB
budget couldn't hold even one alongside a normal screen. Raise it to
16 MB, and run eviction at the end of every frame so memory over the
budget is freed one frame after its content leaves the screen, instead
of lingering until some later layout happens to insert a new entry.
* Fix oversized uniform slices: the array lengths were in bytes, not f32 elements
* Wayland: flush buffered mouse motion before scroll events, and drop motion for closed windows
* GL: fall back to non-mipmapped filtering when glGenerateMipmap fails on strict GLES3 drivers
* Text: bucket emoji raster scales so zooming reuses atlas slots instead of re-decoding every step
* Image: add has_content() and record texture provenance on cache-hit loads too
* Scroll: native trackpad momentum, Chrome-model bounce; presses catch motion, never click children
* Dock: redraw the newly selected tab immediately when the active tab is closed
* Html: standard link colors with pressed precedence; skip re-parsing unchanged text; color setters
* Image: don't redraw on cache-hit loads of the already-bound image (per-draw reloaders looped forever)
* Scroll: log macOS momentum-end phase bits to check Cancelled (touch-cut) vs Ended (natural fade)
* Scroll: momentum state machine; flicks survive pagination; edge sentinels & once-per-frame actions
* Scroll: remove the MAKEPAD_SCROLL_DEBUG diagnostics
* Scroll: time-based fling velocity window; pointer fan-out guard; parked flings survive pagination
* PortalList: optional reached-start/end margins (Some(0) default); repositioning re-announces the edges
* Linux/Wayland: honor UI zoom across window resizes; scale caption bar with zoom
Keep the wayland-side window geom in native units so the zoomed dpi isn't
read back as "native" on the next Configure — UI zoom no longer resets or
flickers on maximize/tile. Also let the caption bar height scale with the
zoom (pin to native only on macOS, where the buttons are OS traffic lights).
* Windows: D3D11 fixes for drawlist mgmt
trying to help with the `new_batch` bugs
* draw_list.rs — `set_zbias` now returns whether it changed
* d3d11.rs — uploads draw_call_uniforms on the given condition:
`uniforms_dirty || zbias_changed || buffer.is_none()`
and the zbias advance is hoisted above the early-continues
* Scroll: hard flicks carry farther and boost on re-flick; iOS-style touch rubber band with per-edge bounce gating; Android fling spline behind a flag
* Splitter: redraw both pane subtrees on drag; cached views keep fresh fixed sizes in the dirty check
* iOS: re-deliver window-geometry changes dropped by re-entrant UIKit callbacks; Init only ever from the first draw
* final cleanup fixes. Ensure all examples, experiments, `studio` all work
* Image: skip re-parsing an SVG that is already shown, keyed on the caller's shared bytes
* CachedView: fix upside-down offscreen texture on GL/GL-ES
GL/GL-ES store offscreen FBOs bottom-up, unlike Metal/D3D/WGSL.
The DSL->script-shader migration switched the CachedView composite to plain
.sample() (non-flipping sample2d on GL), so cached views rendered
vertically mirrored on GL/GL-ES; Metal was fine on macOS.
Add a `sample_rt` script sampler (emits the V-flipping sample2d_rt on
GLSL, plain no-flip sample elsewhere) and use it in the CachedView and
CachedRoundedView composites.
* Fix `AdaptiveView::redraw()` to actually do something
Deadzone-rescaled right stick feeds the exact mouse-drag pipeline: same
0.01 rad/px orbit through pseudo-pixels (~2.6 rad/s full deflection, stick
up = look up), same look_dx/look_dy for scripts, same chase-rig authority
(stick held = kid owns the camera, recenters after release) and
cam_dragging visibility. Applied before script camera writes each tick,
like real mouse events, so set_cam_yaw still wins its tick. Zeroed under
tape tests for determinism. Camera-only pads now count in device selection.
splashgame.md: right stick documented; new rule — every new ability must
also be reachable from the gamepad (bind to the named actions).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
VoiceWave grows an opt-in ptt_use_escape flag (Escape doubles as cancel/
dismiss elsewhere, so hosts choose); both keys drive the same logical talk
button. Gamemaker opts in via the caption_bar's hidden voice_wave and the
hints now read 'hold Esc' — the big friendly key for kids. Verified the
nested named-child merge lands on the live widget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verdict on the gamemaker judder (measured, 25s A/B runs, same window size):
gauss OFF -> unchanged (25-33ms gaps) [pyramid exonerated]
empty game world -> unchanged [game exonerated]
vsync OFF -> FIXED (119.8fps steady, worst 9ms; slow callbacks 124->2)
The stall is CAMetalLayer display-sync throttling: the compositor (hardware-
mirrored + SwitchResX-scaled 7680x2160) returns drawables unevenly, and
nextDrawable blocks the main thread 10-25ms in phases. The durable fix is
present-gated pacing: track in-flight presents via addPresentedHandler and
skip the paint when the pool is busy instead of blocking the event loop.
MAKEPAD_TIMER_TRACE=1 logs paint-clock fire-to-fire gaps >20ms and slow
callbacks >10ms with a per-step breakdown (live_edit/net/pad/paint/gc).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fixed 8ms timer0 presented ~125Hz into a 120Hz vsync queue: the drawable
pool drifted full and nextDrawable blocked the main thread in a ~25-frame
sawtooth (PerfGraph 'wait' ramps). Timer now arms at 1.002/max_fps across
attached NSScreens — the +0.2% makes NSTimer lateness drain the queue instead
of accumulating. Sawtooth verified gone.
perf_monitor: 'gpu' channel — presented-frame GPU interval tapped from the
existing command-buffer completion aggregation (atomic hand-off, folded at
frame_boundary; concurrent with CPU channels, plotted violet).
metal: MAKEPAD_GPU_PASS_TRACE=1 logs per-pass GPU time ([gpu-pass] name ms)
for frame-budget hunts.
Measured (gamemaker, empty world vs full racing game — identical ~4.7ms GPU):
the frame cost is the UI glass pipeline, not the game — scene capture +
6 mip downsamples + 3 smooth upsamples re-run every frame at 120Hz because
the game pane dirties the window; at low GPU clocks the 11-pass chain's
latency swings past the 8.3ms budget in phases.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
platform: Cx.perf_monitor — per-frame ring (240) of paint-to-paint gap +
per-channel CPU us. Built-in channels: event dispatch (outermost, minus
app-attributed time), script exec, GC, pass encode, nextDrawable wait.
Apps register custom channels: cx.perf_monitor.channel("physics", rgb).
Off until enabled; hooks in event dispatch, macos repaint, metal draw_pass.
widgets: PerfGraph — corner-pinned live panel (DrawVector strips): frame-gap
bars colored against 120/60Hz budgets with guide lines, stacked per-channel
CPU, legend with averages. Self-positions bottom-right (DrawVector geometry
+ deferred turtle alignment don't mix — no aligning parent).
gamemaker: PerfGraph hovers the game pane (F3 toggles), engine feeds script
+ physics channels (incl. hot-reload evals); engine text overlay moved to
F4; per-phase engine window kept for ag perf / AIGAME_PERF=1; new 'ag perf'
harness verb + template guidance (template CLAUDE.md force-added: runtime
resource, blanket CLAUDE.md gitignore had kept it untracked).
Measured on my-game-5: engine frame CPU ~0.3ms; the hiccup is frame pacing —
the 8ms NSTimer paint clock beats against the 120Hz display, the drawable
pool drifts full and nextDrawable blocks the main thread in a ~25-frame
sawtooth (avg 2-3.4ms, spikes 20-30ms). The graph shows it as red ramps.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
libs/tts was never tracked despite being a build dependency of the gamemaker
example. tools/download_tts.sh fetches the public upstream weights (HuggingFace
Kokoro-82M + whisper.cpp) and converts them locally with the in-repo stdlib-only
converter; model artifacts are gitignored.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The parallel batch machinery from be21d627a only ever existed to chase a
default-on multi-threaded win that never came (washer w8 still +54% with
the hybrid on). At w1 — the only configuration this opt-in flag is for —
parallel_for runs inline, so the serial path gives the identical result
for ~340 fewer lines. Wire up the previously-dead
dynamic_tree_self_pairs/cross_pairs into a serial collect_batch_candidates
(three BVTT self/cross traversals -> canonical (a,b,child) sort -> serial
filter into move_results[0]) and delete BatchWork, BatchCtx, batch_drain_*,
BatchFilterCtx, batch_filter_*, bvtt_step, dynamic_tree_bvtt_drain/expand,
and the batch_frontier/worker_* scratch fields.
Determinism preserved exactly: OFF 0x61E35C31/step314 bit-identical, ON
0xBE99C5F7/step313 identical across workers 1/2/4. The debug SET-equality
oracle and the determinism_broad_phase_hybrid_across_worker_counts test
are unchanged and still pass; zero warnings.
PGO: pgo.sh never trained -bp=1, so an off-path-only profile laid the
hybrid branch out cold and collapsed the win to ~-5%. Add one -b=8 -bp=1
training run (neutral for the default path — counts merge, the OFF branch
stays hot) and retrain.
Corrected README numbers to measured values (hybrid-trained profile,
paired -bp toggle, washer w1): pair-finding stage 7.6k -> 3.6k ms/1000
(-52%); total ~-17% (~19.0k vs ~22.6k), which beats C (20661) and narrows
Rapier's lead from ~23% to ~12% — not the "17.7k / within 5% / -19%"
be21d627a claimed. Still default-off (w8 regresses ~+54%), opt-in
single-threaded accelerator for churn-heavy scenes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Broad-phase-hybrid subsection (why washer loses, the batch design, the
determinism proof, the single-thread win / multi-thread floor tradeoff,
and the default-off rationale). Washer row in the single-thread rapier
table annotated with the opt-in number (~17.7s, beats C, ~5% behind
rapier → box3d ahead-or-even on all nine single-threaded).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WorldDef.enable_broad_phase_hybrid (default false; -bp=0/1 bench toggle):
an adaptive batch broad phase for high-churn scenes. When
move_count*4 > proxy_count, replaces the per-moved-proxy tree queries
(8k proxies x 3 root-descents on washer) with three BVTT self/cross
traversals (dynamic self + dynamic x static + dynamic x kinematic) that
share the upper-tree descent, plus an O(n) bottom-up refit instead of
the median rebuild. Both traversal and candidate-filter are parallelized
across the task system (per-worker buffers → merge → canonical sort by
(shape_a,shape_b,child) → deterministic contact creation).
Correctness: a #[cfg(debug_assertions)] SET-equality assertion (batch
candidate set == per-mover set) runs in every test and never fires — the
proof the BVTT finds identical contacts (the hash can't prove it since
creation order legitimately re-baselines). New test
determinism_broad_phase_hybrid_across_worker_counts. OFF hash 0x61E35C31
bit-identical; ON hash 0xBE99C5F7 identical across workers 1/2/4 +
external tasks. 180/186/180/180 tests, zero warnings, profile retrained.
Single-threaded washer -18.7% (17715 vs 21780, broad phase -51%) — beats
C (20661), within ~6% of Rapier (16844). DEFAULT OFF because it regresses
multi-threaded (washer w8 +52%): the batch materializes ~40-50k
candidates/step and serially merges+sorts them (a fundamental floor the
inline per-mover path avoids by filtering in the query callback), so at
w8 the parallel per-mover queries win. Cannot be worker-gated (would
break cross-worker determinism). Correct, deterministic, zero-cost when
off — an opt-in single-threaded accelerator for churn-heavy scenes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the C Box3D column to the nine-scene rapier table, all from one
same-window interleaved single-thread run (2026-07-06). Percentages vs
the box3d Rust column. box3d Rust beats Rapier on 8/9 (junkyard flipped
to a +5% win post-tier-2; only washer lost, Rapier's incremental-BVH
broad phase). vs C: within ~7% geomean, ahead on both pyramid scenes,
worst is junkyard +18%. Replaces the derived † junkyard cell with a
direct measurement. Headline updated 7/9→8/9.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
query_tree_for_pairs was materializing every tree-query hit into a Vec
then re-iterating; C runs try_add_pair inline in the query callback
(b3PairQueryCallback). Rewrote to filter inline like C (both world
borrows are shared, so it compiles); only the rare compound inner-query
still uses a child_hits scratch (no compounds in washer/junkyard/
pyramids/trees, so pair-discovery order is unchanged). Removed the
now-dead PairScratch.hits field.
query_tree_for_pairs is washer's single hottest symbol (8k dynamic cubes
churned by a rotating drum re-query the whole tree every step). washer
broad phase -3% (paired plain + retrained-PGO), junkyard -1.4%,
pyramids/trees neutral. Hash bit-identical (0x61E35C31) — pure
structural, same discovery order. 179/185/179/179 tests, zero warnings.
Profile retrained dual-mode.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instrumentation used to decompose the washer scene (which phase holds
rapier's ~24% advantage over box3d). Timer.rs drops the profiler-feature
gate so PhysicsPipeline's per-stage counters always measure (std Instant
instead of web_time); bench gains --stages (per-phase ms split) and
--probe (per-step contact/pair/sleep counts). Diagnostic tooling only;
no effect on simulation. Findings: washer's rapier advantage is entirely
broad phase (box3d ~8.5s vs rapier 1.7s), box3d's narrow phase is
actually faster than rapier's.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Derived cells marked with a dagger and the derivation stated: cold-window
baseline x the same-binary paired improvement (thermal-drift-immune);
direct cold-window rerun will replace them. vs C: junkyard +17%->+7% w1,
+22%->+12% w8, geomeans +6%/+8%. vs rapier: junkyard -8% -> ~parity,
geomean +34%, washer now rapier's only win.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New middle tier between full manifold recycling and the full SAT, behind
WorldDef.enable_feature_recycling (default true; OFF path bit-identical,
hash 0x61E35C31 verified):
- Case A, separated-witness early-out: a previously-non-touching contact
revalidates only the cached winning axis; still separating beyond the
speculative distance means done in one test. Sound structurally: any
cached axis is a valid separation witness (understates only, which
falls through to the full SAT). Carried junkyard: 24k skips/step,
full SATs 25.4k -> 4.5k per step.
- Case B, touching feature rebuild: re-clips the cached winning feature
under explicit staleness bounds (SATCache::sat_pose at last full SAT,
translation < 4x recycle distance, rotation < ~4.6 deg, forced refresh
every 8 steps); degenerate rebuilds and touching<->separated
transitions fall through same-step.
Probe-driven (junkyard: 89k full SATs/step on 105k pairs, 13k touching;
rapier maintains 3.4x fewer pairs): paired same-binary -fr=0/1 A/Bs show
junkyard -8% in every pairing (collide phase -15%), washer neutral to
-5%, pyramid/rain guards neutral, OFF costs nothing. Same-session
cross-engine junkyard: rapier's -8% lead closes to ~-3%.
pgo.sh now trains BOTH modes (single-mode training starved the remaining
full-SAT path); checked-in profile retrained dual-mode. SATCache pose
serialized in snapshots. feature_recycled_contact_count in Counters.
README: port-extension subsection, soundness argument, updated notes.
Also: rapier bench --probe mode from the workload-probe session.
179/185/179/179 tests green with the tier ON, zero warnings,
determinism suite passes across runs/workers/task systems.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- libs/rapier/crates/bench now mirrors every box3d benchmark scene
(trees100/50/25, junkyard, rain, washer added to large_pyramid/
many_pyramids/joint_grid), same -b indices as the box3d benchmark.
Geometry, densities, filters, spawn cadence and joint counts match;
body/collider/joint counts verified equal on all nine scenes.
- Fix an index-out-of-bounds panic in the vendored rapier simd-stable
constraint grouping (interaction_groups.rs): bodies in a different
island than the interaction (kinematic drivers, dynamics mid
island-merge) indexed the wrong island's conflict masks. Out-of-island
bodies are now exempt from conflict tracking, matching the solver's
existing boundary treatment (bounds-checked gathers, dropped scatters).
Original three scenes reproduce their previous timings after the fix.
- README: full nine-scene single-threaded matrix vs rapier (box3d wins
7/9, +33% geomean; rapier wins the hull-churn scenes junkyard/washer)
with comparability caveats for the extended scenes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Geomeans over the nine real scenes: +7% w=1 / +9% w=8. The scene stays
in the benchmark binaries (upstream suite parity); its fixed-overhead
story is kept in the known-remainder notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
junkyard now shows its real post-fix +17%; footnote lattice replaced by
one measurement-conditions note. Geomeans +7% w=1 / +13% w=8.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure full-update pipeline is +38% vs C (both engines, recycling forced
off), diluted to +17% by the at-parity recycle path. Gap is diffuse
(1.3-1.5x per pipeline function); all concentrated hypotheses measured
~zero, including a staging rewrite that halved build_face_a_contact's
instruction count with zero wall-clock effect (the bloat was cold code).
Note: sample attribution unreliable on PGO binaries (hot/cold splits).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disassembly attribution on current binaries: junkyard's +23% sits
entirely in the NON-SAT narrow phase (2.06x C) — the edge SAT is now
FASTER than C. Cause: LLVM+PGO mega-inlining (third occurrence) —
update_contact compiled to 5.5x C's instruction count, collide_hulls had
no symbol at all. inline(never) on collide_hulls /
compute_convex_manifold / query_face_directions restores C's layout:
junkyard -3.6% paired (retrained profile), washer neutral, others
untouched.
hull_at coverage extended to build_face_a_contact / build_polygon /
clip_segment_to_hull_face / find_incident_face for contract completeness
— measured NEUTRAL beyond the boundary fix, and the feature's earlier
-3.6% is now captured by the safe attribute instead. README documents
the demotion honestly (the safe fix superseded the unsafe one).
Gates: 179/179/185/179 tests (default/unchecked-hulls/dp/nosimd), hash
0x61E35C31 everywhere, zero warnings. PGO profile retrained.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Matrix cells stay default-build (cold-window run); the hull scenes'
opt-in feature gains are annotated as paired deltas rather than absolute
cross-session numbers (thermal windows differ ~10%, mixing them would
misstate both). Algebraic-float-ops evaluation moved to a dedicated
'Evaluated ideas' section at the bottom.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
unchecked-hulls (off by default): elides bounds checks on hull-topology
indexing in the three SAT hot loops via a cfg'd accessor. Safety contract
= hull connectivity invariants validated at construction (hull.rs
is_valid_hull_impl + create_hull asserts), immutable behind Arc; debug
builds always assert, so every test run exercises the contract. Measured
(paired, retrained PGO): junkyard -3.6%, washer -2%, nothing elsewhere —
documented honestly that the checks were NOT most of the hull residue.
Tests 179 green with and without the feature; hash 0x61E35C31 both.
README: evaluation of the newly-stabilized algebraic float ops idea —
incompatible with the determinism contract as a default (compiler-
version/ISA-dependent results break cross-arch equality and cross-build
replay), modest expected upside since hot paths are already hand-
contracted; possible future opt-in, not planned.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A sweep of every C b3ParallelFor/enqueue dispatch against the port found
the finalize-bodies pass (per-body transforms, AABB updates, sleep
accounting, continuous/TOI) and the bullet pass were left serial when
threading was ported — C runs both under b3ParallelFor. rain's w=8 gap
was almost entirely this serial fraction (Amdahl decomposition showed
its parallel portion already at C parity).
FinalizeCtx mirrors the collide pass's pattern: taken arrays + SyncSlice
disjoint per-body access, per-worker task contexts, deterministic merges
(bitset OR, split-candidate max like C), bullet list via atomic cursor
(C's b3AtomicFetchAddInt mirror). No new unsafe primitives. Pre-solve/
custom-filter callbacks force single-worker like collide.
w=8: rain +42% -> +8%, joint_grid +36% -> +11%, large_pyramid and
many_pyramids and trees25 at parity; geomean +11%. Serial geomean +5%
(Rust wins joint_grid/large_pyramid/many_pyramids outright). PGO profile
retrained; README tables + narrative updated. Remaining known
serial-vs-C difference: the split-island enqueue overlap (documented).
Hash 0x61E35C31 bit-identical (runs, workers 1/2/4, external tasks);
179/185/179 tests, zero warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Training set was missing washer and trees50/25 (washer -1.5% with
coverage, large_pyramid unchanged — no dilution). Profile refreshed.
Small-stage main-only fast path (generalizing C's single-block
shortcut): swept cutoffs 32/64/256 at w=8 — only large_world benefited
(-8% of ~11ms); rain regressed at every cutoff (its small-count stages
are mesh-contact stages with heavy per-item cost — serializing them
starves real parallelism) and joint_grid has few fat stages (grid
coloring = 2-4 colors), so its w=8 gap is NOT thin-stage sync. Reverted
per the measurable-win rule; negative result documented in the README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disassembly census showed the joint solvers at exact FMA parity with C
but +110 loads/+54 stores per joint: the full 56-byte BodyState get/set
round trip keeps untouched fields live across the ~1000-instruction
solve bodies. StateAccess::set_velocities (same unsafe contract as set,
velocities only, like C's in-place stores) + get_ref field extraction
across all 16 warm-start/solve functions in the 8 joint types.
joint_grid: 817 vs C 801 ms (was -11%). Full fresh matrix in README:
serial geomean +7% vs C with Rust WINNING large_pyramid (-6%) and
many_pyramids (-3%); w=8 geomean +28%. Checked-in PGO profile retrained
for the new code (stale profile cost ~13% on joint scenes).
Same change was measured neutral for contact scatter and correctly
dropped there (state live ~40 instrs vs ~1000) — both verdicts in the
README as a paired case study.
Hash 0x61E35C31 bit-identical everywhere; 179/185/179 tests, zero
warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cargo build --release on anything in the workspace now gets the PGO'd
box3d automatically (-Cprofile-use=libs/box3d/box3d.profdata; verified:
default build runs at the explicit-PGO binary's speed). The profile is
target-independent — x86_64 cross-build with the ARM-trained profile
compiles clean — and degrades gracefully when stale (unmatched functions
keep normal heuristics). Retrain with libs/box3d/pgo.sh.
Fresh four-way interleaved matrix in the README: default box3d is now
faster than or equal to rapier-simd on all three scenes (1118 vs 1451,
1510 vs 1690, 912 vs 914 ms) and faster than non-PGO C on two of three
(C keeps joint_grid 816 vs 912). 179/185/179 tests green with the
config active.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pgo.sh: instrument -> train on the benchmark scenes -> merge -> rebuild.
Paired same-machine runs: large_pyramid 1177 vs 1457 ms (-19%, now 15%
faster than the non-PGO C build), junkyard -14%, many_pyramids -11%.
Determinism hash unchanged under the PGO binary (0x61E35C31 across
runs/workers/task systems) — PGO changes layout/inlining, never
arithmetic. README notes the C-reference fairness caveat.
Also documented as tried-and-dropped (noise-floor in paired A/B, per
the keep-only-measurable-wins rule): cache-line padding of stage-sync
atomics, narrow velocity-only scatter writes, compound child Arc clone
(already eliminated by the earlier scratch fix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Disassembly-driven (fork agents confirmed the stalls, killed the
bounds-check and recycle-rate hypotheses with instruction-level and
runtime-counter evidence — recycle counts are bit-identical to C):
- Manifolds inline-when-single store: Contact.manifolds Vec<Manifold> ->
enum { None, One(Manifold), Many(Vec) } with deref-as-slice. Convex
contacts keep their manifold inline (the Rust equivalent of C's block-
allocator arena locality — the per-contact heap chase was the main
stall in collide/prepare/store). Contact is #[repr(C)] with manifolds
last so hot header fields stay on the leading cache lines. Public
ContactData API unchanged via Deref; contact_solver.rs needed zero
changes. Pure storage change: determinism hash identical (0x61E35C31).
- #[inline(never)] on update_contact + the four convex stage functions:
C compiles these standalone; LLVM had inlined all of them into one
13.6 KB execute_block paying constant register-spill traffic.
Definitive cold-machine matrix: serial geomean 1.15x -> 1.12x vs C
(many_pyramids 2071 vs 1949 ms, large_pyramid 1501 vs 1392); 8-worker
geomean 1.35x -> 1.30x. vs rapier-simd (adjacent runs): box3d ahead 16%
on large_pyramid and 3% on joint_grid, behind 5% on many_pyramids (was
21%). README grids updated.
179/185/179 tests green, zero warnings, hash unchanged across workers/
arch/task systems.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-vendors wide 0.7 + safe_arch, restores the upstream simd-stable
wiring in rapier3d/parry3d manifests and the cfg-simd source, and drops
the added 'stripped build does not support SIMD' guards (upstream's
simd-vs-enhanced-determinism exclusivity guard kept).
Interleaved single-thread retest (min of 4): SIMD buys rapier 1.8-2.2x;
box3d vs rapier-simd is now near parity — large_pyramid 1579 vs 1638 ms,
joint_grid 957 vs 1008 ms (box3d ahead), many_pyramids 2389 vs 1970 ms
(rapier ahead). box3d README grid updated with the honest three-column
table; box3d keeps cross-arch determinism + zero deps at that speed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same scenes/geometry/materials/dt, matched solver budget (4 substeps vs
4 solver iterations), interleaved min-of-4 runs: large_pyramid 2.23x,
many_pyramids 1.82x, joint_grid 1.88x (geomean ~1.97x). Table + fairness
notes at the top of the box3d README (vendored rapier has no SIMD;
enhanced-determinism measured free on these scenes).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- joint prepare: read BodySim through references (was deref-copying
220 bytes twice per joint per step; prepare_joint now at C parity)
- FMA contraction extended to joint solvers (32 sites; hash re-baselined
to 0x61E35C31, still bit-identical across workers/arch/task systems)
- scheduler: workers spin ~tens of us before committing to a kernel
sleep (semaphore try_acquire spin phase; A/B: large_world w=8
24 -> 11.5 ms, joint_grid w=8 1.58x -> 1.49x, other scenes neutral;
intentional deviation from C documented in README)
- tried and reverted: chunks_exact twin-pair edge SAT (won 5% on
junkyard compounds, cost box-box scenes 4-8%; keeps C 1:1 loop shape)
- README: fresh benchmark matrix, second-round notes, stale external
task-hook claim fixed
179/185/179 tests green, zero warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Profiling-driven (sample + disasm comparison vs clang -O3). All changes
safe Rust except one debug_assert-guarded extension of the existing
SyncSlice unsafe contract. Determinism preserved: hash bit-identical
across workers 1/2/4, NEON/SSE2/scalar, internal/external task systems
(new baseline 0x9018E2D8 after approved FMA contraction).
- f32/f64::mul_add contraction in hot scalar math (= C's -ffp-contract=on;
89 sites; wide SIMD ops untouched like C intrinsics). large_pyramid
now at parity with C (1387 vs 1373 ms serial)
- FloatW::get/set: direct lane load/store instead of vector-through-stack
round trip; layout asserted at compile time
- gather_bodies by reference (removes 20-register spill storm)
- per-worker capacity-preserving scratch for convex + mesh collide paths
(C-arena equivalent; mesh path allocated per triangle and serialized
the parallel collide pass on allocator locks)
- update_contact: borrow shapes instead of cloning (deep compound
geometry clones + cross-worker Arc traffic; junkyard w=8 -39%)
- scheduler semaphore: two-level atomic fast path (C uses
dispatch_semaphore_t; old Mutex+Condvar locked every enqueue)
- SyncSlice::get_ref/get_mut unchecked indexing under the existing
unsafe contract, debug_assert-guarded (-6% serial)
- README/PORTING: new numbers, FMA sync conventions, known remainders
179/185/179 tests green (default/double-precision/disable-simd), zero
warnings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full op-stream player in recording_replay.rs (b3RecPlayer port):
opcode dispatch for ~150 ops, StateHash verification at every step
marker, query replay with bitwise comparison, keyframe ring with
budget-driven interval doubling, seek/restart/scrub, validate_replay.
tests/test_recording.rs ports test_recording.c (17 tests incl.
record-at-4-workers/replay-at-1-and-4 hash equality). 179/185/179
tests green across default/double-precision/disable-simd, zero
warnings, determinism hash unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All ~140 opcodes from recording_ops.inl with exact C values, capture hooks
in every mutator and query (~137 sites across body/shape/joint/world),
48-byte header with registry locator backpatch, snapshot seed, query tag
interning, state-hash anchors per step. Recording is observer-only
(bit-identical world state with and without a recording attached).
Replay/player side lands separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- WorldDef enqueue_task/finish_task/user_task_context (C contract incl.
null-return-means-inline); TaskSystem dispatch (Serial/Internal/External)
replaces the bare scheduler; determinism hash bit-identical through an
external thread-per-task system.
- examples/box3d: makepad app rendering the live simulation (offscreen 3D
pass with depth, orbit/zoom camera, instanced lit boxes/spheres, 204-box
pyramid + spheres, 4-worker solver, Space to reset).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of the C threading design: worker threads with a fixed task ring and
help-while-waiting finish (scheduler.rs), atomic block-claiming parallel_for,
and the solver's stage machinery (per-block syncIndex CAS, sync-bits stage
advancement, mainClaimed race). Parallel narrow phase, broad-phase pairs,
sensors, finalize. Shared access goes through documented disjointness
primitives (sync.rs: SyncPtr/SyncSlice/AtomicIndex); worker_count 1 keeps the
serial path bit-identically. Results are bit-identical at any worker count
(determinism hash 0x7A796F4F asserted at 1/2/4 workers). 8 workers: 3.4-5.7x
over serial on heavy scenes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Persist solver constraint arrays/spans/stage blocks and broad-phase pair
query buffers across steps instead of reallocating each world_step.
Bit-identical results (determinism hash unchanged); washer -7.6%, small
wins on trees/rain, pairs stage -6% on junkyard. A contact-manifold
reuse attempt regressed pyramid scenes and was dropped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- contact solver wide ops + V32 now have real SSE2 and NEON paths selected
by target arch; scalar fallback behind the disable-simd feature. All three
paths are bit-identical (cross-arch determinism verified: same ragdoll
hash on NEON, SSE2 under Rosetta, and scalar).
- double-precision feature (C BOX3D_DOUBLE_PRECISION): f64 world positions
with the exact C boundary-function semantics; enables the far-from-origin
test halves (157 tests in DP mode, 151 default).
- world snapshots: recording substrate subset (buffer/writers/geometry
registry/readers) + world_snapshot.c port; bit-identical continuation
after restore, corrupt-image rejection.
- examples/benchmark.rs: all 10 C benchmark scenarios; serial Rust runs
1.05-1.55x slower than C -O2 at one worker (geomean ~1.3x with fat LTO).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full engine port in libs/box3d: math, geometry, GJK/TOI, hull builder,
dynamic tree, manifolds, constraint graph, solver (serial, scalar SIMD
path), all 8 joint types, sensors, mover, world API. 147 ported C unit
tests green in debug and release. See libs/box3d/README.md for the
upstream revision and sync notes, PORTING.md for conventions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* TextInput: expose the location of the caret/cursor
in absolute window-relative coordinates.
THis allows, for ex, a widget to be placed relative to the
current location of the text being inputted by the user.
* View: add fn for toggling `optimize` and force texture caching
Add CachedView fns that make it easier to control its parameters:
* set_optimize(cx, ViewOptimize): switch optimize mode at runtime and
allocate the draw_list on demand. Previously `optimize` was set once from
`texture_caching` and never reset, so a view couldn't toggle between direct
and texture-cached rendering per frame.
* set_texture_max_height(Option<f64>): cap the Texture-mode render turtle's
height so a tall Fit-height cached view can't allocate a render target past
the GPU's max texture size (was a hard MTLTextureDescriptor abort once
content exceeded 16384px). None (default) leaves it uncapped; content past
the cap is clipped. Only affects Texture mode.
* redraw_texture_cache() / force_texture_redraw: force one offscreen
re-render after a content repopulate or an optimize-mode flip. The
rect-based will_redraw check can't see a content change on a recycled or
toggled view, so without this it would composite a stale texture.
* view_size is now updated in every optimize mode, not just draw-list modes.
The None (direct-render) path previously left it stale, so a view toggled
None<->Texture sized its next offscreen turtle from an old height and
clipped/mis-positioned its content.
in absolute window-relative coordinates.
THis allows, for ex, a widget to be placed relative to the
current location of the text being inputted by the user.
- Rename splash2.md -> splash.md so the tracked doc matches what aichat
references via include_str!/read_to_string (a clean clone now builds).
- Fix the live-read path (CARGO_MANIFEST_DIR was ../../../ = one dir above
the repo, loading a stale doc); now ../../splash.md -> repo-root glass doc.
- Full repaint (cx.redraw_all) on Clear/remove so self-managed glass overlay
draw lists aren't left composited stale.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New GlassPanel widget (widgets/src/glass_panel.rs) + lib export
- gauss_view: honor surface_alpha for translucent glass surfaces
- examples/glass: standalone demo (wired into Cargo workspace + makepad.splash)
- rustfmt.toml: re-enable disable_all_formatting to stop rustfmt from
reformatting the whole tree on save
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
script_apply_eval! expands to a ScriptMod{file, line, column, ...} with
its code field prefixed by __script_source__. This adds a runtime body
to the VM just like script_mod! does, so collect_compiled_sites_for_file
counts it toward the compiled-site total for that file.
The file extractor (extract_script_mods_from_rust_file) only scans for
the script_mod! macro pattern, so any file with N script_apply_eval!
calls and M script_mod! calls causes:
hot reload could not match script_mod! blocks for <file>:
runtime has N+M, file has M
Skip __script_source__ bodies in collect_compiled_sites_for_file. These
are Rust-driven runtime evals, not static DSL blocks, so the extractor
cannot match them and hot reload cannot update them anyway.
Fixes hot reload for files that mix script_mod! with script_apply_eval!.
* Support standard keyboard navg shortcuts/keys in TextInput
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
* Add `CropToFill` image fit variant, improve ImageFit docs
This allows you to easily achieve the "centered cropped fit" that most apps
want for things like avatars or small thubmnails that get masked.
* Detect and support hardware keyboards, distinguish from soft/virtual kbd
Mimic desktop behavior on mobile systems as much as possible.
This is esp important for tablets like iPad OS where you're more likely
to have a real physical keyboard attached.
For iOS:
* Arrow keys and Home/End/PageUp/PageDown navigate and auto-repeat
at the system-defined rate (connected via `UIKeyCommand`)
* Cmd+Enter to submit a `TextInput` and Cmd+C/X/V clipboard shortcuts now work.
* Ensure the pop-up diacritic/accent menu is properly placed using a hidden
`UITextInput` native widget, which acts as a sort of "proxy"
* Proactively drain `ShowTextIME` after each draw so the IME position will be
properly updated after each keystroke.
* Importnatly, don't mark the IME dismissed when a hardware keyboard is attached.
For both iOS & Android:
* Add a `has_physical_keyboard()` detection mechanism across both backends,
and fix platform-specific key repeat behavior
For Android:
* Ensure clipboard cut/copy works using the same Ctrl shortcuts (API 26+)
Soft/virtual keyboard/IME changes:
* For multiline TExtInputs, a soft keyboard Enter/Return key will always just
insert a new line, to avoid complexity with keyboard shortcut cfgs.
* CJK keyboard character selection should also be properly positioned now
* minor optimization to avoid re-setting IME pos if it didn't change
* Ensure inline composition is still shown in TextInput on all platforms
This is mostly relevant when using CJK and other similar IMEs.
Previously Makepad didn't shown any "echoes" of the single latin chars
that the user would type, but it would correctly input the selected CJK
glyph. So if you typed `nihao` and then selected `你哈`, then you would
see the proper chinese characters but not the latin "nihao".
Tha't s a bit confusing while typing.
Summary of the fixes per platform:
macOS:
- `set_marked_text` now forwards the marked (composition) text to the
focused TextInput with `replace_last = true`; previously it only stored
it in an ivar. `unmark_text` clears the preview, and both it and
`insert_text` share a new `clear_marked_text_ivar` helper so a commit
doesn't emit a second, destructive text-input event.
Windows:
- Add `WM_IME_COMPOSITION` handling: commit `GCS_RESULTSTR`
(`replace_last = false`) and show `GCS_COMPSTR` inline
(`replace_last = true`), and clear on `WM_IME_ENDCOMPOSITION`. The
message is consumed so DefWindowProc neither draws its own composition
window nor synthesizes a duplicate WM_CHAR for the result.
- Extend the vendored `windows` binding with `ImmGetCompositionStringW`,
`GCS_COMPSTR`/`GCS_RESULTSTR`, and `WM_IME_COMPOSITION`/
`WM_IME_ENDCOMPOSITION`, which it didn't previously generate.
Linux (Wayland):
- Handle the `zwp_text_input_v3` `PreeditString` event (previously empty)
and apply the double-buffered preedit/commit state on `Done`, in the
protocol-mandated order (commit, then preedit), clearing the preview
when a cycle carries no preedit.
Linux (X11):
- Create the input context with XIM on-the-spot (`XIMPreeditCallbacks`)
and forward the preedit string from the draw/start/done callbacks,
falling back to `XIMPreeditNothing` if the IM server doesn't support
callbacks. Callbacks run inside `XFilterEvent`, so they only mutate a
thread-local that the event loop drains into the widget afterward,
avoiding re-entrant access to the app.
Android:
- Don't mark the IME dismissed when a physical keyboard is attached
(mirrors the iOS guard). Android was unconditionally calling
`text_ime_was_dismissed()` on soft-keyboard hide, which tore down the
IME connection that hardware-key composition relies on.
* iOS: replace custom `UITextInput` with a native `UITextView`
`UITextView` is a full system-native keyboard client, so we get all the
major features for free: language HUD pill and the complete globe/Ctrl+Space
shortcut to cycle between IMEs/languages.
Makepad basically just mirrors the state of the system native text view,
via the `full_state_sync`, but the actual native text view is kept invisible
so it doesn't interfere with what we render in Makepad's TextInput.
Notably, the Full Keyboard Accessibility setting now does work properly,
whereas it did not before with our UITextInput-based approach.
We also make sure that arrow keys, nav keys, auto-repeat, and modifiers
are properly hanlded so we can retain the expected kbd shortcuts,
like other desktop platforms.
* iOS: remove the old `UITextInput` connection with the Makepad TextInput
We've now switched to the native UITextview, so we don't need this any more.
* iOS: fix desync during fast typing
Ensure there's no race between the native UITextView and
Makepad's TextInput, as the Enter/REturn key needs special handling
w.r.t. how `pressesBegan` gets it (From a real hardware kbd).
* TextInput: more iOS integration, and text input types
more native integration for things like username/password,
new password fields, email, address, URLs, etc.
These tell iOS to change the keyboard layout/type for the text input.
* iOS: don't let Full Keyboard Access focus on our hidden native cursor
* cleanup
* iOS/TextInput: fix perf issues
* iOS TextInput: more fixes for read-only efficiency, and filtered input
Also port some of these fixes to Android's IME integration layer
* iOS/TextInput: hide the native caret iOS draws during autocorrect
but still allow the "decline autocorrect" bubble to popup where that
hidden caret is located (and the CJK candidate window in the same spot)
* Avoid script VM re-entrant panic: defer animator_cut/play if script VM is held
`animator_cut` / `animator_play` call `cx.with_vm`, which panics
(*"Script VM swapped off"*) when invoked during an apply walk — e.g. a
widget's `on_after_apply` on `ScriptReapply` / `Reload` — because the VM
is already taken for the duration of that walk's enclosing `cx.with_vm`.
- The derive macro's `animator_cut_scoped` / `animator_play_scoped` now
check `cx.is_script_vm_held()`; when held, they queue the op
(`defer_cut` / `defer_play`) and return instead of re-entering the VM.
- `animator_handle_event_scoped` replays the queue via `flush_deferred`
on the next frame, once the VM is free.
The defer path runs **only** in the formerly-panicking case, so VM-free
animations are byte-for-byte unchanged.
Also adds a re-entrancy-naming panic (`VmHolderGuard`) plus
`Cx::try_with_vm` / `Cx::is_script_vm_held` for diagnosing and handling
this class of bug.
* Better spacing/positioning for IME popups like the CJK candidate menu
applied to all platforms, but primarily an issue on macOS/iOS.
The candidate/conversion window (e.g. CJK pinyin) was covering the line of
text being composed. Carry the caret-line rect (not just a point) through
ShowTextIME and feed each backend its native "keep clear of this line" API,
so the OS places the candidate directly above/below the line with a small gap:
- macOS: firstRectForCharacterRange returns the line rect via AppKit
convertRect:toView:nil + convertRectToScreen (drops the hand-rolled
screen-coord math + fudge offsets); invalidate on caret move.
- Windows: ImmSetCandidateWindow with a CFS_EXCLUDE line rect.
- Wayland: set_cursor_rectangle with the real line rect.
- X11: XNSpotLocation/XNArea at the line.
- iOS: return the true composing-line box from firstRectForRange so iOS flips
around the real edges (consistent at any screen position) instead of a
degenerate point; only while marked text is active, to avoid an oversized
autocorrect highlight when typing normally.
* Fix Linux X11 behavior: Ctrl-based kbd shortcuts didn't work in TextInput
also trying to fix X11 behavior for positioning the CJK candidate window,
turns out there was an X11 bug for Ubuntu 22 and older so it's not always
possible, but we can attempt a workaround if errors occur (based on that,
we try to auto-detect the version of X11)
* fix X11 event loop latency by draining only a max of 64 events before redrawing
still working on X11 CJK candidate window positioning...
* add logs to X11 ime to figure out wtf is going on
* more robust fallbacks for X11 CJK candidate window positioning... grr
* maybe try to set the XFontSet attribute? for CJK candidate positioning
* positioning works now but there is a bit of overlap still
* now that X11 CJK candidate positioning works in some cases,
we need to pass the full rectangle containing the current line of text
to the X11 library so that it can position the window both on top
and beneath the current line of text, if needed.
* tweaking X11 CJK candidate positioning
* abandon the screen-positioning heuristic
Instead, we just send the bounding rect of the current text character
and hopefully let the X11 platform libs decide where to put the
CJK candidate popup
* add more spacing to the bounding rect on X11
* tweak for a bit more space between CJK candidate window
* more tweaks, rect height isn't being respected for some reason...
* attempting to add more instrumentation to figure out wtf is going on with X11 CJK positioning
* remove bad instrumentation that was causing freezes. ugh
* different approach for IME placement on X11
* previous positioning attempts for X11 didn't work.
New strategy: let it be positioned, and then try to move it
* still trying to fix X11 CJK candidate window positoining...
* trying to find CJK candidate window with X11 queries (To move it)
* abandon window scanning approach
* better approach, now just tweaking it
* fix one case where the candidate window was flipped but it pointing too low
* tweaking more
* trying to fix above-text line positioning
* still trying to tweak CJK candidates ABOVE the text line
* be more conservative when guessing whether X11 will show the CJK candidate above or below
* improve size heuristic for CJK candidate height
* calling X11 as complete now. jfc. Cleanup, remove debug logs, etc
* TextInput: infer soft-keyboard `input_mode` from `content_type` if its unset
New functon: `effective_input_mode()` will now derive a keyboard layout
from the `content_type`, if one was provided and if it makes sense to infer.
Explicitly setting the `input_mode` will always take precendence.
* Run script-VM gc in the desktop and mobile event loops, not just macOS
Only macOS was calling the script VM's garbage collector.
Now we call it everywhere, based on the original implementation in macOS.
* Limit cache growth for text/script, reclaim memory after gc
This PR includes several misc improvements to reduce memory usage and/or
return unused memory to the OS properly.
- slug atlas: reset the append-only curve buffer past a cap (mirrors the raster
atlas reset: cleared at the prepare_textures boundary, forcing a rebuild), so
it no longer accumulates every distinct large glyph ever rendered.
- script heap: in gc(), truncate the String reuse pool and shrink over-allocated
free-list/slot capacity (never moves a live slot, so all refs stay valid).
- font outline cache: cap per-font distinct-glyph entries (clear-on-exceed).
- image cache: evict Loaded entries past a cap; widgets keep their own texture
clones so displayed images are unaffected, and in-flight loads are preserved.
The string intern table is intentionally left unbounded: it backs stable
pointer-based FontId/FontFamilyId, and is bounded by the few distinct font names.
* Support standard keyboard navg shortcuts/keys in TextInput
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
* Add `CropToFill` image fit variant, improve ImageFit docs
This allows you to easily achieve the "centered cropped fit" that most apps
want for things like avatars or small thubmnails that get masked.
* Detect and support hardware keyboards, distinguish from soft/virtual kbd
Mimic desktop behavior on mobile systems as much as possible.
This is esp important for tablets like iPad OS where you're more likely
to have a real physical keyboard attached.
For iOS:
* Arrow keys and Home/End/PageUp/PageDown navigate and auto-repeat
at the system-defined rate (connected via `UIKeyCommand`)
* Cmd+Enter to submit a `TextInput` and Cmd+C/X/V clipboard shortcuts now work.
* Ensure the pop-up diacritic/accent menu is properly placed using a hidden
`UITextInput` native widget, which acts as a sort of "proxy"
* Proactively drain `ShowTextIME` after each draw so the IME position will be
properly updated after each keystroke.
* Importnatly, don't mark the IME dismissed when a hardware keyboard is attached.
For both iOS & Android:
* Add a `has_physical_keyboard()` detection mechanism across both backends,
and fix platform-specific key repeat behavior
For Android:
* Ensure clipboard cut/copy works using the same Ctrl shortcuts (API 26+)
Soft/virtual keyboard/IME changes:
* For multiline TExtInputs, a soft keyboard Enter/Return key will always just
insert a new line, to avoid complexity with keyboard shortcut cfgs.
* CJK keyboard character selection should also be properly positioned now
* minor optimization to avoid re-setting IME pos if it didn't change
* Ensure inline composition is still shown in TextInput on all platforms
This is mostly relevant when using CJK and other similar IMEs.
Previously Makepad didn't shown any "echoes" of the single latin chars
that the user would type, but it would correctly input the selected CJK
glyph. So if you typed `nihao` and then selected `你哈`, then you would
see the proper chinese characters but not the latin "nihao".
Tha't s a bit confusing while typing.
Summary of the fixes per platform:
macOS:
- `set_marked_text` now forwards the marked (composition) text to the
focused TextInput with `replace_last = true`; previously it only stored
it in an ivar. `unmark_text` clears the preview, and both it and
`insert_text` share a new `clear_marked_text_ivar` helper so a commit
doesn't emit a second, destructive text-input event.
Windows:
- Add `WM_IME_COMPOSITION` handling: commit `GCS_RESULTSTR`
(`replace_last = false`) and show `GCS_COMPSTR` inline
(`replace_last = true`), and clear on `WM_IME_ENDCOMPOSITION`. The
message is consumed so DefWindowProc neither draws its own composition
window nor synthesizes a duplicate WM_CHAR for the result.
- Extend the vendored `windows` binding with `ImmGetCompositionStringW`,
`GCS_COMPSTR`/`GCS_RESULTSTR`, and `WM_IME_COMPOSITION`/
`WM_IME_ENDCOMPOSITION`, which it didn't previously generate.
Linux (Wayland):
- Handle the `zwp_text_input_v3` `PreeditString` event (previously empty)
and apply the double-buffered preedit/commit state on `Done`, in the
protocol-mandated order (commit, then preedit), clearing the preview
when a cycle carries no preedit.
Linux (X11):
- Create the input context with XIM on-the-spot (`XIMPreeditCallbacks`)
and forward the preedit string from the draw/start/done callbacks,
falling back to `XIMPreeditNothing` if the IM server doesn't support
callbacks. Callbacks run inside `XFilterEvent`, so they only mutate a
thread-local that the event loop drains into the widget afterward,
avoiding re-entrant access to the app.
Android:
- Don't mark the IME dismissed when a physical keyboard is attached
(mirrors the iOS guard). Android was unconditionally calling
`text_ime_was_dismissed()` on soft-keyboard hide, which tore down the
IME connection that hardware-key composition relies on.
* iOS: replace custom `UITextInput` with a native `UITextView`
`UITextView` is a full system-native keyboard client, so we get all the
major features for free: language HUD pill and the complete globe/Ctrl+Space
shortcut to cycle between IMEs/languages.
Makepad basically just mirrors the state of the system native text view,
via the `full_state_sync`, but the actual native text view is kept invisible
so it doesn't interfere with what we render in Makepad's TextInput.
Notably, the Full Keyboard Accessibility setting now does work properly,
whereas it did not before with our UITextInput-based approach.
We also make sure that arrow keys, nav keys, auto-repeat, and modifiers
are properly hanlded so we can retain the expected kbd shortcuts,
like other desktop platforms.
* iOS: remove the old `UITextInput` connection with the Makepad TextInput
We've now switched to the native UITextview, so we don't need this any more.
* iOS: fix desync during fast typing
Ensure there's no race between the native UITextView and
Makepad's TextInput, as the Enter/REturn key needs special handling
w.r.t. how `pressesBegan` gets it (From a real hardware kbd).
* TextInput: more iOS integration, and text input types
more native integration for things like username/password,
new password fields, email, address, URLs, etc.
These tell iOS to change the keyboard layout/type for the text input.
* iOS: don't let Full Keyboard Access focus on our hidden native cursor
* cleanup
* iOS/TextInput: fix perf issues
* iOS TextInput: more fixes for read-only efficiency, and filtered input
Also port some of these fixes to Android's IME integration layer
* iOS/TextInput: hide the native caret iOS draws during autocorrect
but still allow the "decline autocorrect" bubble to popup where that
hidden caret is located (and the CJK candidate window in the same spot)
* Avoid script VM re-entrant panic: defer animator_cut/play if script VM is held
`animator_cut` / `animator_play` call `cx.with_vm`, which panics
(*"Script VM swapped off"*) when invoked during an apply walk — e.g. a
widget's `on_after_apply` on `ScriptReapply` / `Reload` — because the VM
is already taken for the duration of that walk's enclosing `cx.with_vm`.
- The derive macro's `animator_cut_scoped` / `animator_play_scoped` now
check `cx.is_script_vm_held()`; when held, they queue the op
(`defer_cut` / `defer_play`) and return instead of re-entering the VM.
- `animator_handle_event_scoped` replays the queue via `flush_deferred`
on the next frame, once the VM is free.
The defer path runs **only** in the formerly-panicking case, so VM-free
animations are byte-for-byte unchanged.
Also adds a re-entrancy-naming panic (`VmHolderGuard`) plus
`Cx::try_with_vm` / `Cx::is_script_vm_held` for diagnosing and handling
this class of bug.
* Better spacing/positioning for IME popups like the CJK candidate menu
applied to all platforms, but primarily an issue on macOS/iOS.
The candidate/conversion window (e.g. CJK pinyin) was covering the line of
text being composed. Carry the caret-line rect (not just a point) through
ShowTextIME and feed each backend its native "keep clear of this line" API,
so the OS places the candidate directly above/below the line with a small gap:
- macOS: firstRectForCharacterRange returns the line rect via AppKit
convertRect:toView:nil + convertRectToScreen (drops the hand-rolled
screen-coord math + fudge offsets); invalidate on caret move.
- Windows: ImmSetCandidateWindow with a CFS_EXCLUDE line rect.
- Wayland: set_cursor_rectangle with the real line rect.
- X11: XNSpotLocation/XNArea at the line.
- iOS: return the true composing-line box from firstRectForRange so iOS flips
around the real edges (consistent at any screen position) instead of a
degenerate point; only while marked text is active, to avoid an oversized
autocorrect highlight when typing normally.
* Fix Linux X11 behavior: Ctrl-based kbd shortcuts didn't work in TextInput
also trying to fix X11 behavior for positioning the CJK candidate window,
turns out there was an X11 bug for Ubuntu 22 and older so it's not always
possible, but we can attempt a workaround if errors occur (based on that,
we try to auto-detect the version of X11)
* fix X11 event loop latency by draining only a max of 64 events before redrawing
still working on X11 CJK candidate window positioning...
* add logs to X11 ime to figure out wtf is going on
* more robust fallbacks for X11 CJK candidate window positioning... grr
* maybe try to set the XFontSet attribute? for CJK candidate positioning
* positioning works now but there is a bit of overlap still
* now that X11 CJK candidate positioning works in some cases,
we need to pass the full rectangle containing the current line of text
to the X11 library so that it can position the window both on top
and beneath the current line of text, if needed.
* tweaking X11 CJK candidate positioning
* abandon the screen-positioning heuristic
Instead, we just send the bounding rect of the current text character
and hopefully let the X11 platform libs decide where to put the
CJK candidate popup
* add more spacing to the bounding rect on X11
* tweak for a bit more space between CJK candidate window
* more tweaks, rect height isn't being respected for some reason...
* attempting to add more instrumentation to figure out wtf is going on with X11 CJK positioning
* remove bad instrumentation that was causing freezes. ugh
* different approach for IME placement on X11
* previous positioning attempts for X11 didn't work.
New strategy: let it be positioned, and then try to move it
* still trying to fix X11 CJK candidate window positoining...
* trying to find CJK candidate window with X11 queries (To move it)
* abandon window scanning approach
* better approach, now just tweaking it
* fix one case where the candidate window was flipped but it pointing too low
* tweaking more
* trying to fix above-text line positioning
* still trying to tweak CJK candidates ABOVE the text line
* be more conservative when guessing whether X11 will show the CJK candidate above or below
* improve size heuristic for CJK candidate height
* calling X11 as complete now. jfc. Cleanup, remove debug logs, etc
* iOS: replace custom `UITextInput` with a native `UITextView`
`UITextView` is a full system-native keyboard client, so we get all the
major features for free: language HUD pill and the complete globe/Ctrl+Space
shortcut to cycle between IMEs/languages.
Makepad basically just mirrors the state of the system native text view,
via the `full_state_sync`, but the actual native text view is kept invisible
so it doesn't interfere with what we render in Makepad's TextInput.
Notably, the Full Keyboard Accessibility setting now does work properly,
whereas it did not before with our UITextInput-based approach.
We also make sure that arrow keys, nav keys, auto-repeat, and modifiers
are properly hanlded so we can retain the expected kbd shortcuts,
like other desktop platforms.
* iOS: remove the old `UITextInput` connection with the Makepad TextInput
We've now switched to the native UITextview, so we don't need this any more.
* iOS: fix desync during fast typing
Ensure there's no race between the native UITextView and
Makepad's TextInput, as the Enter/REturn key needs special handling
w.r.t. how `pressesBegan` gets it (From a real hardware kbd).
* TextInput: more iOS integration, and text input types
more native integration for things like username/password,
new password fields, email, address, URLs, etc.
These tell iOS to change the keyboard layout/type for the text input.
* iOS: don't let Full Keyboard Access focus on our hidden native cursor
* cleanup
* iOS/TextInput: fix perf issues
* iOS TextInput: more fixes for read-only efficiency, and filtered input
Also port some of these fixes to Android's IME integration layer
* iOS/TextInput: hide the native caret iOS draws during autocorrect
but still allow the "decline autocorrect" bubble to popup where that
hidden caret is located (and the CJK candidate window in the same spot)
This avoids a strange case where Linux (both X11 and wayland but
in different ways) failed to properly shut down cleanly.
* Fix X11 close handling by removing destroyed windows from the map,
which helps avoid delivering duplicate window closed events.
* Restructure how close handling happens on Wayland too, and allow
the app to respond to a close request just like other platforms.
* Add a Linux second-signal hard exit so that repeated Ctrl+C
or `kill` comands can actually terminate a failed/hung shutdown.
* Also a tiny fix to windows too: clear Win32 `GWLP_USERDATA` during
`WM_DESTROY` to avoid accessing an old window pointer.
* Ensure that a view that specifies Fit with a max value can be scrolled.
* Turtle: include a view's outer maring in the size calc for a `Fit{max}` bound.
* Modal: dismiss on Escape KeyUp (not KeyDown) so the release can't leak to a
background widget behind the modal.
* Modal: allow scrolling, and reset the scroll to the top when showing it.
* Touch-baased dragging for views (ScrollBar) and PortalList now respect
the blocked scrolling areas, not just the mouse wheel / trackpad scroll.
* Forward the `set_scroll_pos()` through the widget derive traits so that
we don't have to hook it up for each specific widget.
Without this, once you load an SVG for the first time,
you can never change it. This meant that you couldn't change
a buttton's icon, for example, at runtime, even using a script apply.
Now that works, at no cost too, since we track which SVG body/"doc"
has been loaded to ensure we're not re-loading it on every draw
(which was already there, it was just too strict).
* Support standard keyboard navg shortcuts/keys in TextInput
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
* Add `CropToFill` image fit variant, improve ImageFit docs
This allows you to easily achieve the "centered cropped fit" that most apps
want for things like avatars or small thubmnails that get masked.
* Detect and support hardware keyboards, distinguish from soft/virtual kbd
Mimic desktop behavior on mobile systems as much as possible.
This is esp important for tablets like iPad OS where you're more likely
to have a real physical keyboard attached.
For iOS:
* Arrow keys and Home/End/PageUp/PageDown navigate and auto-repeat
at the system-defined rate (connected via `UIKeyCommand`)
* Cmd+Enter to submit a `TextInput` and Cmd+C/X/V clipboard shortcuts now work.
* Ensure the pop-up diacritic/accent menu is properly placed using a hidden
`UITextInput` native widget, which acts as a sort of "proxy"
* Proactively drain `ShowTextIME` after each draw so the IME position will be
properly updated after each keystroke.
* Importnatly, don't mark the IME dismissed when a hardware keyboard is attached.
For both iOS & Android:
* Add a `has_physical_keyboard()` detection mechanism across both backends,
and fix platform-specific key repeat behavior
For Android:
* Ensure clipboard cut/copy works using the same Ctrl shortcuts (API 26+)
Soft/virtual keyboard/IME changes:
* For multiline TExtInputs, a soft keyboard Enter/Return key will always just
insert a new line, to avoid complexity with keyboard shortcut cfgs.
* CJK keyboard character selection should also be properly positioned now
* minor optimization to avoid re-setting IME pos if it didn't change
* Image support: add bmp/qoi,ico, webp, SVG in `Image` widget, 16-bit png
Generally, this commit makes improvements to image decoding and rendering.
Added a bunch of functions for image discovery / metadata gathering:
`decode_image_from_data()`, `image_size_by_data()`, `looks_like_svg()`
Added more `Image[Ref]` functions for other image formats:
`ImageRef::load_{bmp,qoi,ico,gif,webp,svg}_from_data()`, plus a nice
convenience fn for auto-detec+load: `load_image_from_data()`.
Added cheap, lazily-init'd support for SVGs within the `Image` widget.
Fixed some issues with aspect ratio being clobbered during image rotation.
* iOS: fix hardware kbd behavior with "Full Keyboard Access" enabled
That accessibility setting messed with our previous version, but now
we've made it play nicely with FKA.
It's not *quite* perfect yet, we still don't get the nice little
system-native "pill" pop-up that allows you to easily switch between
the languages/IMEs you've enabled. But it sort of works.
* Image support: add bmp/qoi,ico, webp, SVG in `Image` widget, 16-bit png
Generally, this commit makes improvements to image decoding and rendering.
Added a bunch of functions for image discovery / metadata gathering:
`decode_image_from_data()`, `image_size_by_data()`, `looks_like_svg()`
Added more `Image[Ref]` functions for other image formats:
`ImageRef::load_{bmp,qoi,ico,gif,webp,svg}_from_data()`, plus a nice
convenience fn for auto-detec+load: `load_image_from_data()`.
Added cheap, lazily-init'd support for SVGs within the `Image` widget.
Fixed some issues with aspect ratio being clobbered during image rotation.
* Audit and harden image decoding stuff against huge inputs (DoS)
Bound the size of the decoded image, pixel count, frame counts (for animated),
range of SVG sniffing, and encoded file size.
Only once we run those checks do we actually alloc a buffer for the decoded image. before allocating decode buffers. Validate
Add various other checks within the vendored image decoding libraries too.
Especially in right-aligned view rows (`Align: {x: 1.0}`), the turtle logic
wasn't accounting for spacing nor alignment when deciding to wrap.
Now those are taken into account, so we don't get weird cut-off views.
Generally, this commit makes improvements to image decoding and rendering.
Added a bunch of functions for image discovery / metadata gathering:
`decode_image_from_data()`, `image_size_by_data()`, `looks_like_svg()`
Added more `Image[Ref]` functions for other image formats:
`ImageRef::load_{bmp,qoi,ico,gif,webp,svg}_from_data()`, plus a nice
convenience fn for auto-detec+load: `load_image_from_data()`.
Added cheap, lazily-init'd support for SVGs within the `Image` widget.
Fixed some issues with aspect ratio being clobbered during image rotation.
* Support standard keyboard navg shortcuts/keys in TextInput
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
* Add `CropToFill` image fit variant, improve ImageFit docs
This allows you to easily achieve the "centered cropped fit" that most apps
want for things like avatars or small thubmnails that get masked.
* Detect and support hardware keyboards, distinguish from soft/virtual kbd
Mimic desktop behavior on mobile systems as much as possible.
This is esp important for tablets like iPad OS where you're more likely
to have a real physical keyboard attached.
For iOS:
* Arrow keys and Home/End/PageUp/PageDown navigate and auto-repeat
at the system-defined rate (connected via `UIKeyCommand`)
* Cmd+Enter to submit a `TextInput` and Cmd+C/X/V clipboard shortcuts now work.
* Ensure the pop-up diacritic/accent menu is properly placed using a hidden
`UITextInput` native widget, which acts as a sort of "proxy"
* Proactively drain `ShowTextIME` after each draw so the IME position will be
properly updated after each keystroke.
* Importnatly, don't mark the IME dismissed when a hardware keyboard is attached.
For both iOS & Android:
* Add a `has_physical_keyboard()` detection mechanism across both backends,
and fix platform-specific key repeat behavior
For Android:
* Ensure clipboard cut/copy works using the same Ctrl shortcuts (API 26+)
Soft/virtual keyboard/IME changes:
* For multiline TExtInputs, a soft keyboard Enter/Return key will always just
insert a new line, to avoid complexity with keyboard shortcut cfgs.
* CJK keyboard character selection should also be properly positioned now
* minor optimization to avoid re-setting IME pos if it didn't change
Previously, `get_cache_dir()` returned None for Linux (X11, Wayland, Direct)
so there was no shader caching happening like there was on
android and windows.
Now we cache it and also handle removal of stale shader binaries
* video_debug
* Fix video pause being overridden by stall-recovery force-play on macOS
The native AVPlayer poll loop nudged a rate-0 player back into playing
whenever `autoplay` was true, intended as stall recovery but firing every
frame after a user-initiated pause. Once `begin_playback` latched
`autoplay = true` on first start, subsequent pauses were undone on the
next frame poll.
Split user playback intent into a `should_play` field that toggles on
play/pause/resume, and gate the force-play check on that instead of
`autoplay` (which is now a one-shot consumed in `check_prepared`).
Also restore the `Apply::Animate` early return in `Video::on_after_apply`
(needed so hover transitions don't re-decode the PNG/JPG thumbnail every
frame) and drop the redundant `Texture::new(cx)` allocation in
`apply_thumbnail_settings` that load_thumbnail_image immediately
overwrote anyway.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix CPU core locked to 100% on Linux X11/Wayland when idle
The desktop event loop's `select()` call was watching stdin (file descriptor 0)
which acts as ALWAYS readable whenever stdin is redirected to /dev/null
or similar.
So taht was causing the loop to falsely run every time that the select
call was made, even if nothing actually was readable on any of those FDs.
The fix is to just ... not do that, haha. Only makepad-studio uses
something like that, but no longer. It now uses websockets only.
Also, harden vsync behavior by setting `eglSwapInterval` explicitly
(vsync on by default; MAKEPAD_NO_VSYNC opt-out) to ensure that things
that get continuously drawn are capped at the display's refresh rate.
* DrawText: avoid redrawing slug stuff on EVERY frame
On Linux/Windows, the slug text path redrew a draw item's "old area" whenever
that path wasn't drawn in the current draw_text call and the area wasn't Empty.
THis was causing an entire CPU core to be pinned to 100% due to an
infinite loop of repaints.
Now, we only clear a draw item when its area holds genuinely stale content
(instance_count > 0 and a stale redraw_id).
The desktop event loop's `select()` call was watching stdin (file descriptor 0)
which acts as ALWAYS readable whenever stdin is redirected to /dev/null
or similar.
So taht was causing the loop to falsely run every time that the select
call was made, even if nothing actually was readable on any of those FDs.
The fix is to just ... not do that, haha. Only makepad-studio uses
something like that, but no longer. It now uses websockets only.
Also, harden vsync behavior by setting `eglSwapInterval` explicitly
(vsync on by default; MAKEPAD_NO_VSYNC opt-out) to ensure that things
that get continuously drawn are capped at the display's refresh rate.
* Support standard keyboard navg shortcuts/keys in TextInput
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
* Add `CropToFill` image fit variant, improve ImageFit docs
This allows you to easily achieve the "centered cropped fit" that most apps
want for things like avatars or small thubmnails that get masked.
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
There was a bug where apps on iPad would not always be properly redrawn
when being resized (in windowed mode). This fixes that (at least in my testing)
by NOT clearing the dirty state when the MTKView has no render pass descriptor
Thus, the next time it's valid, we repaint it as expected.
* Extend support for system bar appearance to iOS too
* cargo_makepad: fix default icon behavior for iOS
Icons need to not be modified by cargo_makepad if they're already
in the proper iOS-expected format, otherwise they'll end up with
some kind of extra black border around the icon, which looks bad.
Remove the concept of a "full-screen" override for stack nav,
as it's completely useless and just added complexity.
This also fixes the push/pop "sliding" animation to be more fluid
* Dock: avoid ID collisions in drag/drop; never delete dock root in unsplit_tabs
* Clean up and further harden dock logic around splitting/dragging
* cargo_makepad: Android App Bundle builds, API 26 support, stable toolchain
Overhaul the Android build pipeline. Three related build-tooling
changes that share compile.rs/sdk.rs and so are committed together.
Android App Bundle (.aab) support — required for Google Play uploads:
- New `build-aab` command: compile resources with aapt2, link a
proto-format APK, assemble the base module, run bundletool, and sign
with jarsigner.
- New `keystore-create` command wrapping keytool, with a reusable
keystore sidecar file; new `--keystore*`, `--no-sign`,
`--version-code`, `--version-name` flags.
- Version codes may be explicit or auto-generated as a monotonic
YYYYMMDDHH UTC integer.
- Read app id, version, and signing metadata from
`[package.metadata.packager]` / `[package.metadata.makepad.android]`
in Cargo.toml; support a custom AndroidManifest.xml template.
- Upgrade the bundled TOML parser for the dotted keys, inline tables,
and multi-line strings those metadata sections use.
- Download bundletool and copy jarsigner/keytool/aapt2 into the SDK.
minSdkVersion 26:
- Lower the default Android minimum SDK from 33 to 26 and track the
target SDK (35) separately, emitting minSdkVersion and
targetSdkVersion independently in the generated manifest; add a
`--min-sdk-version` override.
Stable Rust toolchain:
- Build Android and iOS on stable instead of nightly. tvOS still needs
nightly for `-Z build-std`, so the channel is resolved per target.
- Add `ensure_rust_toolchain_installed` (install only when missing).
* Android: load newer NDK symbols at runtime to support API 26
With the minimum SDK lowered to 26, NDK entry points that only exist
on newer API levels can no longer be declared with `extern "C"` —
doing so breaks `dlopen`/startup on API 26-28. Resolve them at
runtime instead:
- amidi_sys: lazily `dlopen` libamidi.so (API 29+) into a cached
vtable; the wrappers degrade to error/zero returns when the library
is absent on older devices.
- android_jni: `dlsym` the AChoreographer vsync callbacks, gated on
the running API level.
- ndk_sys: drop the `extern "C"` declarations for
`ANativeWindow_setFrameRate` and the Choreographer callbacks;
android.rs drops the now-unused frame-rate call.
- MakepadActivity: guard `setInitialSurroundingSubText` (API 30+) and
`layoutInDisplayCutoutMode` (API 28+) behind version checks.
- android_jni: the fallback render-loop thread now exits cleanly when
the app is torn down.
* Android: automatic and app-controlled system bar appearance
Add a way to control the tint of the status and navigation bar icons,
fixing white-on-white (invisible) icons when an app draws a light
background under a system dark-mode theme.
- New `Cx::set_system_bar_appearance(SystemBarAppearance)`. The default
`Auto` mode picks dark or light icons from the window background
luminance; `DarkIcons`/`LightIcons` force the choice.
- The `Window` widget resolves the setting each event cycle — for
`Auto`, the Rec.709 luma of `pass.clear_color` — and emits
`CxOsOp::SetSystemBarDarkIcons` only when the resolved value changes.
- On Android this drives `WindowInsetsController.setSystemBarsAppearance`
(API 30+) or the `SYSTEM_UI_FLAG_LIGHT_*` flags (API 26-29). The tint
is re-asserted after fullscreen toggles, since the legacy path
rewrites the whole `systemUiVisibility` bitmask.
* Android: fix soft-keyboard handling and edge-to-edge insets
Several related window-inset and IME fixes, mostly affecting devices
that are not edge-to-edge (Android versions before 15).
- Report safe-area and IME insets as the overlap with the render
surface, not the raw window-edge insets. On a non-edge-to-edge
window the surface already sits inside the system bars, so the raw
insets double-counted — leaving oversized gaps around content and
above the keyboard.
- Also drive safe-area insets from `onGlobalLayout`, so the app is
inset correctly from launch instead of drawing under the status bar
until the first keyboard show or rotation.
- While the keyboard animates, treat the `WindowInsetsAnimation`
callback as the authoritative per-frame inset source and have the
layout-driven callbacks defer to it. Read target IME visibility from
`getRootWindowInsets()` so a show animation is not misread as an
instant dismissal.
- Only reconfigure the Java IME when the `TextInputConfig` actually
changes, instead of on every show.
- `KeyboardView`: compute and apply the content shift at keyboard-show
event time, removing a one-frame lag and a tail-end jump; only
reconcile post-draw when the focused field actually redrew.
- `Modal::close()`: skip the focus revert when the modal is already
closed — it was stealing focus from a just-tapped text input and
causing a first-tap keyboard flicker.
- Hide the keyboard via `WindowInsetsController.hide(ime())` on API 30+.
* platform: don't panic posting actions during shutdown
post_action no longer unwraps the global action sender. It now
silently drops the action if the sender mutex is poisoned, no Cx
sender is installed, or the receiver has been dropped during app
teardown, and only raises the UI signal when the send succeeds.
(Also shortens an over-long field doc comment in cx.rs; no behavior
change.)
* cargo-makepad: link std statically in AAB builds (16 KB page-size fix)
`-C prefer-dynamic` ships std as a separate, 4 KB-aligned libstd.so that
fails Play's 16 KB page-size rule. AAB builds now link std statically;
APK/dev builds keep prefer-dynamic. Also documents {min_sdk_version} in help.
* Disable SLUG text band acceleration
* Load Android optional APIs dynamically
* Fixed the android-only Gauss-pane vertical flip by correcting render-target Y sampling in:
- widgets/src/window.rs:110
- widgets/src/gauss_view.rs:151
Root cause: Gauss captures the scene into render-target textures, then samples them back
into the UI. Those render-target textures need a Y flip when displayed, matching the
existing Image widget behavior.
* Support overriding the dpi factor at runtime, on all platforms
Add `Cx::set_window_dpi_override` for runtime UI zoom, but dispatch it
in a deferred manner so it's safe to call in an event handler.
For each platform, we connect the dpi override to the click/tap
coordinates to remap it properly, which was done on some platforms
but not most.
* Fix and restyle todo example
* cad
* Fix todo input styling and studio build env
* fix slides
* Don't apply UI scaling (dpi override) to safe inset areas / IME areas
Scaling those areas doesn't make sense, as it can lead to empty space
(extra unnecessary padding) on the border of the IME or the device inset area,
which looks bad and is basically objectively wrong
-------------
Centralize native/physical/layout DPI conversion on `CxWindow`, and use it at platform boundaries for window geometry, safe-area insets, input coordinates, soft keyboard spacing, clipboard and selection overlays, and camera preview rects.
Add runtime `set_window_dpi_override` support that rescales all window-local metrics and emits `WindowGeomChange`.
* Improve mobile IME and soft keyboard handling
- Add broader soft keyboard configuration for input modes, autocorrect, autocapitalization, return key types, multiline, and secure text entry.
- Improve iOS text input state handling, composition ranges, selection sync, and native/layout coordinate conversion.
- Improve Android InputConnection handling for composing text, selection updates, batch edits, editor actions, surrounding text, and programmatic text sync.
- Wire TextInput through the expanded IME configuration surface.
- Cover newer iOS and Android IME APIs while preserving fallbacks for older keyboard behavior.
* Fix iOS IME area DPI override scaling
* Remove Android-specific IME hack
* Further fix Android IME to avoid stale composition ranges being underlined
espeically after you tap elsewhere in the TextInput widget
---------
Co-authored-by: admin <info@makepad.nl>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Don't apply UI scaling (dpi override) to safe inset areas / IME areas
Scaling those areas doesn't make sense, as it can lead to empty space
(extra unnecessary padding) on the border of the IME or the device inset area,
which looks bad and is basically objectively wrong
-------------
Centralize native/physical/layout DPI conversion on `CxWindow`, and use it at platform boundaries for window geometry, safe-area insets, input coordinates, soft keyboard spacing, clipboard and selection overlays, and camera preview rects.
Add runtime `set_window_dpi_override` support that rescales all window-local metrics and emits `WindowGeomChange`.
* Fix pre-existing iOS and Android build breaks
iOS (platform/src/os/apple/ios/ios.rs): five `CxOsOp` arms had edits
that landed one match-arm late, so each block referenced bindings only
in scope on the preceding arm. Re-home them:
- `WindowGeomChange` now applies `native_window_geom_to_layout` (the
two stray lines previously sat inside the `Paint`/`prepared` arm).
- `ShowTextIME` now converts `pos` via `layout_vec2d_to_native_points`
(previously lodged inside the `SyncImeState` destructure pattern).
- `ShowClipboardActions` now converts `rect` / `keyboard_shift` to
native points (previously appended to `ShowSelectionHandles`).
- `ShowSelectionHandles` / `UpdateSelectionHandles` now convert `start`
and `end` (the `Update` arm had no conversion, and the `Show` arm's
conversion was actually the clipboard one).
- `FullscreenWindow` / `NormalizeWindow` drop the bogus `start` / `end`
conversions that didn't belong there.
Android (platform/src/ime.rs): `android_jni::to_java_configure_keyboard`
matches on `InputMode::None` and `ReturnKeyType::{Next, None, Previous,
Google, Yahoo, Join, Route, Continue, EmergencyCall}` — variants that
exist on the `ime_improvements` branch (commit 3dc039f0a) but weren't
pulled into this branch. Add them to the enum definitions so the
android target compiles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Support overriding the dpi factor at runtime, on all platforms
Add `Cx::set_window_dpi_override` for runtime UI zoom, but dispatch it
in a deferred manner so it's safe to call in an event handler.
For each platform, we connect the dpi override to the click/tap
coordinates to remap it properly, which was done on some platforms
but not most.
* Don't always set the app's icon (e.g., for packaged app bundles)
Add `Cx::set_window_dpi_override` for runtime UI zoom, but dispatch it
in a deferred manner so it's safe to call in an event handler.
For each platform, we connect the dpi override to the click/tap
coordinates to remap it properly, which was done on some platforms
but not most.
This normalizes the IME geometry calcs across iOS and Android.
The main difference is to make KeyboardView shift the content based on
the focused TExtInput instance instead of trying to shrink the viewport.
Also make sure that StackNavigation widget properly handles the
keyboard shift, even when it is drawing in full-screen mode.
* App menu bar: restore Quit option, use proper app name
The menu bar shows "MakepadStdInLoop" by default, which is really strange
especially for published apps. This fixes that and also allows the app
to set the name, as well as using a sensible default for it if the app
didn't specify it.
We also now restore the previous Makepad 1.0 behavior of having a default
"Quit" entry in the main app's first menu bar entry.
* ensure Exit flow actually makes it out, on macOS
* WIP: adding full app lifecycle event support, and Ctrl+C/signal catch
* iOS: don't always prompt the user for camera/mic access on app startup
This fixes two unrelated but similar-structure issues, both on iOS:
- `Cx::handle_repaint` ran a 32-slot encoder-capture sweep every frame,
which lazy-created `AvCaptureAccess` and fired
`requestAccessForMediaType:AVMediaTypeVideo` — i.e. the camera prompt —
on every Makepad app, even ones that never touch the camera. Gate the
loop on `av_capture.is_some()` and stop the three `video_encoder_*`
methods in `apple_media.rs` from lazy-creating it; they now return
`EncoderNotStarted` (or no-op for `push_frame`) when no encoder exists.
- `AudioUnitAccess::new()` unconditionally activated the shared
`AVAudioSession` with category `PlayAndRecord` + `VoiceChat` mode,
which is wrong for playback-only apps (mic prompt + forced VPIO).
Defer session config to a new `ensure_ios_session(for_input)` helper,
called from `use_audio_inputs` (PlayAndRecord) and `use_audio_outputs`
(Playback). Idempotent, never downgrades.
* Added fuller lifecycle event support, plus ability to catch sigquit
Tested working well on every platform except web/wasm, as I don't have
a working setup able to test that.
* WIP: adding full app lifecycle event support, and Ctrl+C/signal catch
* Added fuller lifecycle event support, plus ability to catch sigquit
Tested working well on every platform except web/wasm, as I don't have
a working setup able to test that.
* Fix portallist alignment handling so centering actually works
Now, alignment on PortalLists now only applies to the "cross-axis"
of each item, not the main axis.
So when you center a portallist itself, it won't add weird space
on the leading side of the list
* Fix text not drawing on top of a background, e.g., `<code>` tags
This worked in *most* but not all cases, e.g., if you had a ton of
inline code tags, some of them would rarely but deterministically
not show the actual text glyphs, but just an empty background.
* Avoid large margin on the left of `<code>` if it's on a new line
* Add a separate "touch" margin; use it on dock splitter and tab close
Without this, those dock UI elements are nearly impossible to grab
and press on a real touch screen device, even on my iPad.
Also, tweak cargo-makepad iOS and Android builds to use a polished
display name for the app (uppercase first character) by default.
* Don't duplicate fonts in Android/iOS app bundles
* Fix portallist alignment handling so centering actually works
Now, alignment on PortalLists now only applies to the "cross-axis"
of each item, not the main axis.
So when you center a portallist itself, it won't add weird space
on the leading side of the list
* Fix text not drawing on top of a background, e.g., `<code>` tags
This worked in *most* but not all cases, e.g., if you had a ton of
inline code tags, some of them would rarely but deterministically
not show the actual text glyphs, but just an empty background.
* Avoid large margin on the left of `<code>` if it's on a new line
* Add a separate "touch" margin; use it on dock splitter and tab close
Without this, those dock UI elements are nearly impossible to grab
and press on a real touch screen device, even on my iPad.
Also, tweak cargo-makepad iOS and Android builds to use a polished
display name for the app (uppercase first character) by default.
* Fix portallist alignment handling so centering actually works
Now, alignment on PortalLists now only applies to the "cross-axis"
of each item, not the main axis.
So when you center a portallist itself, it won't add weird space
on the leading side of the list
* Fix text not drawing on top of a background, e.g., `<code>` tags
This worked in *most* but not all cases, e.g., if you had a ton of
inline code tags, some of them would rarely but deterministically
not show the actual text glyphs, but just an empty background.
* Avoid large margin on the left of `<code>` if it's on a new line
* Simplify tooltip logic, fix positioning to respect safe inset areas
And other misc positioning/formatting fixes, like wrapping
after a hard line break, as well as ensuring that the callout
arrow thing itself is centered (to the fullest extent possible)
w.r.t. the rest of the tooltip body.
Basically, now it looks good again.
* Fix CheckBox/Toggle `set_active` to animate like others
All other widgets allow you to pass an `animate` arg when setting
them as active, except CheckBox (and by proxy, its wrapper Toggle).
This is necessary for proper non-animated thigns like restoring the
state of a toggle from persistent storage, or other similar examples
where you don't really want the animation to occur (because that'll
look like the user did it accidentally or some kind of phantom movement).
* PortalList: pass "touch stop" (FingerUp) events to children, always
I had recently implemented a feature where PortalList would not pass
events down to its children if those events were being direclty handled
by the PortalList as part of its scroll-action. That was generally correct,
but it missed one rare case where a child widget was waiting on a
finger up (touch stop/release) for something like stopping a hover/down
animation.
So the child, like a button, could capture the initial FingerDown
but never the FingerUp, so they'd get stuck on the hover or down
animation. This fixes that issue by passing FingerUp-causing events
down to the child widgets.
Note that the children must use `was_tap()` on the FingerUp in order
to handle a regular click -- but they should have already been doing that.
So this doesn't break anything, it's just strictly a proper fix
for something that i should've covered previously.
* Separate apply-reload and script-reapply into different concepts
The goal here is to differentiate between "applies" that change the actual
Splash script "DSL" (i.e., the template) from a "re-apply" that doesn't
change the DSL template but does change runtime heap objects.
Mostly, we want to ensure that LiveEdit (the former) is different from
things that require heap updates (the latter), such as changing a theme
value or doing screen rotation that changes safe inset areas on mobile.
I don't know that I love this approach as a permanent solution,
but it's a good stepping stone until we can redesign LiveEdit/Apply
to funnel all of these various events through the same singular system.
We probably want to use different attributes on widget fields in order
to have more fine-grained control over what happens on an Apply action.
Generated list of brief details here:
* `Apply::ScriptReapply` variant + `is_script_reapply` /
`is_live_edit_reload` predicates; `Event::ScriptReapply` now applies
via `Apply::ScriptReapply`.
* `String` / `ArcStringMut` `script_apply` early-return on
`ScriptReapply`.
* Codegen: `#[deref]` runs before `#[apply_default]`'s recursive call
so animator state wins over template defaults.
* `Animator::script_apply_default` returns `state_object` on
`ScriptReapply`; new `current_state_apply()` helper for
`on_after_apply` hooks.
* `Cx::request_live_edit()` + `pending_live_edit_request` for primitive
heap mutations (safe-area insets baked into `script_mod!`
expressions).
* `handle_live_edit()` returns `LiveEditTrigger {None, FileChange,
Manual}`; `run_live_edit_if_needed` skips shader-cache reset and
same-tick `ScriptReapply` follow-up for `Manual` (fixes ~1s rotation
lag).
* iOS/Android post-event hook now drains both flags via
`run_live_edit_if_needed` (was firing `LiveEdit` indiscriminately).
* `Window` `WindowGeomChange` uses `request_live_edit()` for
safe-area.
* `StackNavigationView` gains `runtime_title` field re-asserted in
`on_after_apply`; new `StackNavigation::set_title` API.
* `app_main!` collapses 4 duplicate platform branches into a shared
`_app_main_event_closure!` macro.
* Stack nav now handles script reapply for previously-pushed views
`StackNavigation`'s `_after_apply` only restored the currentt view,
but skipped all the pushed view, making their visibility false.
Now, `on_after_apply` sets all pushed views as visible, and also
that the offset (for the slide animation) gets properly re-set
* Simplify tooltip logic, fix positioning to respect safe inset areas
And other misc positioning/formatting fixes, like wrapping
after a hard line break, as well as ensuring that the callout
arrow thing itself is centered (to the fullest extent possible)
w.r.t. the rest of the tooltip body.
Basically, now it looks good again.
* Fix CheckBox/Toggle `set_active` to animate like others
All other widgets allow you to pass an `animate` arg when setting
them as active, except CheckBox (and by proxy, its wrapper Toggle).
This is necessary for proper non-animated thigns like restoring the
state of a toggle from persistent storage, or other similar examples
where you don't really want the animation to occur (because that'll
look like the user did it accidentally or some kind of phantom movement).
* PortalList: pass "touch stop" (FingerUp) events to children, always
I had recently implemented a feature where PortalList would not pass
events down to its children if those events were being direclty handled
by the PortalList as part of its scroll-action. That was generally correct,
but it missed one rare case where a child widget was waiting on a
finger up (touch stop/release) for something like stopping a hover/down
animation.
So the child, like a button, could capture the initial FingerDown
but never the FingerUp, so they'd get stuck on the hover or down
animation. This fixes that issue by passing FingerUp-causing events
down to the child widgets.
Note that the children must use `was_tap()` on the FingerUp in order
to handle a regular click -- but they should have already been doing that.
So this doesn't break anything, it's just strictly a proper fix
for something that i should've covered previously.
* Separate apply-reload and script-reapply into different concepts
The goal here is to differentiate between "applies" that change the actual
Splash script "DSL" (i.e., the template) from a "re-apply" that doesn't
change the DSL template but does change runtime heap objects.
Mostly, we want to ensure that LiveEdit (the former) is different from
things that require heap updates (the latter), such as changing a theme
value or doing screen rotation that changes safe inset areas on mobile.
I don't know that I love this approach as a permanent solution,
but it's a good stepping stone until we can redesign LiveEdit/Apply
to funnel all of these various events through the same singular system.
We probably want to use different attributes on widget fields in order
to have more fine-grained control over what happens on an Apply action.
Generated list of brief details here:
* `Apply::ScriptReapply` variant + `is_script_reapply` /
`is_live_edit_reload` predicates; `Event::ScriptReapply` now applies
via `Apply::ScriptReapply`.
* `String` / `ArcStringMut` `script_apply` early-return on
`ScriptReapply`.
* Codegen: `#[deref]` runs before `#[apply_default]`'s recursive call
so animator state wins over template defaults.
* `Animator::script_apply_default` returns `state_object` on
`ScriptReapply`; new `current_state_apply()` helper for
`on_after_apply` hooks.
* `Cx::request_live_edit()` + `pending_live_edit_request` for primitive
heap mutations (safe-area insets baked into `script_mod!`
expressions).
* `handle_live_edit()` returns `LiveEditTrigger {None, FileChange,
Manual}`; `run_live_edit_if_needed` skips shader-cache reset and
same-tick `ScriptReapply` follow-up for `Manual` (fixes ~1s rotation
lag).
* iOS/Android post-event hook now drains both flags via
`run_live_edit_if_needed` (was firing `LiveEdit` indiscriminately).
* `Window` `WindowGeomChange` uses `request_live_edit()` for
safe-area.
* `StackNavigationView` gains `runtime_title` field re-asserted in
`on_after_apply`; new `StackNavigation::set_title` API.
* `app_main!` collapses 4 duplicate platform branches into a shared
`_app_main_event_closure!` macro.
* Simplify tooltip logic, fix positioning to respect safe inset areas
And other misc positioning/formatting fixes, like wrapping
after a hard line break, as well as ensuring that the callout
arrow thing itself is centered (to the fullest extent possible)
w.r.t. the rest of the tooltip body.
Basically, now it looks good again.
* Fix CheckBox/Toggle `set_active` to animate like others
All other widgets allow you to pass an `animate` arg when setting
them as active, except CheckBox (and by proxy, its wrapper Toggle).
This is necessary for proper non-animated thigns like restoring the
state of a toggle from persistent storage, or other similar examples
where you don't really want the animation to occur (because that'll
look like the user did it accidentally or some kind of phantom movement).
* PortalList: pass "touch stop" (FingerUp) events to children, always
I had recently implemented a feature where PortalList would not pass
events down to its children if those events were being direclty handled
by the PortalList as part of its scroll-action. That was generally correct,
but it missed one rare case where a child widget was waiting on a
finger up (touch stop/release) for something like stopping a hover/down
animation.
So the child, like a button, could capture the initial FingerDown
but never the FingerUp, so they'd get stuck on the hover or down
animation. This fixes that issue by passing FingerUp-causing events
down to the child widgets.
Note that the children must use `was_tap()` on the FingerUp in order
to handle a regular click -- but they should have already been doing that.
So this doesn't break anything, it's just strictly a proper fix
for something that i should've covered previously.
* Simplify tooltip logic, fix positioning to respect safe inset areas
And other misc positioning/formatting fixes, like wrapping
after a hard line break, as well as ensuring that the callout
arrow thing itself is centered (to the fullest extent possible)
w.r.t. the rest of the tooltip body.
Basically, now it looks good again.
* Fix CheckBox/Toggle `set_active` to animate like others
All other widgets allow you to pass an `animate` arg when setting
them as active, except CheckBox (and by proxy, its wrapper Toggle).
This is necessary for proper non-animated thigns like restoring the
state of a toggle from persistent storage, or other similar examples
where you don't really want the animation to occur (because that'll
look like the user did it accidentally or some kind of phantom movement).
And other misc positioning/formatting fixes, like wrapping
after a hard line break, as well as ensuring that the callout
arrow thing itself is centered (to the fullest extent possible)
w.r.t. the rest of the tooltip body.
Basically, now it looks good again.
* Support runtime-reassigned module templates and app-wide events
- Dock/PortalList: add `refresh_widgets_mod_template()` so callers can
re-capture a content template from `mod.widgets.*` after reassigning
it at runtime via `script_eval!`. Dock's variant takes a separate
template_key and mod_widgets_name since local DSL names (e.g.
`room_screen`) don't always match the module entry (`RoomScreen`).
- PortalList: add `all_items_and_pool()` iterator so callers can walk
every live and pooled item (e.g. to push a new property across the
whole list on a preference change).
- StackNavigation: forward non-visibility events (`Event::Actions`) to
all child stack views, not just visible ones. Inactive views need
global state updates too; `View::handle_event` still gates on each
child's own `visible` flag for events that require visibility.
- TextInput: add `submit_on_enter` so callers can opt into Cmd/Ctrl+
Enter submit semantics, plus a `key_focus_lost` helper for commit-
on-blur inputs.
- Image: honor `Size::Fit { max }` when `peek_walk_turtle` returns NaN
so `Fit{max: Abs(..)}` caps image height without clipping.
- FlatList: derive `Default` on the shared `WidgetItem` struct.
- draw: re-export `Base` and `FitBound` from turtle.
* Add Event::ScriptReapply + preserve Dock state on reload
- `Event::ScriptReapply`: new event signalling a widget-tree Apply::Reload
that does NOT re-run `script_mod!`. Fires from `run_live_edit_if_needed`
when `Cx::pending_script_reapply` is set (previously the flag was never
observed on desktop). The AppMain macro caches the app's script root as
a rooted `ScriptObjectRef` and re-applies the tree with it — so runtime
heap mutations (e.g. `script_eval!` overriding a user preference) stay
intact. If a file-driven `LiveEdit` handler then sets the flag again, a
bounded follow-up `ScriptReapply` pass runs in the same tick.
- Dock: on `Apply::Reload`, preserve existing runtime `dock_items` (open
tabs, selected indices, splitter positions). Only insert DSL-defined
items for IDs that don't already exist — so a source hot-reload no
longer wipes the user's opened tabs.
* cleanup, remove unnecessary crap from prior approaches
the whole `refresh_widgets_mod_template` was a misguided approach,
and now that we have script reload/re-apply working and we have fixed
LiveEdit for most widgets, we just don't need it
* cleanup, remove more unused functions
* draw_text: adopt outer many_instances batch on linux/windows
CodeEditor opens an outer raster batch via DrawText::begin_many_instances
before its glyph loop. The linux/windows branch of DrawText::draw_text
ignored self.many_instances and opened its own nested batch, which
resolved (via find_appendable_drawcall) to the same draw_item whose
`instances` Vec was already swapped out by the outer open, panicking on
unwrap in Cx2d::begin_many_instances.
Mirror what the other platform branch (and draw_rasterized_glyphs_abs)
already do: take self.many_instances on entry, track whether the active
raster batch is the outer one, and hand it back on exit so the caller's
end_many_instances finalizes it. Skip the !drew_raster_this_frame area
clear when the outer batch is still live.
* Html/Markdown fixes: sub/superscript, table outlines/alignment, etc
- **Sub/sup in HTML**: added a `y_shift_scales` stack on `TextFlow`, composed onto `temp_y_shift` in `draw_text`. `<sub>` pushes `+0.55`, `<sup>` pushes `-0.2` in html.rs
- **Sub/sup in Markdown**: now handles `MdEvent::InlineHtml` for `<sub>`/`<sup>` (case-insensitive), using the same stacks.
- **Space after `&` (and other entities)**: the HTML lib's entity decoder now resets `last_non_whitespace` after truncate+push, so the whitespace-collapse check no longer drops the next real space.
- **Table column alignment (Markdown)**: `begin_table_cell` takes `align_x: f64`; tracks `Tag::Table` alignments and a per-row column index, passing each cell's `Alignment` through.
- **Table column alignment (HTML)**: `<td>`/`<th>` now honor `align="…"` and inline `style="text-align: …"` via new `cell_align_x` / `align_keyword_to_x` helpers in html.rs.
- **Per-row text alignment plumbing**: new `layout_align` field on `DrawText` is passed to the layouter, which already supports per-row alignment. `TextFlow` propagates a `cell_text_align_x` into it. This is the actual fix that makes cell alignment visible.
- **Wrap-flow alignment scaffolding**: implemented the previously-stubbed `Flow::Right { wrap: true }` branch in the turtle logic. Useful for non-text wrapping walks; text goes through the layouter path above.
- Add examples to uizoo: three new tables in both markdown and html tab -- a plain one, a left/center/right aligned one, and a numeric all-right-aligned one. They cover bold/italic/code/links/sub-sup/emoji/entities/strikethrough inside cells.
* minor cleanup; prefer `style` over `align` HTML tag
* Implement the `<details>`/`summary` widget within Html
* cleanup/improvement
* spacing and size consistency for details/summary header
* Add RowAlign::Center, per-row FinishedWalk support, and inline widget alignment
Closes#712
turtle.rs:
- Add RowAlign::Center variant for vertically centering walks within a row
- Add finish_row_center() that shifts shorter walks to the row's vertical midline
- Fix finish_row's current_row_walks_start() to use last finished row (not first)
- Add Cx2d::align_list_len(), shift_align_entries(), emit_turtle_walk_with_metrics()
draw_text.rs (draw_walk_resumable_with):
- Per-row path: multi-row wrapped text now emits one FinishedWalk per visual row
with separate glyph instance batches, enabling RowAlign::Center per row
- Between rows: call turtle_new_line_with_spacing to trigger finish_row at each
visual-row boundary
- Wrapped rows draw glyphs at turtle position (not layouter position) so glyph
positions stay in sync with turtle tracking when pills inflate row height
- Remove shift_extra_height from allocation (caused turtle/glyph position divergence)
text_flow.rs:
- Fix wrap check to use matches!(Flow::Right { wrap: true, .. }) instead of
equality against Flow::right_wrap() (broke wrapping with non-Top RowAlign)
- Account for inline_code padding in turtle allocation (fixes overlap bug)
* fix build for Linux / Windows
* draw_text: adopt outer many_instances batch on linux/windows
CodeEditor opens an outer raster batch via DrawText::begin_many_instances
before its glyph loop. The linux/windows branch of DrawText::draw_text
ignored self.many_instances and opened its own nested batch, which
resolved (via find_appendable_drawcall) to the same draw_item whose
`instances` Vec was already swapped out by the outer open, panicking on
unwrap in Cx2d::begin_many_instances.
Mirror what the other platform branch (and draw_rasterized_glyphs_abs)
already do: take self.many_instances on entry, track whether the active
raster batch is the outer one, and hand it back on exit so the caller's
end_many_instances finalizes it. Skip the !drew_raster_this_frame area
clear when the outer batch is still live.
* Html/Markdown fixes: sub/superscript, table outlines/alignment, etc
- **Sub/sup in HTML**: added a `y_shift_scales` stack on `TextFlow`, composed onto `temp_y_shift` in `draw_text`. `<sub>` pushes `+0.55`, `<sup>` pushes `-0.2` in html.rs
- **Sub/sup in Markdown**: now handles `MdEvent::InlineHtml` for `<sub>`/`<sup>` (case-insensitive), using the same stacks.
- **Space after `&` (and other entities)**: the HTML lib's entity decoder now resets `last_non_whitespace` after truncate+push, so the whitespace-collapse check no longer drops the next real space.
- **Table column alignment (Markdown)**: `begin_table_cell` takes `align_x: f64`; tracks `Tag::Table` alignments and a per-row column index, passing each cell's `Alignment` through.
- **Table column alignment (HTML)**: `<td>`/`<th>` now honor `align="…"` and inline `style="text-align: …"` via new `cell_align_x` / `align_keyword_to_x` helpers in html.rs.
- **Per-row text alignment plumbing**: new `layout_align` field on `DrawText` is passed to the layouter, which already supports per-row alignment. `TextFlow` propagates a `cell_text_align_x` into it. This is the actual fix that makes cell alignment visible.
- **Wrap-flow alignment scaffolding**: implemented the previously-stubbed `Flow::Right { wrap: true }` branch in the turtle logic. Useful for non-text wrapping walks; text goes through the layouter path above.
- Add examples to uizoo: three new tables in both markdown and html tab -- a plain one, a left/center/right aligned one, and a numeric all-right-aligned one. They cover bold/italic/code/links/sub-sup/emoji/entities/strikethrough inside cells.
* minor cleanup; prefer `style` over `align` HTML tag
CodeEditor opens an outer raster batch via DrawText::begin_many_instances
before its glyph loop. The linux/windows branch of DrawText::draw_text
ignored self.many_instances and opened its own nested batch, which
resolved (via find_appendable_drawcall) to the same draw_item whose
`instances` Vec was already swapped out by the outer open, panicking on
unwrap in Cx2d::begin_many_instances.
Mirror what the other platform branch (and draw_rasterized_glyphs_abs)
already do: take self.many_instances on entry, track whether the active
raster batch is the outer one, and hand it back on exit so the caller's
end_many_instances finalizes it. Skip the !drew_raster_this_frame area
clear when the outer batch is still live.
* Switch to SLUG/DrawGlyph font drawing stack (via Codex)
* Improve same-frame new glyph caching and SLUG packed instances
* avoid performance regression by batch updating slug atlas cache
* TextFlow: separate SLUG glyph batches to avoid interleaving HTML text drawing
* SLUG optimization: append instead of a full clone each generation
* trying out codex perf fix for linux wayland
* Disable SLUG glyps on Linux for now
* Fix Linux SLUG rendering, warmup, and promotion behavior
- re-enable Linux SLUG through a separate Linux-only DrawText helper
instead of bloating the normal DrawText shader path
- preserve widget text styling on Linux SLUG by syncing common DrawText
state into the helper, including base colors, gradients, and interactive
states such as hover, focus, down, active, pressed, drag, empty, and
disabled
- keep normal Linux UI text on the raster/MSDF path and only switch to
SLUG above the Linux cutoff, while still falling back cleanly when SLUG
data is unavailable
- fix Linux SLUG glyph placement so promoted text uses the correct glyph
origin, layout position, and atlas packing
- add progressive SLUG warmup on Linux by budgeting glyph generation and
uploads across redraws and falling back to raster/MSDF until SLUG data
is actually ready
- lazily register and prewarm the shared Linux SLUG helper so app startup
and first-use latency stay low
- opt only the Linux SLUG helper into async GL shader compilation and use
parallel shader compile support when available, avoiding the large
startup and first-tab stalls seen before
- fix Linux runtime shader issues caused by copied widget text shaders and
custom get_color logic by using a shared helper shader with the expected
text-state inputs
- stabilize Linux SLUG promotion by preventing stale retained areas,
cleaning up raster/helper ownership correctly during draw, and
shadow-promoting the first ready SLUG frame before making it visible
- eliminate the visible SLUG handoff flicker so Linux text now switches
from raster/MSDF to SLUG without freezes or noticeable visual artifacts
- add a dedicated UIZoo SLUG tab with side-by-side below-cutoff and
above-cutoff examples, plus diagnostic cases for plain labels,
gradients, custom text shaders, and glyph/color probes
- keep the final diff focused by removing unrelated formatting-only churn
from the worktree during cleanup
* Tighten Linux SLUG promotion and helper sync
- make Linux SLUG promotion state local to each DrawText instance
instead of using a global per-redraw gate
- cache Linux SLUG helper shader field intersections so helper state
syncing avoids repeated per-draw allocations and linear membership checks
- harden the shadow-promotion fallback path so failed helper batching
only takes a single raster fallback path
- preserve DrawText memory alignment after adding Linux SLUG bookkeeping
* try to fix emoji on Android
* emoji fix take 2
* uizoo example: allow touch/drag scroll. Don't panic in FileTree demo
* Fix emoji on Android
* Windows: async HLSL shader compile + extend SLUG helper path to Windows
Fixes two major performance issues on Windows that made uizoo unusable on
first launch:
1. **60-75s startup stall** caused by synchronous `D3DCompile` of ~30+
SLUG-bearing text shader variants on the UI thread before the window
could present.
2. **3-4s hang when opening the SLUG tab** caused by synchronous compile
of the fat DrawTextSlug helper shader the first time a SLUG glyph was
needed.
Also fixes a latent HLSL-only `CreateInputLayout` E_INVALIDARG crash
triggered by shaders with >26 instance inputs (exposed by DrawTextSlug).
`DrawTextLinuxSlug` → `DrawTextSlug` and all `linux_slug_*` /
`LinuxSlug*` symbols dropped their `Linux` prefix. Cfg guards expanded
from `target_os = "linux"` to `any(target_os = "linux", target_os = "windows")`
so Windows now:
- uses the same lean base `DrawText` shader (SDF/MSDF only, no SLUG
curve-solver HLSL inlined)
- uses a separate `DrawTextSlug` helper shader for the SLUG path
- has the same progressive glyph-build budget and DPI cutoff
(`default_slug_new_glyphs_per_redraw`, `default_slug_min_dpxs_per_em`
in `fonts.rs` now match `OsType::Windows` alongside the Linux variants)
- falls back to raster/MSDF while the SLUG helper shader or its glyph
data isn't yet ready
macOS/iOS/Android/WASM paths are untouched — they still use the fat
all-in-one `DrawText` shader via `cfg(not(any(linux, windows)))`.
Added `AsyncHlslCompile` in `d3d11.rs` and an `async_hlsl_compile` field
on `CxOs`. Shaders flagged `async_compile: true` (the SLUG helper) now
dispatch to a background thread per shader via `std:🧵:Builder`
(named `hlsl-compile-<id>` for debugging). Workers call `D3DCompile`
off the UI thread, write the resulting DXBC to the on-disk cache, and
send only a status result (not the bytes themselves — SLUG is ~240 KB
and ferrying it through the channel is wasteful) back via an mpsc
channel guarded by a `Mutex`.
`hlsl_compile_shaders` drains completed results at the top of each
call, constructs `CxOsDrawShader` objects on the main thread (the
bytes come from the cache-hit path in `CxOsDrawShader::new`), and
triggers `redraw_all()` so widgets whose shaders just became ready
get re-rendered. The existing `sh.os_shader_id.is_none()` guard in
`render_view` handles skipping the draw call while a shader is
pending.
Added `Cx::is_draw_shader_window_ready()` on Windows for the SLUG
helper's readiness check; on Windows it's simply
`os_shader_id.is_some()` since HLSL compile is either synchronous
(cache hit) or tracked via the async path.
Cold-start still compiled 30+ shaders synchronously (parallelized via
`std:🧵:scope`) which took ~5-10s because FXC's per-call speed is
the bottleneck and parallelism helps less than expected. Now
`hlsl_compile_shaders` partitions queued shaders by cache state:
- cache hit → sync path: disk read + D3D11 object creation, a few ms
- cache miss OR `async_compile: true` → async path: worker thread
On a fully cold cache, every shader is a cache miss → the window
presents on the first frame with no compile work on the UI thread.
Widgets fill in over the next ~1-2s as their shaders become ready.
On a warm cache every shader is a hit → instant startup as before.
Added `shader_bytes_cached()` for cheap existence checking of the
cache entries.
`d3d_compile_hlsl` now passes `D3DCOMPILE_SKIP_OPTIMIZATION`. FXC's
optimizer is what makes individual compiles burn hundreds of ms to
seconds on text shaders with loops; UI shaders don't benefit enough
from it to justify the cold-cache cost. If a specific shader is later
shown to be a runtime hotspot, the fix is to recompile it optimized
on a background thread and hot-swap, not to pay the cost upfront for
every shader.
Bumped `CACHE_KEY_VERSION` to 2 so pre-existing `.dxbc` blobs compiled
with the old flags are invalidated cleanly on upgrade.
`d3d11.rs` used its own `index_to_char(i) = i + 'A'` which produced
invalid HLSL semantic names (`[`, `\`, `]`, …) past 26 inputs, while
the HLSL generator in `shader_hlsl.rs` already used a correct
multi-character scheme (`A..Z, AA..AZ, BA..`). `CreateInputLayout`
returned E_INVALIDARG because the names didn't match. Replaced with
`makepad_script::shader_hlsl::index_to_semantic` so both sides of
the binding agree.
This was a latent bug — no existing shader had >26 instance inputs
until `DrawTextSlug` (which inherits many interactive-state fields
from Label-derived shaders). Linux/GLSL is unaffected because GLSL
binds by identifier, not semantic name.
- Hoisted `d3d_compile_hlsl`, `hlsl_cache_key`, and
`get_or_compile_shader_bytes` out of `CxOsDrawShader::new` to module
scope so they can be shared with the async worker.
- Compute `hlsl_cache_key` once per shader during partition and reuse
at dispatch instead of recomputing.
- Scoped borrows in the async drain loop eliminate mapping/bindings
clones.
- `platform/src/os/windows/d3d11.rs` — compile pipeline, async infra,
semantic-name fix
- `platform/src/os/windows/windows.rs` — `async_hlsl_compile` field on
`CxOs`
- `draw/src/shader/draw_text.rs` — rename `LinuxSlug*` → `Slug*`,
expand cfg gates
- `draw/src/text/fonts.rs` — extend SLUG cutoff/budget defaults to
Windows
No changes to macOS, iOS, Android, or WASM paths.
`CharExt::column_count` was hard-coded to return 1 for every char. The
code editor's layouter advances x-position by this value per grapheme
(see `code_editor.rs:1217`), so CJK glyphs — which the text shaper draws
at ~2× the Latin monospace advance — overlap one another. Cursor
placement, selection rectangles, and wrap points suffer the same
off-by-half because they all read from `column_count`.
Match the Unicode East Asian Width property so Wide and Fullwidth
characters (plus common emoji that render at double-width) report 2
columns. Keeps a small literal match table instead of pulling in the
\`unicode-width\` crate, since only broad blocks are needed and perf on
the layout hot path matters.
Ranges covered:
U+3000..U+30FF CJK punctuation / Hiragana / Katakana
U+3400..U+4DBF CJK Unified Ideographs Extension A
U+4E00..U+9FFF CJK Unified Ideographs
U+AC00..U+D7AF Hangul Syllables
U+F900..U+FAFF CJK Compatibility Ideographs
U+FF00..U+FF60 Fullwidth forms
U+FFE0..U+FFE6 Fullwidth sign forms
U+20000..U+2FFFF CJK Unified Ideographs Extensions B..F
U+1F300..U+1F9FF Emoticons / symbols / transport / supplemental
Effect: Chinese/Japanese/Korean/emoji in code blocks render with correct
spacing in CodeView (and in any widget that layouts via CodeSession).
Latin-only workflows are unaffected.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent iOS touch-input correctness fixes.
1. Hit-test: stop inflating widget bounds by touch radius
`Cx::hits_with_options_and_test` was inflating every widget's
clickable rect outward by `touch.radius` on all four sides, where
`radius` comes from `UITouch.majorRadius` on iOS (and the Android
equivalent). On real fingers this is 5-15pt, but the iOS Simulator
synthesizes mouse clicks as touches with `majorRadius` ~25-40pt, so
every button in the simulator captured clicks ~30pt outside its
visible bounds — a click well above or below the Upload Avatar
button still activated it.
Remove the inflation in both TouchState::Start and TouchState::Stop
and hit-test against the touch centroid only. This matches UIKit/
AppKit native behavior; Apple's HIG and Material Design already
require touch targets large enough that hidden inflation isn't
needed. Apps that want explicit hit padding can still pass it via
`HitOptions::margin`. `touch.radius` is still populated and
delivered to apps that want it for visual feedback.
Affected platforms: iOS, Android, web touch. Desktop mouse paths
(MouseDown/Up) never touched this code.
2. Window geometry: read from MTKView, not UIScreen
`IosApp::check_window_geom` was reading `inner_size` from
`UIScreen.mainScreen.bounds`, which describes the *physical screen*
rather than the app's drawing surface. On iPad Split View, Slide
Over, Stage Manager, and multi-scene apps the window is a fraction
of the screen, so `inner_size` could disagree with the MTKView's
actual bounds — introducing a constant offset between layout and
the touch coordinate space (UITouch `locationInView:` is always
view-local).
Read `bounds`, `contentScaleFactor`, and `safeAreaInsets` from the
MTKView itself, falling back to UIScreen only during the early-init
window before the view exists.
Also use the `size` parameter delivered to
`mtkView:drawableSizeWillChange:` directly (converting pixels→points
via `contentScaleFactor` from the same view) rather than re-querying.
UIKit gives us the authoritative new size synchronously with the
resize callback; re-reading any other source can race by one layout
pass during rotation or multitasking transitions.
Extract the common WindowGeom construction / first-draw / update_geom
/ callback-dispatch tail into `apply_new_window_geom`.
Shaping a string containing right-to-left characters (Arabic, Hebrew,
etc.) could panic in shape_recursive with "byte range starts at N but
ends at M", and even when it didn't panic, the surrounding LTR text was
laid out in visually wrong positions.
- shaper: compute fallback byte ranges in shape_recursive in a
direction-aware way. HarfBuzz emits clusters monotonically in the
shaping direction (non-decreasing for LTR, non-increasing for RTL),
so the "next logical cluster" lives visually after the current group
for LTR and visually before it for RTL. Also collect glyph groups
into an indexable Vec so we can look both forward and backward, and
defensively fall back to rendering .notdef tofu rather than
recursing on an inverted range if HarfBuzz ever produces unexpected
non-monotonic output.
- shaper: run the Unicode Bidirectional Algorithm via the already-
present unicode-bidi crate before shaping. Segment each input into
visual runs with ParagraphBidiInfo, then shape each run in its
resolved direction and append in visual order. This keeps an RTL
chunk embedded in LTR text from stomping on the surrounding LTR
layout. Full RTL support (joining quality, cursor/hit-testing in
RTL runs) is still not wired up above the shaper.
- shaper: add an is_definitely_ltr fast path that short-circuits BiDi
entirely when the input contains no characters in any RTL Unicode
block. Uses str::is_ascii (SIMD) for the common ASCII case and
falls back to a char-level range check, so ASCII / Latin / Greek /
Cyrillic / CJK / emoji text pays no BiDi classification or vec-
allocation cost on cache miss.
- shaper: cache the rustybuzz Feature conversion in shape_step with
a one-slot content-keyed cache, so repeated shape calls with the
same feature set (typically empty) don't rebuild the Vec each time.
Implement streaming-compatible table support in the shared TextFlow
layout engine. Tables render incrementally as events arrive — no
buffering needed since pulldown_cmark provides column count upfront
via the header separator line.
- Add FlowBlockType::TableCell variant with SDF shader for cell
borders and header backgrounds in DrawFlowBlock base definition
- Add begin/end table, row, and cell methods to TextFlow using
turtle layout with equal-width column distribution
- Draw cell borders after row layout completes so all cells share
the row's max height for uniform borders
- Replace 8 TODO stubs in markdown.rs with direct TextFlow calls
- Add table/thead/tbody/tr/th/td tag handling in html.rs with
column counting via HtmlNode lookahead
- Skip whitespace-only text nodes inside HTML tables using the
pre-computed all_ws flag
* Android: fix nondeterminstic crashes when using a bad surface
Makepad apps on Android were randomly crashing within the `Cx::render_view`
callstack when the host Activity surface was recycled, e.g., on
background/foreground transitions, rotation, IME show/hide, etc.
Details of the change are generated below:
----
`SurfaceHolder.Callback.surfaceDestroyed` posted a fire-and-forget message
to the render thread and returned to Android immediately, leaving two
overlapping races:
1. The render thread could drain `SurfaceDestroyed` from the mpsc channel,
call `destroy_surface()` (which does `eglMakeCurrent(NULL, NULL, NULL,
NULL)` and nulls the EGL surface), then in the *same* `RenderLoop`
iteration call `handle_drawing()` → `render_view()` and issue GL calls
with no current EGL context.
2. Even when our Rust side was well-behaved, Android was free to recycle
the underlying buffer queue the moment `surfaceDestroyed` returned to
Java, while the render thread was still mid-frame against it.
**Layer 1 — Surface validity tracking.** Added `CxOs::surface_alive`,
flipped synchronously in `SurfaceCreated`/`SurfaceChanged`/`SurfaceDestroyed`
handlers, plus a `CxOs::has_drawable_surface()` helper that gates every
GL/Vulkan dispatch entry point: `main_loop`'s `handle_drawing()`,
`draw_pass_to_window_for_active_backend`, `draw_pass_to_texture_for_active_backend`,
`draw_pass_to_fullscreen`, and `eglSwapBuffers` in `present_window_for_active_backend`.
**Layer 2 — Synchronous Java↔Rust handshake.** `FromJavaMessage::SurfaceDestroyed`
now carries an `Arc<(Mutex<bool>, Condvar)>` ack channel. The JNI binding
blocks the Android UI thread on the condvar (2-second budget, well under
the 5-second ANR threshold) until the render thread confirms it has
released the surface. This is the same pattern `android.opengl.GLSurfaceView`
uses, and it closes the underlying-buffer-recycled-mid-frame race.
**Layer 3 — Defense-in-depth re-bind.** `draw_pass_to_fullscreen` now calls
a new fallible `try_make_current()` every frame, recovering from any GL
context drift caused by foreign code or our own teardown path, and
bailing cleanly if the bind fails instead of crashing inside the driver.
Verified all 5 gated entry points against the `openxr_render_loop` →
`openxr_handle_repaint` path:
- 4 entry points are unreachable in XR mode (XR uses its own swapchains
and `xrEndFrame`, never `eglSwapBuffers` or the popup overlay path).
- `draw_pass_to_texture_for_active_backend` IS reachable (off-screen UI
textures composited into the XR scene). To prevent these from being
wrongly skipped in Vulkan+XR mode after the host surface is recycled,
added an explicit XR escape hatch to `has_drawable_surface()`: when
`in_xr_mode && openxr.session.is_some()`, return `true` based purely on
`vulkan.is_some()`, ignoring the (intentionally nulled) `display.window`.
This matches the existing `keep_xr_backend_alive` logic in the
`SurfaceDestroyed` handler.
- `destroy_surface` is now idempotent (safe to call when already null).
- `make_current` asserts on null surface with a descriptive panic message
instead of crashing inside EGL.
- Lifecycle handlers verify surface creation actually succeeded before
flipping `surface_alive` to `true` (no false-positive ready signal).
Tested working on my OnePlus Open w/ Android 15.
-------------
* Other fixes: minor change to logging format to include level indicator
* Fix warning in cargo makepad android
* Fix Android platform errors, mostly no draw on start/resume
Also a small related optimization on iOS
------------
* Android: JNI method ID caching (`ndk_utils.rs`)
* Rewrote the `call_method!` macro to cache `jmethodID` in a per-call-site `static AtomicPtr`. Previously, every JNI call allocated two `CString`s and called `GetObjectClass` + `GetMethodID` from scratch. Now these are resolved once and reused for all subsequent calls. Also deletes the local class reference after first resolution.
* Android: `to_java_update_tex_image` uses cached macro (`android_jni.rs`)
* Replaced manual `GetObjectClass`/`CString::new`/`GetMethodID` calls with the now-cached `call_bool_method!` macro, eliminating per-frame JNI overhead for video texture updates.
* Android: Touch event coalescing (`android.rs`)
* When draining pending messages before a RenderLoop frame, consecutive pure-Move touch events are now coalesced — only the last position is dispatched. Start/Stop events are never dropped. This reduces redundant event dispatch during active touch scrolling.
* Android: Black screen fix (`android.rs`)
* Added `needs_first_draw` flag to `CxOs`. When a `RenderLoop` callback arrives but the surface isn't drawable, the flag is set. When the surface later becomes available, `redraw_all()` is called to ensure the first frame is painted. The flag is also set on `SurfaceDestroyed` so resuming from background always gets a guaranteed first frame.
* iOS: Remove unnecessary `passes_todo.clone()` (`ios.rs`)
* Removed a redundant `Vec::clone()` in the popup pass draw loop — both the outer and inner loops borrow `passes_todo` immutably.
* Optimize text layout and PortalList widget performance
Text layout: eliminate redundant HarfBuzz reshaping (layouter.rs)
- Replace `can_fit()` reshaping of cumulative multi-word substrings (always cache misses) with summing pre-computed per-word widths
- Replace `fit()` reshaping with concatenation of cached per-word `ShapedText` results, adjusting cluster offsets
- Cache each word's `Rc<ShapedText>` in the Fitter constructor for reuse
PortalList: O(1) reusable item pool lookup (portal_list.rs)
- Change `reusable_items` from `Vec<WidgetItem>` (O(n) scan + O(n) remove) to `HashMap<LiveId, Vec<WidgetItem>>` (O(1) lookup + O(1) pop)
PortalList: skip touch cursor hit-testing (portal_list.rs)
- Gate `point_hits_interactive_item()` behind `!e.device.is_touch()` to skip expensive recursive widget-tree walk on touch devices where there is no cursor
* undo unnecessary change to inter-word/ligature width estimation
avoid problems with words'/ligatures' kerning
The font_member_is_needed_for_text() optimization pre-scanned text content
to decide whether to skip loading CJK/emoji font members. This was broken
in two ways:
1. The hardcoded Unicode ranges in is_emoji_char() were incomplete (e.g.,
missing U+2B50 ⭐), so emoji glyphs silently failed to render.
2. When members were skipped, font_ids.len() never matched
expected_member_count, so is_font_family_complete() never returned true
for i18n font families. This caused every draw call to hit the slow
path — the opposite of the optimization's intent.
Fix: load all font family members unconditionally. The one-time load cost
is negligible, and the fast path (is_font_family_complete → early return)
now works correctly on subsequent draws.
Removed the now-unused helpers: font_member_is_needed_for_text(),
is_cjk_fallback_font_path(), is_emoji_fallback_font_path(),
resource_basename(), text_has_cjk(), is_cjk_char(), text_has_emoji(),
is_emoji_char(), and FontFamily::ensure_fonts_loaded_for_text().
* Fix emojis in Html by removing broken font loading optimization
The font_member_is_needed_for_text() optimization pre-scanned text content
to decide whether to skip loading CJK/emoji font members. This was broken
in two ways:
1. The hardcoded Unicode ranges in is_emoji_char() were incomplete (e.g.,
missing U+2B50 ⭐), so emoji glyphs silently failed to render.
2. When members were skipped, font_ids.len() never matched
expected_member_count, so is_font_family_complete() never returned true
for i18n font families. This caused every draw call to hit the slow
path — the opposite of the optimization's intent.
Fix: load all font family members unconditionally. The one-time load cost
is negligible, and the fast path (is_font_family_complete → early return)
now works correctly on subsequent draws.
Removed the now-unused helpers: font_member_is_needed_for_text(),
is_cjk_fallback_font_path(), is_emoji_fallback_font_path(),
resource_basename(), text_has_cjk(), is_cjk_char(), text_has_emoji(),
is_emoji_char(), and FontFamily::ensure_fonts_loaded_for_text().
* Ensure .notdef glyph for unsupported characters is visibly drawn
When a character isn't in any font in the family (e.g., U+1FAEA not in
the bundled NotoColorEmoji), the shaper exhausts all fallback fonts and
emits glyph id 0 (.notdef) from the last font tried. For bitmap-only fonts
like NotoColorEmoji (no glyf table), the .notdef has no outline, so the
rasterizer returns None and draw_glyph silently skips it — leaving
invisible blank space.
Fix: in shape_recursive, when all fallback fonts are exhausted and glyphs
remain unmapped (id == 0), reassign them to the primary font (IBM Plex
Sans), whose .notdef has visible contours (the standard "tofu" rectangle).
This is done at the point of emission rather than as a post-processing
scan, so the common case (all glyphs found) has zero overhead.
* Another proper fix for black screen on Android start/resume
* Fix all Android surface artifacts on pause, resume, and cold start
* cleanup: remove java-level logging in MakepadActivity
* additional cleanup/improvements for android lifecycle stuff
* WIP fixing performance issue on Windows OS when resizing app window
* Improved resize behavior (flickering/stretching) on Windows
* cleanup window-resize optimizations for Windows OS
* Android: fix nondeterminstic crashes when using a bad surface
Makepad apps on Android were randomly crashing within the `Cx::render_view`
callstack when the host Activity surface was recycled, e.g., on
background/foreground transitions, rotation, IME show/hide, etc.
Details of the change are generated below:
----
`SurfaceHolder.Callback.surfaceDestroyed` posted a fire-and-forget message
to the render thread and returned to Android immediately, leaving two
overlapping races:
1. The render thread could drain `SurfaceDestroyed` from the mpsc channel,
call `destroy_surface()` (which does `eglMakeCurrent(NULL, NULL, NULL,
NULL)` and nulls the EGL surface), then in the *same* `RenderLoop`
iteration call `handle_drawing()` → `render_view()` and issue GL calls
with no current EGL context.
2. Even when our Rust side was well-behaved, Android was free to recycle
the underlying buffer queue the moment `surfaceDestroyed` returned to
Java, while the render thread was still mid-frame against it.
**Layer 1 — Surface validity tracking.** Added `CxOs::surface_alive`,
flipped synchronously in `SurfaceCreated`/`SurfaceChanged`/`SurfaceDestroyed`
handlers, plus a `CxOs::has_drawable_surface()` helper that gates every
GL/Vulkan dispatch entry point: `main_loop`'s `handle_drawing()`,
`draw_pass_to_window_for_active_backend`, `draw_pass_to_texture_for_active_backend`,
`draw_pass_to_fullscreen`, and `eglSwapBuffers` in `present_window_for_active_backend`.
**Layer 2 — Synchronous Java↔Rust handshake.** `FromJavaMessage::SurfaceDestroyed`
now carries an `Arc<(Mutex<bool>, Condvar)>` ack channel. The JNI binding
blocks the Android UI thread on the condvar (2-second budget, well under
the 5-second ANR threshold) until the render thread confirms it has
released the surface. This is the same pattern `android.opengl.GLSurfaceView`
uses, and it closes the underlying-buffer-recycled-mid-frame race.
**Layer 3 — Defense-in-depth re-bind.** `draw_pass_to_fullscreen` now calls
a new fallible `try_make_current()` every frame, recovering from any GL
context drift caused by foreign code or our own teardown path, and
bailing cleanly if the bind fails instead of crashing inside the driver.
Verified all 5 gated entry points against the `openxr_render_loop` →
`openxr_handle_repaint` path:
- 4 entry points are unreachable in XR mode (XR uses its own swapchains
and `xrEndFrame`, never `eglSwapBuffers` or the popup overlay path).
- `draw_pass_to_texture_for_active_backend` IS reachable (off-screen UI
textures composited into the XR scene). To prevent these from being
wrongly skipped in Vulkan+XR mode after the host surface is recycled,
added an explicit XR escape hatch to `has_drawable_surface()`: when
`in_xr_mode && openxr.session.is_some()`, return `true` based purely on
`vulkan.is_some()`, ignoring the (intentionally nulled) `display.window`.
This matches the existing `keep_xr_backend_alive` logic in the
`SurfaceDestroyed` handler.
- `destroy_surface` is now idempotent (safe to call when already null).
- `make_current` asserts on null surface with a descriptive panic message
instead of crashing inside EGL.
- Lifecycle handlers verify surface creation actually succeeded before
flipping `surface_alive` to `true` (no false-positive ready signal).
Tested working on my OnePlus Open w/ Android 15.
-------------
* Other fixes: minor change to logging format to include level indicator
* Fix warning in cargo makepad android
* Fix Android platform errors, mostly no draw on start/resume
Also a small related optimization on iOS
------------
* Android: JNI method ID caching (`ndk_utils.rs`)
* Rewrote the `call_method!` macro to cache `jmethodID` in a per-call-site `static AtomicPtr`. Previously, every JNI call allocated two `CString`s and called `GetObjectClass` + `GetMethodID` from scratch. Now these are resolved once and reused for all subsequent calls. Also deletes the local class reference after first resolution.
* Android: `to_java_update_tex_image` uses cached macro (`android_jni.rs`)
* Replaced manual `GetObjectClass`/`CString::new`/`GetMethodID` calls with the now-cached `call_bool_method!` macro, eliminating per-frame JNI overhead for video texture updates.
* Android: Touch event coalescing (`android.rs`)
* When draining pending messages before a RenderLoop frame, consecutive pure-Move touch events are now coalesced — only the last position is dispatched. Start/Stop events are never dropped. This reduces redundant event dispatch during active touch scrolling.
* Android: Black screen fix (`android.rs`)
* Added `needs_first_draw` flag to `CxOs`. When a `RenderLoop` callback arrives but the surface isn't drawable, the flag is set. When the surface later becomes available, `redraw_all()` is called to ensure the first frame is painted. The flag is also set on `SurfaceDestroyed` so resuming from background always gets a guaranteed first frame.
* iOS: Remove unnecessary `passes_todo.clone()` (`ios.rs`)
* Removed a redundant `Vec::clone()` in the popup pass draw loop — both the outer and inner loops borrow `passes_todo` immutably.
Windows was issuing two shutdown events any time the window was closed,
so it no longer does that. Might've been my fault in a previous change,
not sure.
Makepad apps on Android were randomly crashing within the `Cx::render_view`
callstack when the host Activity surface was recycled, e.g., on
background/foreground transitions, rotation, IME show/hide, etc.
Details of the change are generated below:
----
`SurfaceHolder.Callback.surfaceDestroyed` posted a fire-and-forget message
to the render thread and returned to Android immediately, leaving two
overlapping races:
1. The render thread could drain `SurfaceDestroyed` from the mpsc channel,
call `destroy_surface()` (which does `eglMakeCurrent(NULL, NULL, NULL,
NULL)` and nulls the EGL surface), then in the *same* `RenderLoop`
iteration call `handle_drawing()` → `render_view()` and issue GL calls
with no current EGL context.
2. Even when our Rust side was well-behaved, Android was free to recycle
the underlying buffer queue the moment `surfaceDestroyed` returned to
Java, while the render thread was still mid-frame against it.
**Layer 1 — Surface validity tracking.** Added `CxOs::surface_alive`,
flipped synchronously in `SurfaceCreated`/`SurfaceChanged`/`SurfaceDestroyed`
handlers, plus a `CxOs::has_drawable_surface()` helper that gates every
GL/Vulkan dispatch entry point: `main_loop`'s `handle_drawing()`,
`draw_pass_to_window_for_active_backend`, `draw_pass_to_texture_for_active_backend`,
`draw_pass_to_fullscreen`, and `eglSwapBuffers` in `present_window_for_active_backend`.
**Layer 2 — Synchronous Java↔Rust handshake.** `FromJavaMessage::SurfaceDestroyed`
now carries an `Arc<(Mutex<bool>, Condvar)>` ack channel. The JNI binding
blocks the Android UI thread on the condvar (2-second budget, well under
the 5-second ANR threshold) until the render thread confirms it has
released the surface. This is the same pattern `android.opengl.GLSurfaceView`
uses, and it closes the underlying-buffer-recycled-mid-frame race.
**Layer 3 — Defense-in-depth re-bind.** `draw_pass_to_fullscreen` now calls
a new fallible `try_make_current()` every frame, recovering from any GL
context drift caused by foreign code or our own teardown path, and
bailing cleanly if the bind fails instead of crashing inside the driver.
Verified all 5 gated entry points against the `openxr_render_loop` →
`openxr_handle_repaint` path:
- 4 entry points are unreachable in XR mode (XR uses its own swapchains
and `xrEndFrame`, never `eglSwapBuffers` or the popup overlay path).
- `draw_pass_to_texture_for_active_backend` IS reachable (off-screen UI
textures composited into the XR scene). To prevent these from being
wrongly skipped in Vulkan+XR mode after the host surface is recycled,
added an explicit XR escape hatch to `has_drawable_surface()`: when
`in_xr_mode && openxr.session.is_some()`, return `true` based purely on
`vulkan.is_some()`, ignoring the (intentionally nulled) `display.window`.
This matches the existing `keep_xr_backend_alive` logic in the
`SurfaceDestroyed` handler.
- `destroy_surface` is now idempotent (safe to call when already null).
- `make_current` asserts on null surface with a descriptive panic message
instead of crashing inside EGL.
- Lifecycle handlers verify surface creation actually succeeded before
flipping `surface_alive` to `true` (no false-positive ready signal).
Tested working on my OnePlus Open w/ Android 15.
-------------
* Other fixes: minor change to logging format to include level indicator
* Fix warning in cargo makepad android
The decoder only checked for double padding ('==') at input[len-2],
subtracting 1 output byte. Single padding ('=') at input[len-1] was
not handled, leaving 1 extra garbage byte in the decoded output.
This affected 2 out of 3 input lengths (any input where len % 3 == 2),
producing decoded output 1 byte longer than expected.
Fix: check input[len-1] for '=' first (subtract 1 byte), then check
input[len-2] for '=' (subtract another byte for double padding).
Added 7 roundtrip tests covering: no padding (3n bytes), single
padding (3n+2 bytes), double padding (3n+1 bytes), empty input,
lengths 1-20, all 256 byte values, and URL-safe alphabet.
Co-authored-by: prime intellect <prime@prime-intellects-Mac-Studio.local>
* Avoid re-entrant borrows of the IOS_APP global
I noticed this was happening any time the IME on iOS was used,
so I restructured those usages of IOS_APP to avoid them.
I then noticed that it could happen in other places, so I refactored
those as well.
* Fix missing iOS plist entry
* Fix `flow: Right` with `wrap: true`
This tiny math bug was causing widgets that got wrapped to the next line
in a `Flow: Right { wrap: true}` view to not get properly drawn
(the left side would get cut off).
* Expose `wrap_spacing` in `Layout` and splash script
This allows you to set the vertical spacing between widgets when
they wrap to the next line in a Right wrap flow layout.
Key repeat (holding down a key) did not work at all on Linux Wayland. The `RepeatInfo` event from the compositor was commented out, and all key events had `is_repeat: false` hardcoded.
- **`xkb_sys.rs`**: Added `xkb_state_get_keymap` FFI binding and a `key_repeats()` method on `XkbState` to check whether a key supports repeat (e.g., modifiers don't repeat).
- **`wayland_state.rs`**:
- Added `KeyRepeatState` struct and `KEY_REPEAT_TIMER_ID` constant.
- Added `key_repeat_rate`, `key_repeat_delay`, and `key_repeat` fields to `WaylandState`.
- Handled the previously-ignored `RepeatInfo` event to store the compositor's repeat rate/delay.
- On key press: start a one-shot timer with the repeat delay if the key supports repeat.
- On key release / keyboard leave: cancel the repeat timer.
- Added `handle_key_repeat_timer()` which fires `KeyDown(is_repeat: true)` and `TextInput` events, transitioning from the initial delay to a steady-state repeating timer.
- **`wayland_app.rs`**: Intercept the key repeat timer ID in the event loop and route it to `handle_key_repeat_timer()` instead of sending a generic `Timer` event.
- **`raw_input.rs`**: The evdev backend already received `KeyAction::KeyRepeat` from the OS but hardcoded `is_repeat: false` and didn't emit `TextInput` events. Fixed both.
- **`select_timer.rs`**: Fixed a pre-existing bug in `stop_timer` where removing a timer from the delta chain didn't adjust the successor's `delta_timeout`. This caused successor timers to fire early by the removed timer's delta. Also changed `update_timers` to use `pop_front()` instead of `stop_timer()` internally, since `select_time_used` already accounts for the removed timer's delta.
--------------
On Linux Wayland (and several other platforms), Makepad apps never received
`Event::Shutdown` when the window was closed via the client-side decoration
close button or when the app called `cx.quit()`.
When the CSD close button is clicked, it pushes `CxOsOp::CloseWindow`, which
is processed by `handle_platform_ops()`. When the last window is removed (or
`CxOsOp::Quit` is handled), this function returns `EventFlow::Exit`. However,
most backends did **not** call `Event::Shutdown` before exiting — only macOS
did it correctly.
Added `call_event_handler(&Event::Shutdown)` in the `handle_platform_ops() → Exit`
path for all affected backends, matching the existing macOS behavior:
- **Linux Wayland** (`linux_wayland.rs`) — added Shutdown call
- **Linux X11** (`linux_x11.rs`) — added Shutdown call
- **Windows** (`windows.rs`) — added Shutdown call
- **Linux Direct** (`linux_direct.rs`) — added Shutdown call
- **OpenHarmony** (`open_harmony.rs`) — added Shutdown call after main loop exit
(this backend uses `self.os.quit` instead of `EventFlow::Exit`)
- **macOS** — already correct
- **Android** — Shutdown is delivered via `FromJavaMessage::Destroy`
- **iOS / tvOS / Web** — different lifecycle models where explicit shutdown
doesn't apply (suspended by OS, or no reliable browser mechanism)
* cargo-makepad: support proper NDK builds (and on iOS)
The newly-emerging `aws-lc-rs` crate is quite popular and is
gradually replacing `ring`, which means we need to support it,
which is especially difficult to get right on Android and iOS targets.
This changeset makes it really easy to build that crate as part of
your app, if desired. There's no cost to apps that don't use it.
Android: make the stripped NDK installation the default, and make the
`full-ndk` option actually install the FULL NDK, not just the full set
of prebuilts. Like the whole thing, including build tooling like cmake
and other libraries.
* cargo-makepad: make install-toolchain for android more robust
Overwrite an existing installation instead of erroring out
* Support scrolling while centered, both vertically and horizontally
The previous turtle logic didn't allow you to center-align a view
while still making it scrollable. This small fix supports that now,
meaning that you can have:
* a vertically-centered view (`Align: { y: 0.5 }`) that is y-scrollable
* a horizontally-centered view (`Align: {x: 0.5 }`) that is x-scrollable
Also added some simple examples of this to `uizoo`
* fix iOS build
* Dock: add touch support for Tab/Tab bar interactions (scroll, drag/drop)
Details below:
1. **Finger-based tab drag-and-drop via long press** (tab.rs, android.rs, ios.rs):
- On touch devices, tab dragging now requires a long press before moving,
distinguishing it from scroll gestures.
- Added internal drag-and-drop support for Android and iOS backends,
synthesizing Drag/Drop/DragEnd events from touch move/up, matching the
existing Linux X11/Wayland approach.
2. **Finger-based drag-scrolling through the tab bar** (tab.rs, tab_bar.rs):
- A finger down + move (without long press) on a tab now scrolls the tab bar
horizontally instead of initiating a tab drag.
- Includes flick-to-scroll with velocity and decay for natural momentum.
- Touch tab selection is deferred to finger-up and only fires on a clean tap
(no long press, no scroll gesture), so scrolling/dragging doesn't
accidentally select tabs.
3. **Horizontal scroll input for tab bar** (scroll_bar.rs):
- When `use_vertical_finger_scroll` is enabled on a horizontal scroll bar,
both horizontal (trackpad) and vertical (mouse wheel) scroll inputs are
accepted, so trackpad users can scroll the tab list in either direction.
* fix drag/drop on macOS by using internal drag logic.
Fix ghost tab on dock to be much cleaner in terms of behavior
* Cleanup dock tab drag&drop behavior
Make platforms consistent. Switch macOS to internal drag item tracking
instead of OS-native (just for the dock for now).
Ensure ghost tab that gets drawn is consistently hidden (instantly)
upon being dropped in an invalid target zone.
* Smooth scroll the tab bar to the selected tab, if not visible
This animation does a lot to help the user track where tabs are.
Previously, without this, tabs would get lost in a lengthy tab bar
because you could select a tab and not realize which one was selected
as there was no visual indication that a non-visible tab was chosen.
This was especially strange when a new tab is programmatically selected,
as you couldn't tell where you were in the tab bar.
Now, if you select a tab that is far beyond the visible bounds of the
dock tab bar, it will auto-scroll to it with a smooth animation.
Also, if you click on a tab that is partially visible, it'll scroll
just enough to make that tab fully within the tab bar view.
Basically it's just like any IDE's tab bar now.
* Dock: add touch support for Tab/Tab bar interactions (scroll, drag/drop)
Details below:
1. **Finger-based tab drag-and-drop via long press** (tab.rs, android.rs, ios.rs):
- On touch devices, tab dragging now requires a long press before moving,
distinguishing it from scroll gestures.
- Added internal drag-and-drop support for Android and iOS backends,
synthesizing Drag/Drop/DragEnd events from touch move/up, matching the
existing Linux X11/Wayland approach.
2. **Finger-based drag-scrolling through the tab bar** (tab.rs, tab_bar.rs):
- A finger down + move (without long press) on a tab now scrolls the tab bar
horizontally instead of initiating a tab drag.
- Includes flick-to-scroll with velocity and decay for natural momentum.
- Touch tab selection is deferred to finger-up and only fires on a clean tap
(no long press, no scroll gesture), so scrolling/dragging doesn't
accidentally select tabs.
3. **Horizontal scroll input for tab bar** (scroll_bar.rs):
- When `use_vertical_finger_scroll` is enabled on a horizontal scroll bar,
both horizontal (trackpad) and vertical (mouse wheel) scroll inputs are
accepted, so trackpad users can scroll the tab list in either direction.
* fix drag/drop on macOS by using internal drag logic.
Fix ghost tab on dock to be much cleaner in terms of behavior
* Cleanup dock tab drag&drop behavior
Make platforms consistent. Switch macOS to internal drag item tracking
instead of OS-native (just for the dock for now).
Ensure ghost tab that gets drawn is consistently hidden (instantly)
upon being dropped in an invalid target zone.
* Add ellipsis text truncation support (text_overflow + max_lines)
Re-implement ellipsis truncation for text that overflows its container,
following conventions from CSS (text-overflow), Android (TextOverflow), and
Flutter (TextOverflow). This was supported in Makepad 1.0 but removed in 2.0.
Full summary below:
-----------------------
- Add `max_rows: Option<usize>` and `ellipsis: bool` fields to `LayoutOptions`
- Implement `apply_ellipsis_truncation()` post-processing step that:
- Detects when text was truncated (by max_rows or single-line overflow)
- Shapes the "…" (U+2026) glyph using the same font family
- Removes trailing glyphs from the last visible row to make room
- Trims trailing whitespace before the ellipsis for clean appearance
- Appends the ellipsis glyph(s) to the last row
- Recalculates the text bounding box
- Add early-exit in `layout_by_word()` and `layout_by_grapheme()` when
`max_rows` is exceeded, avoiding unnecessary layout work for long texts
- Add `is_truncated: bool` field to `LaidoutText` for consumer detection
- Fix pre-existing bugs in `LayoutOptions` Hash/PartialEq: `wrap` and
`line_spacing_scale` were missing, which could cause stale cache hits
- Add `TextOverflow` enum with `Clip` (default) and `Ellipsis` variants
- Add `max_lines: usize` and `text_overflow: TextOverflow` live properties
on `DrawText`, passed through to `LayoutOptions`
- Register `TextOverflow` in the script module for DSL access
- Resolve `Fit` width max bounds when ellipsis/max_lines is active, so
text layout knows the width constraint even for Fit-sized containers
- **Label** (widgets/src/label.rs): Add top-level `max_lines` and
`text_overflow` properties, forwarded to `draw_text` in `draw_walk()`
- **TextFlow** (widgets/src/text_flow.rs): Same top-level properties,
forwarded before each text draw call
- **Html / Markdown**: Inherit from TextFlow via `#[deref]` automatically
- Add `..mod.text` to widget prelude (widgets/src/lib.rs) so `Ellipsis`
and `Clip` are accessible in all widget DSL
- Make `FitBound::eval_width()` and `eval_height()` public (draw/src/turtle.rs)
so DrawText can resolve Fit max bounds during layout
```rust
// Simple single-line ellipsis
Label {
width: Fill
max_lines: 1
text_overflow: Ellipsis
text: "Long text gets truncated…"
}
// Multi-line with ellipsis
Label {
width: Fill
max_lines: 3
text_overflow: Ellipsis
text: "Wraps up to 3 lines, then truncates…"
}
// Also works via draw_text for any widget with DrawText
Button {
draw_text +: { max_lines: 1, text_overflow: Ellipsis }
}
```
See the demos I newly added to the `uizoo` example too.
* Fix TextFlow widget-level ellipsis for multi-run styled text (like Html)
The per-run forwarding of max_lines/text_overflow to DrawText caused each
styled run (bold, italic, etc.) to independently truncate with its own
ellipsis, producing double "……" artifacts in Html/Markdown content.
- Add `lines_drawn` and `content_truncated` fields to track visual lines
across all text runs within a single TextFlow
- Compute per-run `max_rows` based on remaining visual lines instead of
blindly forwarding the widget's `max_lines` to every DrawText call
- Handle continuation runs (starting mid-line) correctly: they get +1
row allowance since their first row shares the current visual line
- Skip further text runs once a run reports `is_truncated` (ellipsis drawn)
- Skip non-continuation runs when no visual lines remain
- `draw_walk_resumable_with` now returns `(usize, bool)`: row count and
whether the layout was truncated, so TextFlow can track state across runs
- Add three Html ellipsis examples: 1-line, 2-line styled, and emoji+styled
Details below:
1. **Finger-based tab drag-and-drop via long press** (tab.rs, android.rs, ios.rs):
- On touch devices, tab dragging now requires a long press before moving,
distinguishing it from scroll gestures.
- Added internal drag-and-drop support for Android and iOS backends,
synthesizing Drag/Drop/DragEnd events from touch move/up, matching the
existing Linux X11/Wayland approach.
2. **Finger-based drag-scrolling through the tab bar** (tab.rs, tab_bar.rs):
- A finger down + move (without long press) on a tab now scrolls the tab bar
horizontally instead of initiating a tab drag.
- Includes flick-to-scroll with velocity and decay for natural momentum.
- Touch tab selection is deferred to finger-up and only fires on a clean tap
(no long press, no scroll gesture), so scrolling/dragging doesn't
accidentally select tabs.
3. **Horizontal scroll input for tab bar** (scroll_bar.rs):
- When `use_vertical_finger_scroll` is enabled on a horizontal scroll bar,
both horizontal (trackpad) and vertical (mouse wheel) scroll inputs are
accepted, so trackpad users can scroll the tab list in either direction.
* TextInput: support single-line horizontal scrolling when text overflows
When a single-line TextInput's text content is wider than its visible area
(due to Fill, Fixed, or parent-constrained Fit width), the text now
automatically scrolls horizontally to keep the cursor visible.
- Add `scroll_x` tracking with auto-scroll-to-cursor logic
- Push a clip rect for all TextInput modes (not just multiline) to prevent
text from bleeding outside widget bounds
- Layout single-line text without max_width constraint so overflow is
detectable via `size_in_lpxs.width`
- Handle mouse wheel/trackpad scroll events for single-line horizontal
scrolling (maps both axes to horizontal)
- Account for `scroll_x` in IME position and selection rect calculations
- Add `Turtle::set_width()` and `Cx2d::compute_max_width_from_ancestors()`
(width counterparts to existing height methods)
- Add UIZoo examples: Fill width, Fixed width, and Fit-in-container
* a bit more cleanup, no need for clip size to be an option
* TextInput: support multiline mode with proper scrolling
* ScrollBar integration with mouse wheel, scrollbar handle drag,
and the correct way of dealing with `handled_y` propagation,
which basically means that scrolling can be handled by the TextInput
as a child, or not and left to the parent.
* Only auto-scroll to the cursor when it actually moves, not on every redraw
* `is_multiline` controls wrapping too, which makes more sense.
Now a single-line mode TextINput shouldn't wrap the text
* Allow setting `text` in the Splash DSL (make it `#[live]`)
Also added some examples to the uizoo demo: empty, pre-filled, read-only, toggle
between multi and single line.
* minor cleanup for TextInput multiline/scrolling
* Explicitly support multiline TextInput with Relative max height bounds
* more cleanup
* TextInput: support cascading parent-relative max height bounds
This was needed in Robrix, specifically a fairly common case in which
a TextInput should expand to Fit its content, but should not exceed
a certain percentage of the height of its parent.
This builds on the initial relative min/max Fit bounds that Eddy added
a while back, but weren't directly handled by TextInput, whcih itself
has special demands because it has to start internally
wrapping/scrolling.
* fix merge artifacts
* TextInput: support multiline mode with proper scrolling
* ScrollBar integration with mouse wheel, scrollbar handle drag,
and the correct way of dealing with `handled_y` propagation,
which basically means that scrolling can be handled by the TextInput
as a child, or not and left to the parent.
* Only auto-scroll to the cursor when it actually moves, not on every redraw
* `is_multiline` controls wrapping too, which makes more sense.
Now a single-line mode TextINput shouldn't wrap the text
* Allow setting `text` in the Splash DSL (make it `#[live]`)
Also added some examples to the uizoo demo: empty, pre-filled, read-only, toggle
between multi and single line.
* minor cleanup for TextInput multiline/scrolling
* Explicitly support multiline TextInput with Relative max height bounds
* more cleanup
Fixes a minor bug in which the entered text within a TextInput widget
would not get its layout recalculated if the width changed, meaning
that text could get cutoff on the right side of the widget.
Now we ensure that the text layout gets re-done (and the cached value
is not incorrectly used) if the width has changed since the last layout.
* Fix behavior of `PortalList::at_end()` to be fully correct
This has been buggy for a long time, now it is flawless.
* cleanup, add some clarifying comments
This was causing punctuation marks to wrap to the next line
by themselves instead of sticking with the previous word,
but that looks really strange/wrong.
---------------
Unicode word boundary segmentation (UAX#29) treats punctuation as
separate segments from words, causing the text layouter to wrap
punctuation like `.` `,` `;` `)` to a new line by itself.
Added `merge_segments_for_line_breaking()` that post-processes word
boundary segments before width measurement, following standard
line-breaking conventions (UAX#14 / CSS Text Module Level 3):
- Trailing/closing punctuation (`. , : ; ! ? ) ] }` etc.) merges into
the preceding segment (no break before).
- Opening punctuation (`( [ {` etc.) merges into the following segment
(no break after).
- Consecutive punctuation chains correctly (e.g., `):` stays with the
preceding word).
add handlers for "will enter/exit fullscreen" instead of just handling
"did" enter/exit, in order to properly animate. Otherwise it looks
janky for a split second where the traffic light buttons are on top of
the old app content before it refreshes.
* Expose window chrome button bounding box in `WindowGeom`
This allows apps that wanna draw something in the title/caption bar
to do so in a proper way without potentially drawing over the native
window chrome buttons (on macOS, the traffic light buttons).
Without this it'd be pretty tough to figure that out.
This also auto-sets the caption bar height to be tall enough such that
the window chrome / traffic light buttons are perfectly vertically-centered
in the middle of the caption bar. This was needed on macOS to prevent
things from looking janky as hell on newer macOS versions, which changed
the default size of the traffic chrome buttons.
It'll also be useful for drawing things in the caption bar on linux
or windows too.
Full change set:
- `widgets/src/window.rs`: Hide the caption bar on `LinuxWindow` when
`!custom_window_chrome` (X11 — WM provides native decorations) directly in
`sync_caption_bar_state`, removing the need for apps to do this manually.
- `event/window.rs`: Add `window_chrome_buttons: Rect` to `WindowGeom` —
the bounding box of the OS/app-drawn window chrome buttons in logical window
coordinates (top-left origin). Non-zero on macOS (traffic lights), Windows
(min/max/close), and Wayland with `custom_window_chrome`. Zero on all other
platforms (X11, LinuxDirect, mobile, web). Documented with per-platform
details and guidance on how to use it for caption-bar layout margins.
- `macos_window.rs`: Add `traffic_lights_geom()` — queries all three
traffic-light buttons via `standardWindowButton:`, converts their frames
to Makepad's coordinate system via `convertRect:fromView:`, and returns
the bounding box as a `Rect`.
- `win32_window.rs`: Populate `window_chrome_buttons` with the right-aligned
138×29 px bounding box of the three Makepad-drawn caption buttons.
- `linux_wayland.rs`: Populate `window_chrome_buttons` in the
`WindowGeomChange` handler when `custom_window_chrome: true`, using the
same right-aligned 138×29 px layout.
- `cx_api.rs`: Add `update_caption_bar_height_script_value()` to push a
measured height into `mod.widgets.CAPTION_BAR_HEIGHT` on the script heap.
- `window.rs` (DSL): Declare `mod.widgets.CAPTION_BAR_HEIGHT = 27.0` in the
`script_mod!` block and change `caption_bar.height` from the hardcoded `27`
to `(mod.widgets.CAPTION_BAR_HEIGHT)`. On `WindowGeomChange`, derive the
default caption bar height from `window_chrome_buttons` using equal
top/bottom padding (`pos.y * 2 + size.y`) and trigger a script reapply.
- `platform/src/lib.rs`: Export `LinuxWindowParams`, `WindowGeom`,
and `SafeAreaInsets`.
* Fix calculation of title bar height based on buttons
ensure dynamic override actually propagates via Rust code
* clearly define system-calculated caption bar height vs manual override
* Fix window drag move bounds to match the caption bar area
remove excess debug logs
Previously, PortalList's drag scrolling straight up didn't work
when the initial FingerDown event (touch/tap/click) landed on a widget
that is "interactive", meaning it could handle events. Not sure when that
concept was introduced, but it's kinda flawed given that all widgets
just defaulted to being `true` (always interactive). But imo that goes
against the ethos of simple event handling based on ordering of calls to
`handle_event()`, not to mention the whole `capture_overload` thing.
I think this is the solution that we've always wanted. The PortalList itself
now tracks when it is scrolling (and only starts a scroll once it is sure
enough finger/mouse movement has occurred, `TAP_COUNT_DISTANCE`),
and it does not deliver these interactive events to child widgets
while it is scrolling. This will make things a lot easier for the app dev too,
since that's how iOS and Android work too.
Details of changes to `portal_list.rs`:
- Always enter `ScrollState::Drag` on FingerDown regardless of whether
the touch point is over an interactive widget. A `committed` flag and
`drag_scroll_threshold` (defaulting to `TAP_COUNT_DISTANCE`) gate
when scroll deltas actually apply, preventing micro-scrolling during
taps/clicks on interactive items.
- Suppress event forwarding to child widgets once a drag scroll commits
(finger moves past threshold), so children don't receive stale
interaction events during scrolling.
- Suppress event forwarding when a finger-down/click arrives while a
scroll animation (flick, pulldown, etc.) is in progress, so tapping
to stop a scroll doesn't also activate a child widget.
- Add configurable `drag_scroll_threshold` property to PortalList.
Primarily on Linux, ensure that we show the title/caption bar
and draw it within Makepad (i.e., client-side drawing) if the
DE/WM doesn't show it by default.
This should make things behave as expected on Linux X11 and Wayland
both.
no app-level overrides are needed now.
1. on Windows, the windows buttons now behave and are drawn
just like all other apps -- proper bg coloring on hover and down,
and the right sizing.
2. and for the caption label, it is centered properly (by accounting
for the size of the windows_buttons button set), and then when the
window is too narrow, it is left-aligned in the remaining space
to ensure that it stil looks good.
* Introduce knowledge of device screen bounds/cutous/"safe inset areas"
Tested working on iOS, implemented for Android but not yet tested.
The approach may need to be improved, because it currently restricts
the whole app window to being fully within the safe areas.
We may not necessarily want that, or if we do, then we probably also
need to support setting the base color of the reserved system areas
(beneath the app bounds and above in the notification bar area).
* Use metal scissor rect to prevent SVGs/icons from being mis-drawn in safe areas
This prevents anything from being accidentally drawn in the safe
inset areas when the pass clear_color is transparent. Of course,
we can still draw the pass clear_color in those areas.
* Workaround: apply a scissor rect within safe inset area
Only apply it to clip any DrawSvg/DrawVector-specific draw calls
within the safe inset area.
This is unfortunately still just a hacky solution, because if we
actually do want to draw svg/vectors within that safe inset area,
then we won't be able to.
* Properly fix gpu artifacts when rendering SVGs
The `DrawSvg` vertex shader had a GPU fringe expansion pass designed for `fill_gpu()` mode, where fringe vertices encode per-vertex normals in the `v` and `stroke_dist` fields. SVG rendering used `fill_gpu()`, which produces **coincident-vertex fringe triangles** (body and outer fringe at the same CPU position, expanded on the GPU). These zero-area triangles caused **Metal GPU rasterization artifacts** — stray fragments appearing at unexpected screen positions.
**`draw/src/svg/render.rs`** — Switch SVG fill from `fill_gpu()` to `fill()`. Pre-computed fringe produces vertices at physically different positions (no coincident vertices).
**`draw/src/shader/draw_svg.rs`** — Remove the GPU fringe expansion code from the vertex shader. With pre-computed fringe, the `v` and `stroke_dist` fields are constants (`1.0` and `0.0`), not per-vertex normals. The expansion code was misinterpreting `v=1.0` as a horizontal normal, corrupting vertex positions.
**`libs/apple_sys/src/lib.rs`** — Added `MTLScissorRect` struct (unused now but available for future use).
**`src/home/rooms_sidebar.rs`** — Changed shadow offset from `vec2(1.0, 0.0)` to `vec2(0.0, 10.0)` so the `RoundedShadowView` shadow only draws below the header, eliminating the gray line at the top of the screen (issue 1).
* Expose safe area inset padding to app, don't forcibly apply it to root window
* Fixed safe area insets padding, with support for rotation
We now make these values available to the app dev (see below)
instead of forcibly inserting them as padding on all root windows.
This will allow each app to choose how and when they want to apply said pad values
(or if they want to at all) in an easy way, both at the Splash level
or more dynamically/programmatically at the Rust level.
Required quite a few changes to how things work in the iOS platform plumbing,
also described below in the generated summary:
On iOS and Android, Makepad apps render content behind device cutouts (Dynamic Island, camera notch), home indicators, and rounded screen corners because the framework has no awareness of safe area insets.
Added platform-level safe area inset querying on iOS and Android, exposed the values through both the Splash DSL (`mod.widgets.SAFE_INSET_PAD_*`) and Rust (`cx.display_context.safe_area_insets`), and ensured they update correctly on device rotation.
**New types:**
- `UIEdgeInsets` struct in `libs/apple_sys` for Objective-C interop
- `SafeAreaInsets` struct in `platform/src/event/window.rs` (top/right/bottom/left in logical points)
- Added `safe_area_insets` field to `WindowGeom` and `DisplayContext`
**iOS (`platform/src/os/apple/ios/`):**
- Query `[UIView safeAreaInsets]` from the MTKView in `check_window_geom()`
- Added `viewSafeAreaInsetsDidChange` callback on `MakepadViewController` to detect inset changes on rotation
- Populate `display_context` before `Event::Startup` so values are available during app script initialization
- Fixed MTKView setup: removed redundant `addSubview:` (conflicted with `setRootViewController:`) and added autoresizing mask — both required for safe area propagation on rotation
**Android (`platform/src/os/linux/android/`):**
- Added `SafeAreaInsets` variant to `FromJavaMessage` and corresponding JNI function
- Java side (`ResizingLayout.onApplyWindowInsets`): queries `WindowInsets.Type.systemBars() | displayCutout()` and sends insets to Rust (converted from px to dp)
- Added `safe_area_insets` field to `CxOs`, populated on `SafeAreaInsets` message and included in `WindowGeom` construction
- Added `surfaceOnSafeAreaInsets` native method to `MakepadNative.java`
**Splash DSL variables (`widgets/src/lib.rs`):**
- `mod.widgets.SAFE_INSET_PAD_TOP`
- `mod.widgets.SAFE_INSET_PAD_BOTTOM`
- `mod.widgets.SAFE_INSET_PAD_LEFT`
- `mod.widgets.SAFE_INSET_PAD_RIGHT`
- Values read from `display_context` at widget module initialization (during `Event::Startup`)
- Updated on the script heap via `Cx::update_safe_inset_script_values()` on `WindowGeomChange`
**Rotation support:**
- Added `pending_script_reapply` flag on `Cx` — set when safe area insets change, checked at the end of the platform event loop iteration
- Fires a deferred `LiveEdit` event to re-evaluate and re-apply all Splash widget definitions with updated inset values
- Implemented in both iOS and Android event loops
**StackNavigationView fix (`widgets/src/stack_navigation.rs`):**
- Full-screen stack views now position at `max(safe_area_insets.top, parent_rect.pos.y)` instead of hardcoded `y: 0`, respecting both mobile safe areas and desktop title bars
**All other platforms:**
- Added `..Default::default()` to all `WindowGeom` constructors (macOS, Windows, Linux X11/Wayland/Direct, web, tvOS, OpenHarmony) so the new `safe_area_insets` field defaults to zeros
* Additional opptimizations for windows shader compilation
`hlsl_compile_shaders` was called unconditionally after every draw event, even on
frames where no new shaders needed compilation. Added an early return:
```rust
if self.draw_shaders.compile_set.is_empty() {
return;
}
```
The loop previously collected `compile_set` into a temporary `Vec` before
iterating, in order to release the borrow on `compile_set` so the loop body
could mutate other `draw_shaders` fields. This caused a heap allocation and a
full copy of all indices on every compilation batch.
`std::mem::take` atomically replaces `compile_set` with an empty `BTreeSet` and
returns ownership of the original — no intermediate allocation, no copy, and no
separate `.clear()` needed at the end:
```rust
let compile_set = std::mem::take(&mut self.draw_shaders.compile_set);
for draw_shader_id in compile_set { ... }
// no .clear() needed
```
Previously `shader_cache_dir()` was an inner function called inside
`CxOsDrawShader::new`, meaning it ran once **per shader** on every compilation.
Each call performs two syscalls: `env::var("LOCALAPPDATA")` and
`fs::create_dir_all`. With N shaders compiling on first launch, this was 2N
unnecessary syscalls.
`shader_cache_dir()` is now a module-level function called **once** before the
loop in `hlsl_compile_shaders`, and the resulting `Option<&Path>` is passed into
`new` as a parameter.
The HLSL source already lives in `cx_shader.mapping.code`. The previous code
cloned it into an owned `String` before passing it to `new`, even though all
downstream uses (hashing, `D3DCompile`, cache I/O, error printing) only need a
`&str`. Changed the parameter type to `&str` and restructured the loop body into
a block scope so the immutable borrow on `cx_shader` ends before the mutable
reborrow — eliminating the clone entirely.
`CxOsDrawShader::new` already took `&UniformBufferBindings` by reference. The
clone existed only because the immutable borrow on `cx_shader` had to be released
before the mutable reborrow. The same block-scope restructuring from fix#4
resolves this: `&cx_shader.mapping.uniform_buffer_bindings` is now passed
directly.
This field was written once on construction and **never read** — the only reader
was the O(n) deduplication scan removed in the previous round of fixes. It held
a full copy of each shader's HLSL source for the entire lifetime of the
application. At tens of KB per shader and dozens of shaders, this was megabytes
of permanently retained dead storage. The field is gone.
* More shader optimizations on windows
Properly get the Local AppData directory instead of using the
env var %LOCALAPPDATA, which may not always be there.
Now we do it with `SHGetKnownFolderPath(FOLDERID_LocalAppData)`,
which is canonically correct.
We also cache the directory path itself.
On Windows, a large app like Robrix freezes for 10–20+ seconds after login while sync begins.
Profiling (`sc.user_aux.etl` from Visual Studio Performance Profiler) showed:
| Module | Exclusive CPU samples | % of total |
|---|---|---|
| `d3dcompiler_47.dll` | 24,239 | **76.76%** |
| `robrix.exe` | 2,715 | 8.60% |
A single thread (TID 20308) consumed **27.9 seconds of CPU** over the 35-second trace.
Every other robrix thread combined used under 2 seconds.
The butterfly call graph confirmed: `robrix.exe → d3dcompiler_47.dll` with 25,337
inclusive hits (80.24%). The UI was blocked the entire time.
In `makepad/platform/src/os/windows/windows.rs`, the main Win32 event loop calls:
```rust
if self.need_redrawing() {
self.call_draw_event(time_now);
self.hlsl_compile_shaders(&d3d11_cx); // blocks here
}
```
`hlsl_compile_shaders` iterates over every shader in `compile_set` and calls
`CxOsDrawShader::new`, which calls `D3DCompile` (from `d3dcompiler_47.dll`)
**synchronously on the UI thread** for each unique shader. After login, many
new UI panels render for the first time, flooding `compile_set`. `D3DCompile`
is a full software HLSL→DXBC compiler with no OS-level cache — it is CPU-bound
and cannot yield.
This affects all makepad apps on Windows, not just Robrix.
**File changed:** `makepad/platform/src/os/windows/d3d11.rs`
Added a disk-based shader bytecode cache so that `D3DCompile` is only called
once per unique shader source, on first launch. Subsequent launches load the
pre-compiled DXBC bytecode directly, skipping `D3DCompile` entirely.
**Specific changes:**
1. `CxOsDrawShader` struct: changed `pixel_shader_blob` and `vertex_shader_blob`
field types from `ID3DBlob` to `Vec<u8>`. These fields were stored but never
read after construction, so there is no behavioral difference.
2. `compile_shader` (inner fn): changed return type from `ID3DBlob` to `Vec<u8>`,
copying the blob bytes out before returning.
3. Three new inner helper functions added to `CxOsDrawShader::new`:
- `hlsl_cache_key(hlsl: &str) -> u64` — FNV-1a 64-bit hash of the HLSL
source string, stable across Rust versions, used as the cache key.
- `shader_cache_dir() -> Option<PathBuf>` — resolves
`%LOCALAPPDATA%\makepad\d3d11_shader_cache\`, creating it if needed.
Returns `None` gracefully if `LOCALAPPDATA` is unset or the directory
cannot be created, in which case compilation proceeds as before.
- `get_shader_bytes(...)` — checks for a cached `<hash>_vs.dxbc` /
`<hash>_ps.dxbc` file; on a cache miss, compiles via `D3DCompile` and
writes the result to disk before returning.
- **First launch:** all shaders compile as before; each VS/PS blob is written to
`%LOCALAPPDATA%\makepad\d3d11_shader_cache\<hash>_vs.dxbc` and `<hash>_ps.dxbc`.
- **Subsequent launches:** bytecode is read from disk; `CreateVertexShader` /
`CreatePixelShader` / `CreateInputLayout` are called directly with the cached
bytes — `D3DCompile` is never invoked.
- **Cache invalidation:** the cache key is the FNV-1a hash of the HLSL source,
so entries automatically become stale (and are recompiled + re-cached) whenever
the shader source changes.
- **Failure safety:** file I/O errors are silently ignored — a failed write means
the cache is just skipped next time, and a failed read falls through to
recompilation.
* fix windows build by adding missing consts to windows-rs
* Improve font parsing and text drawing perf with a hybrid caching approach
* Reset rustybuzz face cache when cloning FontFace
* fix improper row decorations, back to working properly
* ft makepad_test
* Improve run handling, manifest parsing, and stdout newline
Replace dynamic free-port lookup with an ephemeral localhost SocketAddr in test runtime and remove the unused find_free_listen_address helper. Ensure headless stdout messages end with a newline. Simplify send_to_app error handling and add a test that queued bootstrap messages are delivered once an app socket connects. Substantially enhance process_manager: unify cargo flag parsing, parse Cargo.toml to determine package/bin targets, resolve the correct binary name for direct stdio runs, and build the cargo/build+exec script from the resolved args. Add unit tests for manifest parsing and script generation and adjust related call sites.
* test harness
* Preserve test attrs; return Vec for gateway binds
In the test macro (libs/makepad_test/macros/src/lib.rs) preserve wrapper-only attributes (ignore and should_panic) on the generated wrapper test while removing them from the inner function. Added Attribute import, is_wrapper_only_test_attr helper, adjusted attribute filtering and emission, and added unit tests to verify attribute placement and expansion.
In the hub (studio/hub/src/hub.rs) change gateway_bind_candidates to return a Vec<SocketAddr> instead of an iterator and special-case ephemeral port 0 to preserve ephemeral binding; otherwise collect the range of candidate ports into a Vec. Added tests to validate candidate behavior. Also minor formatting/whitespace tweaks and a small IPv6 formatting adjustment.
* Add visible Studio mode and remote client
Enable running UI tests visibly through a running Makepad Studio. Adds a new makepad-network dependency and studio_remote client (libs/makepad_test/src/studio_remote.rs) and integrates it into the runtime via a TestConnection enum. Introduces visible-mode tooling: env vars (MAKEPAD_TEST_VISIBLE, MAKEPAD_TEST_STUDIO, MAKEPAD_TEST_STUDIO_MOUNT, MAKEPAD_TEST_STARTUP_DELAY_MS, MAKEPAD_TEST_ACTION_DELAY_MS, MAKEPAD_TEST_KEEP_OPEN_MS), pacing/delays after actions, and pause-before-shutdown. Splits startup into start_headless_app/start_visible_app, clears existing visible builds before launching, and updates tests, docs (GUIDE.md, README.md), and selector/runtime minor cleanups/formatting.
Explicitly redraw the overlay widget draw_list when opening and closing Modal and PopupNotification. This makes the overlay visible immediately on the first open (before the overlay content has refreshed or established a reusable draw area) and ensures the previous frame isn't left visible too long on close. Keeps existing background redraws intact.
* Introduce UI constants and tweak studio layout
Consolidate and adjust studio UI sizing, spacing and styling across desktop widgets.
Key changes:
- studio/desktop/src/app_ui.rs: Add STUDIO_HEADER_HEIGHT and StudioDock; use constant for various header/caption heights; reorganize PaneToolbar into grouped Views, adjust spacing/margins, refine caption label styling, and apply custom draw_text/draw_bg for tabs.
- studio/desktop/src/desktop_file_tree.rs: Add STUDIO_FILE_TREE_ROW_HEIGHT and STUDIO_FILE_TREE_NODE_HEIGHT; switch hardcoded row/node heights to constants, adjust padding and pass node_height to FileTree; update DesktopFileTree::ROW_HEIGHT.
- studio/desktop/src/desktop_log_view.rs: Increase LogEmptyItem height, adjust padding/alignment and label text style; update DesktopLogView::EMPTY_ROW_HEIGHT.
Why: unify header/row sizing via constants, improve spacing and visual consistency, and centralize small styling tweaks for easier future adjustments.
* Add TerminalCloseableTab and TerminalAddTab
Define two new tab templates in app_ui.rs: TerminalCloseableTab (closeable terminal tab style with custom close button sizing and colors) and TerminalAddTab (compact "+" add-tab style with centered text and custom background/hover colors). Wire them into the StudioDock templates and switch the terminal_add DockTab to use TerminalAddTab. Update app_backend.rs to insert terminal tabs using TerminalCloseableTab instead of the generic CloseableTab.
* Standardize UI border colors and add draw_bg style
Replace explicit border color values with theme.color_u_hidden for border_color and border_color_2, and set active borders to a subtle bg tint (theme.color_bg_app * 0.92) to reduce visual prominence. Add a new draw_bg style block (with colors and the same hidden/active border settings) and apply the same border adjustments to related UI elements to unify border appearance across the app UI.
* Refactor and restyle app UI components
Add new reusable styles and refine layout/spacing in app_ui.rs. Introduces LogToolbarToggle and SidebarFilterInput styles and applies them to the log toolbar and file tree filter respectively, tightening visual consistency. Adds StudioTerminalView with padding and uses it in TerminalPane. Adjusts several sizes and spacings (log filter width 232 -> 216, clear button 24 -> 20, spacing 6 -> 4, adds 10px spacer View), and updates paddings for buttons. Refactors caption toggles into a shared CaptionChromeToggle (size/icon_walk/border_radius/colors tweaked) and defines CaptionSidebarToggle/CaptionPanelToggle as specializations that set their SVG icons. These changes are primarily visual/layout polish and consolidation of repeated style definitions for maintainability.
* Adjust close button size, margin and colors
Refine the close button styling: add a close_button block and standardize its size to 11x11, update margins (left: 1.0, right: 7.0) and unify draw_button colors/hover/active states to updated hex values. These tweaks align spacing and visual states across tab elements for a cleaner, more consistent UI.
* Adjust app background and empty text colors
Tweak background state multipliers for theme.color_bg_app to slightly brighter values (color: 0.82, hover: 0.88, focus: 0.92, down: 0.85, empty: 0.82 vs previous 0.78/0.86/0.9/0.82/0.78). Also switch empty text colors from theme.color_label_outer_off to theme.color_label_inner_inactive (hover matches), leaving color_empty_focus as theme.color_label_outer. These changes improve contrast and visual consistency for empty and interactive app UI states.
* Add animated sidebar toggle and splitter APIs
Add an animated, persistent sidebar toggle and supporting splitter APIs/UI.
- New icon resource: studio/desktop/resources/icons/icon_sidebar_toggle.svg
- UI: replace hidden caption bar with a visible caption that includes a sidebar toggle button (CaptionSidebarToggle), layout adjustments and related controls.
- App behavior: add SidebarAnimation struct and App fields to track animation state and next-frame. Handle button actions, next-frame stepping, and window drag queries to avoid initiating window drag over the toggle.
- Add App methods to query/set the workspace root splitter position, start/step sidebar animations, toggle sidebar (remember/restore width), and sync tab-bar visibility for mounts.
- Persist mount sidebar restore width by adding sidebar_restore_width to MountState and initializing default.
- Make save_state pub(super) so App can save when animation finishes.
- Widgets: expose splitter position and set_splitter_align on Dock and Splitter to allow programmatic width changes and redrawing.
These changes improve UX by providing a smooth animated sidebar hide/show, remembering user width per mount, and ensuring the dock UI updates correctly during mount/tab changes.
* Persist sidebar_restore_width in mount state
Add an Option<f64> sidebar_restore_width to PersistedMountStateRon and wire it through loading and collection so the sidebar width is saved and restored. Include tests to verify a round-trip serialization/deserialization preserves the value and that missing legacy data defaults to None for backward compatibility.
* Sync run preview splitter state
Hide and restore the Run preview column based on whether run tabs exist. Added run_panel_split_restore to AppData to remember the last editor_split ratio per mount, plus a helper (run_preview_splitter_is_collapsed) and a new method sync_run_preview_splitter that collapses the preview when there are no runs and restores the previous ratio when runs reappear (defaults to Weighted(0.62)). Called sync_run_preview_splitter from relevant places: after creating/ensuring run tabs, when clearing build tabs, when closing run tabs, and at startup to initialize each mount. Also added /.cocoindex_code/ to .gitignore.
* Add bottom panel toggle with animation
Introduce a bottom panel toggle UI and animation support. Adds a new icon resource and CaptionPanelToggle button in the caption bar, moves/adjusts caption layout, and wires the button to toggle the bottom panel. Adds a TerminalShellPane, bottom_terminal_tab, and integrates bottom_panel_tabs into the workspace layout. Implements BottomPanelAnimation, animation helpers (panel_animation_progress, start/step logic), workspace splitter height getters/setters, and toggle/select helpers (toggle_bottom_panel, select_bottom_terminal_panel). Persists mount bottom_panel_restore_height in MountState and persisted state with tests updated. Also updates drag/tab behavior to account for the new bottom terminal tab and routes animation next-frame events.
* Refactor reflow_resize and add wrap test
Rewrite reflow_resize to simplify reflow logic and improve handling of growing/shrinking rows and scrollback. The change always captures the old grid state, builds logical (re-wrapped) scrollback lines, and then branches on whether the new height is greater, less, or equal to the old height. On growth it may pull rows from the logical scrollback when the terminal was full, preserve content when a custom scroll region is present, and correctly update cursor, high-water, saved cursor and bottom_trimmed_rows. On shrink it chooses how many bottom rows to push into scrollback based on cursor position, trimmed rows, and content below the cursor, and trims scrollback to max_scrollback. Also adjust cursor bounds and pending_wrap handling. Add a test (visible_wrapped_prompt_rows_do_not_reflow_on_width_growth) to ensure visible wrapped prompt rows do not get reflowed when the width increases.
* Removed `remove_all(view_id)` from `push_view()` to allow
the same view ID on the stack multiple times. Most apps will
need somethin like this, otherwise they'd have to have a weird
statically-known set of fixed views that are eligible to be
on the stack. Robrix, at the least, doesn't have that.
* Fixed `show()` to force-reset the animator state, which was needed
to make a fresh animation always play (even when animating in "reused" views)
* Reset `state` to `Inactive` in `show()`, otherwise it'll never transition to `Active`
TL;DR: the shader compiler needed explicit casts in for loop bounds,
and the window positioning was messed up, causing the app-level title bar
to overlap with the native OS-level title bar (which means you couldn't
see or press the window chrome buttons)
--------
here's an AI-generated summary of the changes, for more details:
On OpenGL ES 3.0 targets (Linux/EGL, Android), shaders containing `for` loops
over `uint` variables failed to compile with a GLSL type-error. The shader
compiler emitted the loop header as:
```glsl
for(uint i = 0; i < 4; i++) { … }
```
The integer literals `0` and `4` are of type `int` in GLSL. GLSL ES 3.0 forbids
implicit casts between `int` and `uint`, so the initialiser and the comparison
both produce a compile error. The other shader backends (Metal/WGSL/Rust) do not
have this restriction, so no corresponding arm existed for GLSL.
**`platform/script/src/shader_control.rs`** — Add a `ShaderBackend::Glsl` arm to
`handle_for_1` that wraps both loop bounds in an explicit constructor call for the
loop variable's type:
```glsl
for(uint i = uint(0); i < uint(4); i++) { … }
```
This satisfies GLSL ES 3.0's strict no-implicit-cast rule and matches the
behavior already implemented for the WGSL backend (which uses typed variable
declarations for the same reason).
**`platform/src/os/linux/opengl.rs`** — Gate the helper functions
`shader_source_hash` and `shader_source_preview` (and their call-sites) behind
`#[cfg(target_os = "android")]`. These functions are only referenced from
Android-specific shader-cache code paths; without the attribute the compiler
emits dead-code warnings on every other Linux/OpenGL build.
- `platform/script/src/shader_control.rs`
- `platform/src/os/linux/opengl.rs`
----------------------------------------------------------------------------
On GNOME (and likely other modern WMs), restoring a saved window position would
consistently produce two visual artifacts:
1. **No WM title bar visible** — the client area was rendered where the title bar
should appear.
2. **Black bar at the bottom** — the bottom portion of the window surface was not
covered by rendered content.
The root cause was two related issues in `xlib_window.rs`:
**Issue 1 — `XMoveWindow` called after `XMapWindow` (races with WM reparenting)**
After `XMapWindow`, the window manager asynchronously reparents the client window
into a decoration frame. If `XMoveWindow` is called after reparenting has occurred,
the coordinates are interpreted relative to the WM frame rather than the root
window. For example, calling `XMoveWindow(client, 23, 89)` after GNOME reparents
places the client 89 px from the top of the WM frame. Since the title bar is only
~37 px tall, the client ends up 52 px below the title bar, and its bottom edge
extends 52 px *beyond* the bottom of the WM frame. GNOME responds by resizing or
repositioning the client, producing the rendering artifacts described above.
**Issue 2 — No `USPosition` hint set**
Without the `USPosition` flag in `WM_NORMAL_HINTS`, GNOME ignores the position
provided to `XCreateWindow` and applies its own smart-placement algorithm. This
meant the application relied entirely on the post-map `XMoveWindow` call described
above, which was itself broken.
**`platform/src/os/linux/x11/x11_sys.rs`** — Add the standard `XSizeHints` flag
constants:
- `USPosition` (`1 << 0`) — user-specified x, y
- `USSize` (`1 << 1`) — user-specified width, height
- `PPosition` (`1 << 2`) — program-specified position
- `PSize` (`1 << 3`) — program-specified size
**`platform/src/os/linux/x11/xlib_window.rs`** — Two changes in `XlibWindow::init()`:
1. Before calling `Xutf8SetWMProperties`, populate an `XSizeHints` struct with
`flags = USPosition | PPosition` (and `x`/`y` set to the requested coordinates)
when a position was provided. Pass this struct as the `WM_NORMAL_HINTS` argument
instead of the previous `ptr::null_mut()`. This tells GNOME/Mutter to honor the
requested position rather than running its own placement heuristic.
2. Move the `XMoveWindow` call to *before* `XMapWindow`. At that point the window is
still a direct child of the root window, so the coordinates are unambiguously
root-relative. This eliminates the race with WM reparenting entirely.
* Schedule loader removal after presented frame
Introduce loader_after_presented_frame_id and add schedule/cancel helpers to remove the canvas loader after a presented animation frame. Cancel any pending requestAnimationFrame when the loader is removed or conditions change, and update update_startup_loader to use the new scheduling logic (remove loader only after seen animation frame and quiet frames threshold). This prevents premature removal and visual glitches while preserving the existing fallback timer.
* small fonts should be used when --profile=small
* dont include large fonts on small profile + fix compress serving
* Refactor window initialization and caption sync
Extract sync_caption_bar_state and sync_caption_title from ensure_initialized and call them before the initialized early-return so caption bar state and title are kept in sync even when the widget is re-applied. ensure_initialized still performs the original one-time setup (pass, depth texture, demo frame), but runtime chrome (VR button and caption visibility/title) is now updated up-front to avoid stale UI state.
* fix --bindgen
* Set imports.env in wasm import patches
Add additional string replacements to ensure imports.env = env is injected into generated JS for different formatting variants of the __wbg_get_imports(...) call. This makes the patch robust to variations like missing const or spacing so the env object is always attached to the wasm imports.
* Revert "Set imports.env in wasm import patches"
This reverts commit 37933234d36b4ee867690cf167474638e81febf7.
* Revert "fix --bindgen"
This reverts commit cb236b202b92a46eda3cb1ee4c678bdf6507081a.
* Reapply "fix --bindgen"
This reverts commit 27df4175bd1889222b928ffb68213aa865d1c839.
* Reapply "Set imports.env in wasm import patches"
This reverts commit 23695b4fa646d8f1b7a31a3fc27eb92f5bce0cf8.
* fix xr compilation error
* Fix windows build: add missing constants in vendored windows-rs bindings
* fix more build errors and warnings in windows-rs vendored copy
* Fix SVG parsing and vector drawing
* Fix windows build: add missing constants in vendored windows-rs bindings
* fix more build errors and warnings in windows-rs vendored copy
* Fix SVG parsing and vector drawing
* Fix erroneous unzip glob pattern that doesn't work on normal linux
* try another approach: unzip everything, cp needed dirs
These are needed to support better formatting of Html code
that mixes multiple different styles together, e.g., inline code
next to normal code, or inline code within a blockquote or a heading.
Full summary of changes:
**File:** `draw/src/shader/draw_text.rs`
Added `#[live(0.0)] pub top_drop: f32` to `TextStyle`. This is a vertical offset expressed as a fraction of font size — positive values shift text downward. It's useful for aligning baselines when mixing fonts with different vertical metrics (e.g., a code font rendered inline with regular text).
**File:** `draw/src/shader/draw_text.rs`
When `temp_y_shift != 0`, the extra shift pixels are now added to `allocate_height()` and the emitted walk rect. This prevents containers (blockquotes, etc.) from clipping the descenders (g, p, q, y) of vertically-shifted text.
**File:** `widgets/src/text_flow.rs`
After selecting the appropriate text style (normal/bold/italic/fixed), `draw_text.temp_y_shift` is now set from that style's `top_drop` value. This allows each style variant to specify its own vertical offset, since `TextFlow` uses a single shared `DrawText` instance for all text rendering.
**File:** `draw/src/turtle.rs`
New public method to mutate `layout.padding.left` after a turtle has been created.
**File:** `widgets/src/text_flow.rs`
After drawing the bullet/number marker, the actual cursor position is now measured and `set_padding_left()` is called so that wrapped continuation lines align with the text after the marker, rather than being over-indented by the estimated `font_based_padding` (which was `2.5 * font_size`).
Additionally, the hardcoded `draw_text(cx, " ")` spacer after the marker was replaced with `walk_margin(cx, self.list_item_marker_pad)` for precise pixel-based control.
**File:** `widgets/src/text_flow.rs`
Added `#[live(5.0)] list_item_marker_pad: f64` — a configurable spacing (in pixels) between the list item marker (bullet/number) and the content text that follows it.
Mat4f::mul(a, b) was computing b*a due to transposed summation
indices in the multiplication loop. All call sites (glTF TRS
composition, view-projection, MVP chains, scene hierarchy) are
written expecting standard a*b order, and transform_vec4 uses
standard column-major M*v convention.
Fix: swap the operand bindings so the existing index pattern
produces the correct a*b result.
Add regression test mat4_mul_order that verifies:
- Scale(2)*Translate(5,7) yields tx=10 (scaled translation)
- transform_vec4 on result*(1,1,0,1) yields (12,16)
Co-authored-by: ant <ant@offline.click>
* Add web URL/location and history handling
Sync browser location and history with the WASM app on the Web platform.
- Cx API: add default CxOsApi methods browser_update_url and browser_history_go and Cx wrappers to call them.
- Web/WASM IPC: add ToWasmLocationChange, FromWasmBrowserUpdateUrl, FromWasmBrowserHistoryGo structs for message passing and register them in init.
- Web JS: emit_location_change on popstate, and implement FromWasmBrowserUpdateUrl and FromWasmBrowserHistoryGo to update history (push/replace/back/forward/go).
- Cx web runtime: add normalize_web_pathname, split_web_location and update_web_location_state helpers; handle incoming ToWasmLocationChange to update internal state and signal events; implement browser_update_url and browser_history_go to forward requests to JS and update internal location state.
These changes enable SPA-style URL updates, history navigation, and app-side reactions to browser location changes while avoiding redundant updates.
* Handle hashchange and improve URL parsing
Add a window "hashchange" listener to emit location changes and trigger the wasm pump so fragment navigation updates are handled. Update FromWasmBrowserUpdateUrl to construct URLs relative to the current full location (window.location.href) so fragment- and relative-only updates resolve correctly. Tighten split_web_location parsing to treat '/', '?', and '#' as path delimiters after a scheme, ensuring queries and fragments are detected when extracting the path.
* fix wasm run without --release
* LTO off in small profile
Replace unwraps when querying the first glyph with safe handling and provide a fallback monospace cell size if no glyph is available (e.g. on wasm while fonts are still loading). Computes a reasonable width/height from the current font_size (width = 0.6 * font_size, height = font_size) so the editor can render on first draw instead of aborting. Keeps existing cell_size and cell_offset_y calculations based on the chosen dimensions.
* regenerate windows-rs to export missing functions
* fix compilation errors on windows
* import WS_EX_TOOLWINDOW from windows-rs instead of customizing the constant
---------
Co-authored-by: jasonqiu <jasonqiuchen@outlook.com>
* Initial plan
* feat(platform): add window visuals API and M1 backdrop wiring
Co-authored-by: wheregmis <26774729+wheregmis@users.noreply.github.com>
* feat(uizoo): add M2 GlassPanel widget and demo tab
Co-authored-by: wheregmis <26774729+wheregmis@users.noreply.github.com>
* blue example
* Apply backdrop intensity and transparency
macOS: apply visuals.backdrop_intensity to the NSVisualEffectView alpha (clamped) so backdrop intensity affects the effect view.
Windows: honor visuals.backdrop_intensity by computing an alpha-packed accent color, toggle WS_EX_LAYERED for transparent windows, call SetLayeredWindowAttributes and DwmExtendFrameIntoClientArea when needed, and set the AccentPolicy accordingly (enable blur for transparent or non-none backdrops, set gradient_color based on intensity, and set accent_flags depending on DWM attribute result).
Tests: update expected backdrop_intensity in a unit test from 4.0/1.0 to 0.25 and assert a platform op is emitted. These changes ensure backdrop intensity and transparency settings are respected across platforms.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: wheregmis <26774729+wheregmis@users.noreply.github.com>
Introduce selective font loading and a SharedBytes abstraction with mmap support to reduce memory and IO overhead when handling fonts.
Key changes:
- Add SharedBytes (with MappedBytes using memmap2) and stats; switch FontData to SharedBytes and update loader/loader tests.
- Platform: add memmap2 dependency (non-wasm), new platform API helpers get_resource_abs_path and get_resource_font_bytes to prefer mmap'ed files and fall back to owned bytes.
- Font family/load changes: ensure_fonts_loaded_for_text and update_font_definitions now accept optional text to load only needed fallback fonts (CJK/emoji) based on text content and resource basename heuristics.
- Avoid eager loading of heavy bundled fallback fonts (LXGWWenKai*, NotoColorEmoji.ttf) when loading all script resources.
- Add env override MAKEPAD_TEXT_ATLAS_SIZE for text atlas size parsing and tests; add related parsing helpers and tests.
- Minor fixes: safer transmute for font_face data slice and several unit tests to validate behavior.
Overall this reduces unnecessary font resource reads/mmap usage and allows tuning text atlas size via environment.
* Add wasm server_manager and ownership guard
Introduce a new wasm server_manager module that implements WasmServerOwnershipGuard to manage per-workspace lock files, PID/port ownership, and safe replacement of stale or live servers. Integrate the guard into compile::run by preparing and activating the guard before starting the HTTP server; start_wasm_server now accepts the guard, returns a Result, checks bind success, activates the guard, and propagates thread join errors. The server manager includes platform-specific PID handling, port-probing, lock read/write/remove helpers, and unit tests exercising startup scenarios and lock lifecycle. Also add mod declaration and apply minor formatting/refactor cleanups across compile.rs (error formatting, whitespace, and logging improvements).
* Add startup mutex and port occupant diagnostics
Rename server_manager into top-level module and add startup mutex handling and improved diagnostics. Introduces atomic lock writes (write_file_atomically), a StartupMutexGuard with configurable timeouts/polling, and logic to detect/recover stale startup locks to prevent concurrent wasm run startups. Extends ServerManagerProbes with describe_port_occupant and implements platform-specific detection (lsof on Unix, netstat/tasklist on Windows) to provide clearer errors when ports are occupied. Updates imports (main.rs, compile.rs, mod.rs) and adds unit tests covering startup lock behavior and unknown-occupant error text.
* Improve hot-reload logging and delivery
Add clearer logging and error handling for wasm hot-reload events. Introduces hot_reload_display_name to extract a user-friendly file name, logs when a hotreload is detected, and checks tx.send result to avoid panics when the watcher channel is closed. broadcast_hot_reload_event now logs whether events were skipped (no /$watch clients) or sent and includes the delivered client count with proper pluralization.
* Check lock PID owns port and add startup timestamp
Require that a lock's PID both be alive and actually own the server port to be considered a live lock (classify_lock_state now takes listen_addr). Add parsing of a started_at timestamp in startup locks and use now_unix_millis to age out stale startup locks even if the PID is alive. Extend ServerManagerProbes with now_unix_millis and pid_owns_port, implement port_occupant_info and PortOccupant to centralize occupant detection/refinement for unix and windows, and refactor describe_port_occupant to use it. Update tests and MockProbes to exercise PID ownership checks and startup-lock aging; add tests for PID reuse (treated as stale if it doesn't own the port) and for startup lock aging.
* Hot reload: dedupe sites, add logging
Avoid duplicate ScriptMod sites during hot reload and improve diagnostics. collect_compiled_sites_for_file now tracks seen ScriptModKey values to prevent pushing duplicates. handle_cx_live_edit counts processed files and logs how many overrides were applied and from how many changed files. forward_hot_reload_fs_event logs each detected file and reports if the watcher channel is closed. Minor import cleanup and a helper hot_reload_display_name were added for nicer log output.
* shared core for reload
* Warn and fallback to unminified JS on write error
When writing the minified JS file fails, log a warning and fall back to copying the original JS file instead of returning an error. This avoids failing the build on IO/write errors (e.g. permissions or disk issues) while preserving the previous behavior of using the unminified copy when reading fails. No change to successful minification path.
* wip
* Handle wasm data segments and manage brotli artifacts
Add full support for parsing, encoding and rewriting Wasm data segments: introduce WasmDataSegmentKind (Active/Passive), helpers to encode varints and const i32 exprs, and functions to rewrite the data section or replace a section payload. Update wasm_split_data_segments to preserve passive segments and data.count (section 12), only extract active segments into a separate split blob, and adjust segment counting and tests accordingly.
Bump brotli dependency to 8.0 in tools, and add remove_brotli_artifact to remove leftover .br files when brotli compression is disabled. Call this cleanup in cargo_makepad build/copy paths and when removing split data, and add .bin MIME mapping and print mapping for .bin in the server output. Tests updated to cover passive segments and data_count preservation.
* Support split data v2 and wasm rebuild
Add support for a new split data format (version 2) and the ability to rebuild a WASM module with its data section. wasm_bridge.js: parse v1/v2 split blobs, return {version, segments}, add varint encode/decode helpers, encode split-data section payloads, implement rebuild_split_wasm, fetch-and-instantiate logic to fetch both wasm and split blobs and handle v1 preloaded splits or rebuild for v2. wasm_strip.rs: bump split data version to 2, include segment kind and memory_index in encoded split data, preserve passive segments, update encoding/decoding and tests, and return the updated segment count. tools/cargo_makepad/src/wasm/compile.rs: separate target spec directories for threaded vs single builds (threads/single).
* basic splitting
* Instantiate secondary Wasm module in threads
Store and expose a compiled secondary WebAssembly module from the primary module, and ensure worker contexts (Web Worker and AudioWorklet) instantiate that secondary module before running thread entrypoints. Changes: save _secondary_module on primary_wasm in wasm_bridge, include secondary_module in WasmWebBrowser info, and add async instantiate_secondary logic + awaits in audio_worklet and web_worker so the secondary module is instantiated with {env, primary: primary_wasm.exports} prior to initializing stack/TLS or starting execution. This ensures the secondary module can import primary exports in threaded contexts and prevents race conditions by waiting for instantiation to complete.
* more cleanups
* more cleanup
* wip
* remove double wasm pump
* cleanup
* Cold-first auto wasm split with fallback
Add a cold-only function-splitting mode and automatic fallback to preserve startup-safe behavior. Introduces wasm_split_functions_cold() and a split_auto flag to run a cold-first pass that moves defer-safe cold functions to a secondary wasm; if no useful cold candidates are found the build falls back to the normal startup-path function split so the app still gets a secondary payload.
Changes include: update to CLI help text, new split_auto handling in WasmConfig, AutoSplitOutcome variants, compile logic to prefer cold-only splits then fall back to the regular split, adjusted logging for automatic mode, and README wording clarifications. Files touched: README.md, libs/wasm_strip/src/wasm_strip.rs, tools/cargo_makepad/src/main.rs, tools/cargo_makepad/src/wasm/compile.rs, tools/cargo_makepad/src/wasm/mod.rs.
* Tune brotli compression parameters
Adjust brotli settings in tools/cargo_makepad/src/wasm/compile.rs: increase buffer size from 4KB to 64KB, lower quality from 12 to 11, and raise window size from 22 to 24. This balances throughput and compression ratio for larger wasm artifacts—bigger buffer and window can improve compression effectiveness while slightly reducing CPU cost by lowering quality.
* Add optional wasm-opt optimization flag
Introduce an optional --wasm-opt option that runs Binaryen's wasm-opt -Os on built wasm when available. Adds a wasm_opt flag to WasmConfig (default false), parses the CLI option, and integrates a try_wasm_opt helper that writes a temp wasm, invokes wasm-opt, reads back the optimized output and prints size/reporting while gracefully falling back on errors. The wasm-opt step runs before the existing split/strip pipeline. Also updates CLI help text, minor Cargo.toml formatting changes, and removes a vendor UPSTREAM.md reference.
* update readme
* Shorten split exports to $s/$p and use base62
Rename split-related exports/imports to shorter identifiers and compress numeric indices using base62. Changes: export/table slot prefix changed from "__mp_split_table"/"__mp_split_slot_*" to "$s" and the primary import namespace from "primary" to "$p" across wasm_bridge, audio_worklet, and web_worker. Introduces encode_base62 in wasm_strip to emit $f/$t/$m/$g names with base62-encoded indices, and updates primary/secondary module generation and tests accordingly. Also includes minor JS formatting/whitespace cleanups and small safety/compatibility tweaks during WebAssembly instantiation.
* Preload WASM modules and set cache headers
Inject modulepreload links into generated HTML (conditionally including wasm_bridge and bindgen when bindgen is enabled, otherwise preloading web_gl only) to improve module loading. Also change served asset Cache-Control from max-age=0 to max-age=86400 (1 day) for both brotli-compressed and uncompressed responses to enable client caching and reduce repeated fetches.
* Minify JS when copying before brotli
Add a lightweight JS minifier and apply it in cp_brotli for .js files. Introduces minify_js which strips line/block comments, collapses unnecessary whitespace, preserves strings/escaped characters and simple regex literals (heuristic), and removes empty lines. cp_brotli now attempts to read and minify .js input and write the minified output to the destination (falling back to cp on read error), then continues to optionally brotli-compress the result. This reduces payload size prior to compression with a small, simple minification step.
* Format web.js and disable XR capability checks
Apply consistent JS formatting (spacing, brace placement, object literal spacing, and minor whitespace cleanups) across platform/src/os/web/web.js. Replace the previous XR capability detection with a no-op (query_xr_capabilities now returns Promise.all([])) and remove the await call in load_deps so XR checks are not performed during startup. Also includes minor non-functional tweaks (timers, audio worklet messaging formatting, fetch call spacing, and various input/keyboard handler cleanups). Overall changes are primarily stylistic with the notable behavior change of disabling XR capability probing.
* Add WASM no-cache headers; remove web video arms
Set Cache-Control to "no-store, must-revalidate" (plus Pragma/Expires) for .wasm responses while keeping max-age=86400 for other assets; wire these into the HTTP response headers. Also remove the explicit VideoSource::InMemory and VideoSource::Filesystem match arms from the web platform code (they previously logged errors and emitted VideoDecodingError events).
Video playback: extended API with volume, playback rate, seek ranges,
buffered ranges, can_play_type, audio-only mode. Unified player wrapping
native backend (AVPlayer/GStreamer/MediaFoundation) with software fallback.
YUV shader pipeline (BT.601/709/2020, NV12 biplanar, rotation).
Camera: V4L2 backend (Linux), expanded NDK Camera2 (Android), AVCapture
stream refactor (iOS/macOS) with shared session architecture. NV12
zero-copy paths on iOS (CVMetalTextureCache) and Android (AImage planes).
Camera preview modes (texture/native/auto).
Video encoding: H264 hardware encode on Apple (VideoToolbox) and Android
(MediaCodec). Camera-to-encoder pipeline with pixel buffer passthrough.
Media plugin system: externalized codec implementation via MediaPlugin
trait. MsePlayer, VideoFrameDecoder, MediaVideoEncoder, SoftwareVideoPlayer
interfaces. Runtime codec capability query and merge.
Includes camera example app.
Co-authored-by: ant <ant@offline.click>
- Add popup window type for context menus and dropdowns
- Wayland: xdg_popup with grab for compositor-driven dismiss
- X11: override-redirect windows with pointer grab
- Explicit-close semantics: app must handle PopupDismissed
- Fix Wayland crash on repeated context menu open/close
- Emit WindowClosed before PopupDismissed in PopupDone
Co-authored-by: ant <ant@offline.click>
* platform: popup window API with X11/Wayland support
- Add popup window type for context menus and dropdowns
- Wayland: xdg_popup with grab for compositor-driven dismiss
- X11: override-redirect windows with pointer grab
- Explicit-close semantics: app must handle PopupDismissed
- Fix Wayland crash on repeated context menu open/close
- Emit WindowClosed before PopupDismissed in PopupDone
* platform: native mobile selection handles
iOS: custom UIView selection handles with UIPanGestureRecognizer (all versions),
UITextSelectionDisplayInteraction for native highlights (iOS 16+),
MakepadSelectionRect for UITextInput protocol.
Android: custom SelectionHandleView with GradientDrawable oval, touch drag
listeners, JNI bridge for handle drag events.
Widgets: TextFlow clipboard action integration (show on touch up with selection,
hide on touch down/focus lost, TextCut handler), PortalList select-all and
clipboard actions, selection bounding rect computation for popup positioning.
Includes text_selection example app.
---------
Co-authored-by: ant <ant@offline.click>
- Android log levels: map log! macro levels to Android log priorities
(ERROR=6, WARN=5, INFO=4). Some devices suppress DEBUG by default.
- cargo-makepad android: show help text instead of panicking on missing
or invalid subcommand.
- Android build: discover .class files dynamically instead of hardcoding
~25 individual paths. Add Java 8 source/target flags.
- Remove deprecated AsyncTask import from MakepadNetwork.java.
- Studio stdout: add newline after JSON messages for JSON-lines parsing.
- Studio stdout: skip profiler timing in stdout mode to avoid overhead.
- Script thread: fix panic on empty call stack in call_has_me/call_has_try.
- Cursor: reset to Default on FingerHoverOut in TextFlow and TextInput.
Co-authored-by: ant <ant@offline.click>
process_tap_count incremented without bound. TextInput handles
tap_count 2 (select word) and 3 (select all) but ignored counts
>= 4. Rapid repeated double-clicks produced tap counts of 4, 5, 6...
hitting the _ => {} fallthrough and doing nothing.
Cycle the counter back to 1 after reaching 3 (1->2->3->1->2->3).
Co-authored-by: ant <ant@offline.click>
* regenerate windows-rs to export ImmAssociateContext
* fix ime popup window still shown when TextInput has no focus
* fix ime popup window still shown when TextInput has no focus for macos
* fix ime himc state
* IME support for linux
---------
Co-authored-by: jasonqiu <jasonqiuchen@outlook.com>
* regenerate windows-rs to export ImmAssociateContext
* fix ime popup window still shown when TextInput has no focus
* fix ime popup window still shown when TextInput has no focus for macos
---------
Co-authored-by: jasonqiu <jasonqiuchen@outlook.com>
Replace broad cx.load_all_script_resources() calls with a targeted cx.load_script_resource(handle) to only request the specific resource needed. Refactor script resource loading (platform/src/script/res.rs) by extracting load_script_resource_impl(handle, crate_manifests) and exposing load_script_resource(handle); keep load_all_script_resources() by iterating per-handle. Improve wasm handling: resolve web_url per-resource, set explicit error states when missing, and fire async HTTP requests safely. Remove an early call to load_all_script_resources() from web startup. Additional changes: serve precompressed .br files in the local wasm dev server (with COOP/COEP headers when threaded), add wasm-specific font/theme script entries for widgets, and update various callers (draw shaders, widgets, math_view, gltf/view_splat, image) to use the per-handle loader. These changes reduce unnecessary global loads and limit network/file operations to only required resources.
* selection: clipboard delegate, primary selection, mobile UI, handles, accessibility
Level 0: Route clipboard through Makepad instead of arboard. Custom
ClipboardDelegate in havishell forwards set_text/get_text/clear through
Makepad's CopyToClipboard and pending paste state. Fix Wayland serial
constraint by queuing CopyToClipboard when no serial is available.
Level 1: Primary selection (Linux). Add CxOsOp::SetPrimarySelection,
Wayland zwp_primary_selection_device_manager_v1 protocol bindings, X11
PRIMARY atom handling. HAVI stores selection text in SharedDocumentSelection
and calls set_primary_selection on change.
Level 2: Mobile clipboard actions UI. Long-press selects word and shows
native clipboard toolbar. TextCopy/TextCut events return selection text.
Level 3: Selection handle API. Add CxOsOp Show/Update/HideSelectionHandles,
Event::SelectionHandleDrag, and HAVI integration for handle drag events.
Platform stubs for all backends.
Level 4: Accessibility plumbing. Add CxOsOp::AccessibilityUpdate with
type-erased Box<dyn Any + Send> payload. HAVI implements
notify_accessibility_tree_update to forward accesskit::TreeUpdate through
Makepad. Platform no-op stubs.
* wayland: handle primary selection data_offer child objects
---------
Co-authored-by: ant <ant@offline.click>
Remove a leftover debug call that set the document title when handling ToWasmRedrawAll in platform/src/os/web/web.rs. This avoids an unnecessary DOM update used only for debugging and cleans up the redraw handler.
Add a wasm32-specific branch in KeyModifiers::is_primary so the primary modifier is treated as true when either `logo` or `control` is set on WebAssembly targets. This preserves expected web behavior where Meta/Command or Control can act as the primary key. Also tighten the non-Apple cfg to exclude wasm32 explicitly to avoid overlapping cfg matches.
Introduce a single-threaded wasm build mode and add defensive runtime handling for missing wasm threading support.
- CLI: add --no-threads flag and WasmConfig.threads to control threaded vs single-threaded builds. Parse and strip wasm-specific options before forwarding build/run args.
- Build: select target features and RUSTFLAGS based on threading; omit atomics/bulk-memory features for single-threaded builds. Adjust generated server instructions to require COOP/COEP only for threaded builds.
- Dev server: conditionally include COOP/COEP headers when serving threaded wasm artifacts.
- JS runtime (platform/src/os/web/web.js): guard audio worklet startup and thread creation on wasm._has_thread_support; make alloc_thread_stack return null with clear console warnings when required exports or alignment are missing; pass allocated thread_info to workers.
These changes enable building and running a single-threaded wasm variant without COOP/COEP server requirements and improve runtime resilience when threading features are unavailable.
Rename AlignEntry::BeginTurtle/EndTurtle to BeginClip/EndClip — these
entries control GPU clip rect stacking, not turtle lifecycle.
Add push_clip_rect/pop_clip_rect to Cx2d: lightweight API for manual
clip rect control without creating a full turtle. The existing
clip_and_shift_align_list pass intersects nested clip rects and writes
draw_clip into draw call instances.
Co-authored-by: ant <ant@offline.click>
Add Texture::set_data_u32(cx, width, height, data) that replaces pixel
data and dimensions in one call. Unlike put_back_vec_u32, this also
updates width/height, making it safe for image sources that change
resolution (animated images, lazy-loaded placeholders).
Co-authored-by: ant <ant@offline.click>
dispatch_studio_msg pushed to screenshot_requests but never
triggered a redraw. The GL readback that captures the screenshot
only runs during the render path, so the request was never
serviced in windowed backends (Wayland, X11, macOS).
Co-authored-by: ant <ant@offline.click>
The Contents.json only had a single universal+platform entry, which is
only recognized by iOS 16+. Add classic per-idiom entries (iphone, ipad,
ios-marketing) so actool compiles both sets into Assets.car. iOS 15
falls back to the idiom-based entries.
Co-authored-by: ant <ant@offline.click>
Move font family registration out of on_custom_apply (script apply time)
and into ensure_fonts_loaded (draw time). This avoids redundant work when
the same FontFamily is applied to hundreds of widgets during a frame.
ensure_fonts_loaded now has a fast path that checks is_font_family_complete()
and returns immediately when all expected members are already registered.
The slow path calls load_all_script_resources() to progress pending loads
before falling back to update_font_definitions().
Other changes:
- FontFamilyDefinition gains expected_member_count to distinguish partial
from complete registrations.
- set_font_family_definition skips cache eviction when the definition or
cached family is already equivalent.
- load_font_family now clones the definition instead of removing it,
allowing re-loads after cache eviction without losing the definition.
- Loader::font_family_definitions and Layouter::loader are now pub(crate)
to support the completeness query from Fonts.
---
Review observations (not yet addressed):
1. STALE FAMILY ON RE-APPLY (medium risk): If a FontFamily is re-applied
with different members but the same object index (same family_id), the
fast path in ensure_fonts_loaded will see the old definition as
"complete" and never call update_font_definitions with the new members.
Fix: compare current member handles against stored definition, or set a
dirty flag in on_custom_apply that forces one refresh.
2. UNNECESSARY LAYOUT CACHE FLUSH (medium risk): Layouter::set_font_family_definition
unconditionally clears cached_params and cached_results even when
Loader::set_font_family_definition short-circuits as unchanged. During
partial-load states ensure_fonts_loaded may call update repeatedly with
identical partial definitions, flushing the text layout cache each time.
Fix: propagate a changed bool from Loader and only clear when true.
3. COMPLETENESS IGNORES CACHE-ONLY STATE (low risk): is_font_family_complete
only checks font_family_definitions, not font_family_cache. If a family
was already loaded into cache and its definition consumed, completeness
returns false. The new early-return in set_font_family_definition for
cache-match mitigates this in practice but the invariant is fragile.
Fix: also check font_family_cache in is_font_family_complete, or ensure
definitions are always retained (which this diff partly does by switching
from remove to get+clone in load_font_family).
Co-authored-by: ant <ant@offline.click>
* Add support for Video widget on WASM
* Add support for Video widget on Linux
* Linux video: GStreamer appsink pipeline, GL texture upload, accurate seeking
* Add support for Video widget on Windows
* Harden cross-platform video playback and error handling
* Cleanup video playback logs
* Restore wasm builds
* Restore linux builds
* Restore linux builds
* Remove unused import
file_resource and crate_resource now check if a resource with the same
abs_path already exists before creating a new entry. Returns the existing
handle instead of reading the same file multiple times.
Before: 54 resource entries, same font files loaded up to 15 times each
(427MB of duplicate heap data). After: ~10 unique entries, each file
loaded once (~50MB).
Co-authored-by: ant <ant@offline.click>
Android defaults to target/android/, apple to target/apple/.
Prevents cross-platform builds from invalidating each other's caches.
Co-authored-by: ant <ant@offline.click>
Fix EGL context initialization on Android to properly wire up the GL
render bridge display/context/surface.
Add GL state reset in restore_gl_context on Linux/Android to prevent
state leakage from external GL consumers (e.g. Servo) back into the
Makepad render pipeline.
Co-authored-by: ant <ant@offline.click>
Refactor app icon handling into a unified app_icon module that replaces
the old window_icon.rs. Build-time icon generation produces platform-
native formats (ICO with multiple sizes for Windows, ICNS for macOS,
multi-resolution PNGs for Linux/Wayland/X11).
Add `cargo makepad desktop` subcommand for desktop packaging with
automatic icon detection from MAKEPAD_APP_ICON_PATH env var, or from
Cargo package metadata.
Resolve binary names from [[bin]] targets in Cargo.toml so .app bundles,
.exe outputs, and APK labels use the correct name instead of defaulting
to the package name.
Use llvm-rc for Windows .res generation (cross-compilation compatible)
with absolute link paths for reliable resource embedding.
Co-authored-by: ant <ant@offline.click>
When script_mod! is used with 'let app = startup() do ...' but the
block omits the final 'app' expression, the module returns nil. This
silently creates an App with an empty WidgetRef -- no window, no UI,
no error. The app runs indefinitely doing nothing.
Panic with an actionable message instead of silently succeeding.
Co-authored-by: ant <ant@offline.click>
* Fix draw_svg to support a rotated Icon. Add `IconRotated` widget.
Fix icon resource paths in `splash` example
* Expose geometry in draw_svg to make icon/svg rotation more efficient
simplifies the code too
The rebase squash into 26318769 used the early draft of window_icon.rs,
dropping the OnceLock-based global setter added in the fixup commit.
Restore from pre-rebase commit 83bf5d62:
- add static GLOBAL_ICON: OnceLock<WindowIcon>
- add pub fn set_window_icon(icon: WindowIcon)
- refactor default_window_icon() to check global override first
- re-export set_window_icon from platform lib.rs
Co-authored-by: ant <ant@offline.click>
* Remove left margin from window caption label
Delete the hardcoded Inset{left: 100} margin on the caption Label in widgets/src/window.rs so the label can be centered by the parent layout. This cleans up alignment and removes an unnecessary offset in the window header.
* Use window title in caption; format button click
Apply the configured window title to the window caption and make a small UI code cleanup.
- examples/splash: set window.title to "Splash Example" and reformat the tooltip button click check to a multiline expression for readability.
- widgets/src/window.rs: import label::* and update ensure_initialized to copy cx.windows[window_id].create_title into the caption_label when non-empty so the window chrome shows the configured title.
The CalloutTooltip is a fancier wrapper atop Tooltip that allows
the user to display a tooltip with a callout triangle that points
at a particular widget, making it clearer what the tooltip
corresponds to. It also supports a custom text color, background color,
positioning suggestion (top, right, left, bottom), full text wrapping,
and dynamically resize and re-orient itself to fit within the app screen.
New GlRenderBridge API lets external code (e.g. servo in havi) render
via GL into a makepad-displayable texture with zero-copy.
Platform backends:
- Linux/Android: wraps existing EGL context (EglRenderBridge)
- Windows: ANGLE EGL on D3D11 device via mozangle (AngleRenderBridge)
- macOS: standalone CGL 3.2 Core bridged to Metal via IOSurface (CglRenderBridge)
Cx methods: create_gl_render_bridge, create_gl_render_bridge_texture,
restore_gl_context -- all platform-dispatched via cfg.
Co-authored-by: ant <ant@offline.click>
Wayland wl_pointer::Axis values are defined as motion-event vectors:
positive vertical = downward on screen. This means positive values
represent content sliding down under the pointer, i.e. the viewport
moving UP.
Makepad's internal scroll convention is positive = viewport moves DOWN.
This matches X11 (button 4/up maps to negative, button 5/down maps to
positive) and macOS (which negates scrollingDeltaY for the same reason).
The web backend also follows this via browser deltaY semantics.
The Wayland backend was passing axis values through without negation,
producing reversed scroll on all Wayland sessions. Negate at the
accumulator stage, consistent with how winit handles the same mismatch
(explicit comment: "Wayland sign convention is the inverse of winit").
The ignored AxisRelativeDirection event is unrelated -- it is a hint
telling clients whether the compositor applied natural-scrolling
inversion to the axis values, intended for widgets like volume sliders
that should track physical finger direction. The axis values themselves
already have natural scrolling applied by the compositor.
Co-authored-by: ant <ant@offline.click>
Add WindowIcon and WindowIconBuffer types to platform/src/window.rs with
RGBA8 pixel buffer data. Embed a default 64x64 Makepad icon generated at
runtime (dark rounded rect with white M glyph).
Platform backends:
- Windows: CreateIcon from RGBA->BGRA data, set on WNDCLASSEXW.hIcon
- X11: XChangeProperty with _NET_WM_ICON atom (RGBA->ARGB u32 array)
- macOS: NSBitmapImageRep + NSImage, setApplicationIconImage on NSApp
- Wayland: vendor xdg-toplevel-icon-v1 protocol, regenerate bindings,
bind wl_shm + xdg_toplevel_icon_manager_v1 globals, create shm buffer
icon when compositor supports it, silent fallback to app_id otherwise.
Use configurable create_app_id field (default "Makepad").
Codegen: extend tools/wayland_codegen to generate xdg toplevel_icon
bindings into libs/linux/wayland-protocols/src/xdg.rs.
Co-authored-by: ant <ant@offline.click>
* platform: unified StudioToApp dispatch and control channel
Shared dispatch_studio_msg() in cx_shared.rs handles all common
StudioToApp variants (input, clipboard, screenshot, widget dump, kill).
Each stdin backend resolves window_id for mouse events, then delegates.
Control channel (web_socket.rs) enables StudioToApp dispatch in windowed
apps. Event loops poll it alongside their native event sources.
WindowGeomChange events are emitted on stdin geometry updates.
* fix RunView Y-flip on Linux: flip in shader instead of CPU readback
---------
Co-authored-by: ant <ant@offline.click>
accept_host_endpoint() has a 10s timeout. Calling it at spawn time
fails for any build that takes longer to compile. Defer to when
"Running " appears in stderr, which is when the binary actually
starts and connects to the aux channel.
build_server: match+return instead of expect on spawn failure.
Co-authored-by: ant <ant@offline.click>
* Add macOS video playback support using AVPlayer + CVMetalTextureCache
Extends the Video widget from Android-only to also support macOS with
hardware-accelerated, zero-copy video rendering through Metal. Uses
AVPlayer for decoding and CVMetalTextureCache to map decoded frames
directly to MTLTextures without CPU copies.
Key changes:
- New AppleVideoPlayer module (AVPlayer + CVPixelBuffer → MTLTexture pipeline)
- FFI bindings for CVMetalTextureCache, CMTime, and related Apple APIs
- Video frame polling integrated into macOS paint cycle
- TextureFormat::VideoRGB ungated from Android-only to all platforms
- Video widget made cross-platform (TextureHandleReady wait is Android-only)
- Fixed shader aspect ratio bug (&&→|| for non-positive dimension check)
- Added video demo to uizoo and scratchpad examples
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add iOS video playback support
* Enhance video playback functionality with seek support and current position tracking
Key changes:
- Added `seek_video_playback` operation to allow seeking to specific timestamps.
- Introduced `current_position_ms` field in `VideoTextureUpdatedEvent` for tracking playback position.
- Implemented seeking functionality across iOS, macOS, and Android platforms.
- Updated Video widget to support new controls and indicators for seeking.
* Improve error handling for unsupported texture pixel formats
* Make volume icon drawing use fixed width
* Make play/pause drawing use fixed width
* Remove video from scratchpad
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add **/* glob to Linux NDK unzip to match subdirectories
`*` alone doesn't match `/` on Linux unzip builds with WILD_STOP_AT_DIR,
causing only top-level files to be extracted. Adding `**/*` ensures bin/,
lib64/, sysroot/ subdirectories are included.
* Auto-bundle NDK shared library deps (e.g. libc++_shared.so) into APK
When libmakepad.so has NEEDED entries for shared libraries provided by
the NDK sysroot (like libc++_shared.so from C++ dependencies), those
libraries were not being included in the APK, causing runtime dlopen
failures on device.
Add bundle_ndk_shared_deps() which uses the NDK's llvm-readelf to scan
libmakepad.so for NEEDED entries, then copies any matching .so files
from the NDK sysroot base lib dir into the APK. System libraries
(present in the API-level subdirectory) are excluded since they are
provided by the Android OS at runtime.
The detection is general-purpose and not hardcoded to any specific
library name.
* fix: make SYS_GETTID arch-conditional (186 on x86_64, 178 on aarch64)
The constant was hardcoded to 178 (correct for aarch64 but maps to
query_module on x86_64), causing Android's seccomp filter to kill the
process immediately on x86_64 emulators.
* Upgrade Android NDK from r25 to r28, fix x86_64 seccomp crash
cargo_makepad:
- sdk.rs: NDK version 25.2.9519653 → 28.2.13676358, download URLs r25c → r28b
- sdk.rs: Update NDK_IN extract paths for all platforms (Windows, macOS, Linux)
- sdk.rs: macOS NDK extraction is now host-aware (darwin-aarch64 for Apple Silicon)
- compile.rs: MacosAarch64 uses native darwin-aarch64 prebuilt (no more Rosetta)
- compile.rs: ndk_prebuilt_dir() split for MacosX64 vs MacosAarch64
platform:
- libc_sys.rs: Make SYS_GETTID arch-conditional (186 on x86_64, 178 on aarch64)
Was hardcoded to 178 which maps to query_module on x86_64, causing
Android seccomp to kill the process on x86_64 emulators
- android.rs: Add pub display field on CxOs, pub make_current() on CxAndroidDisplay
for Servo embedding support
* Fix GLSL struct constructor crash on Android GLES drivers
The r28 script-based shader backend (shader_glsl.rs) generates GLSL
struct constructor syntax in the vertex/fragment main() unpack code,
e.g.: vb_geom = QuadVertex(vec2(packed_geometry_0.x, ...));
Some GLES drivers (notably Android emulator ANGLE/SwiftShader) reject
this with 'Structure constructor arguments do not match structure
fields', even though it's valid per the GLES 3.0 spec.
This is a regression from r28. The old r25 shader compiler
(generate_glsl.rs) used VarUnpacker::unpack_var which generated direct
swizzle assignments (vb_geom.xy = packed_geometry_0.xy), never struct
constructors in the unpack path. The r25 branch is not affected.
Fix: generate per-sub-field assignments for struct-typed variables in
the main() geometry/instance/varying unpack code, matching the r25
behavior. Struct constructors in user shader function bodies are
unaffected.
* fix(android): Proper fullscreen support on API 30+ and FullscreenWindow/NormalizeWindow ops
Android platform changes:
- Add CxOsOp::FullscreenWindow handler: sets os.fullscreen flag and calls JNI setFullScreen
- Add CxOsOp::NormalizeWindow handler: clears os.fullscreen and exits fullscreen
MakepadActivity.java:
- onCreate: On API 30+, switch from legacy Theme.NoTitleBar.Fullscreen to
Theme_DeviceDefault_NoActionBar to avoid FLAG_FULLSCREEN conflict
- onCreate: Call setDecorFitsSystemWindows(false) and set
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS before setContentView so SurfaceView
is laid out at y=0 from the start
- Refactor setFullScreen into applyFullScreen with proper WindowInsetsController
API on API 30+ (hide/show statusBars + navigationBars)
- Legacy path (pre-API 30) uses full immersive sticky flags
- ResizingLayout.onApplyWindowInsets: Return WindowInsets.CONSUMED to prevent
system bar insets from offsetting child SurfaceView
* Fix Android touch radius DPI scaling and revert aggressive fullscreen inset handling
- Divide touch.radius by dpi_factor alongside touch.abs for correct touch sizing
- Return insets instead of consuming them in ResizingLayout so child views get proper insets
- Remove premature setDecorFitsSystemWindows/cutout mode override; let apps opt in via FullscreenWindow
* Make DrawImage image_scale/image_pan fields pub
Needed by external crates (e.g. servo makepad_shell) that create custom
widgets using DrawImage and need to flip the Y axis for GL render textures.
---------
Co-authored-by: ant <ant@offline.click>
Three minimal changes to support embedding Servo as a WebView:
1. windowing_backend.rs: Make opengl_cx field pub (was pub(super))
Servo needs EGL display/context/platform handles to create a shared
GL context. egl_platform and egl_platform_display have no EGL query
to retrieve them — they must come from whoever created the display.
Matches Makepad convention (Cx fields are pub, no accessors).
2. opengl.rs: Add Cx::create_gl_render_texture(width, height)
Eagerly allocates a RenderBGRAu8 texture and returns (Texture, gl_id).
Needed because Makepad allocates GL textures lazily during render,
but Servo needs the GL texture ID at init to attach to its FBO.
No existing API for eager allocation (update_render_target and
cx.os.gl() are both pub(crate)).
3. image.rs: Add is_render() Y-flip in Image widget
FBO render targets are Y-up in OpenGL; Makepad is Y-down.
CachedView handles this via sample2d_rt shader, but Image widget
was untested with render textures. set_texture() accepts them but
displays upside-down. Fixes a genuine gap — no existing Makepad
code puts RenderBGRAu8 into an Image widget.
Co-authored-by: ant <ant@offline.click>
Replace occurrences of self.len() - 1 with self.len().saturating_sub(1) in serialization code to avoid underflow when serializing empty slices/maps. Adds a unit test in serde_json to confirm an empty HashMap serializes to "{}". Changes touch libs/micro_serde/src/serde_json.rs and libs/micro_serde/src/serde_ron.rs.
This ensures that if an error occurs in a `cargo makepad` invocation,
the `cargo makepad` binary will actually return an error
(and importantly, an error exit code) instead of returning a success
exit code of `0` in all cases.
This allows CI passes and automated testing to work as normal.
Extra: cleaned up "show help" output
* WIP
* Improvements for cursor control
* Enhance text selection and key event handling for Samsung keyboard compatibility
* Default to multine inputtype in android
* WIP Input configurations
* Enhance iOS text input handling
* Cleanup
* Comment out 'Next' variant across platforms for future implementation.
* Rename IME Config API to match web APIs
* Fix Android emoji deletion by implementing UTF-16 code unit index conversion
* Proper ASCII-only input and improve iOS keyboard handling
* Replace 'is_numeric_only' with 'input_mode'
* Prevent pasting invalid characters
* Cleanup
* Cleanup
* Hide clipboard actions on text change
* Cleanup
* Unify keyboard event types and fix iOS text input regressions
Unified TextInputEvent with new fields for better IME support across platforms:
- Added `composition` field for IME preview ranges (CJKinput)
- Added `full_state_sync` for complete buffer state (Android approach)
- Added `replace_range` for autocorrect/suggestion replacements (iOS approach)
All platforms: Standardized on CharOffset for character position handling
* Prevent text synchronization with the platform during active composition
* Add UITextInputCurrentInputModeDidChangeNotification support
* Add underline for active IME composition in TextInput
* Improve editor action handling for multiline inputs on Android
* Enhance IME composition tracking and clipboard action handling in TextInput
* Simplify IME state handling on Android
* Cleanup IME handling on iOS
* Cleanup
* Cleanup IME handling on iOS
* Improve docs/comments
* Improve docs/comments
* Add floating cursor support for keyboard trackpad in iOS
* Refine IME handling in TextInput to prevent iOS buffer loss during composition updates
* Move UITextInput protocol implementation into its own module
* Move MakepadInputConnection into its own file
* Cleanup
* Improve general IME handling in TextInput. Improve docs and comments
* Refactor text input configuration to separate soft keyboard settings for mobile platforms.
The `pub Math` DSL registration in math_widget shadows the shader
built-in `Math` namespace (defined in draw/src/shader/std.rs), which
provides `Math::random_2d` and `Math::rotate_2d`. This causes runtime
shader compilation errors in every widget that uses `Math::random_2d`
for color dithering (Slider, Button, Label, CheckBox, Icon, TextInput,
Tab, LinkLabel, PopupMenu, DropDown, RadioButton, View).
Rename the widget DSL name from `Math` to `MathView`, consistent with
Makepad naming conventions (CodeView, CircleView, RoundedView, etc.).
The Rust struct remains `Math` — only the DSL registration name changes.
Downstream users need to update:
`inline_math = <Math> {}` → `inline_math = <MathView> {}`
`display_math = <Math> {}` → `display_math = <MathView> {}`
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This bit of info was already tracked, but it wasn't exposed by the
`FingerMoveEvent` struct.
Doing so will allow widgets to differentiate between a gesture like
long-press then drag (for drag-n-drop) vs. just a regular drag
(for something like finger-based scrolling).
This is useful in TextInput, Dock, and any view that may respond
differently to a drag vs a "select then drag".
- MakepadTextInputView with marked text (composition) for CJK input
- UTF-16 ↔ char index conversion for emoji/Unicode handling
- TextRangeReplaceEvent for autocorrect/autocomplete
* Fix `Uncaught ReferenceError: env is not defined`
at bindgen.js: const imports = __wbg_get_imports(); imports.env = env;
at (index): let wasm = await init({module_or_path: module}, env);
* fix `Uncaught DOMException: WebAssembly.Memory object could not be cloned.`
at web.js: worker.postMessage(this.alloc_thread_stack(args.context_ptr, args.timer));
* add missing rustc flags to enable threading
* allow cargo makepad in linux arm
* debugs
* don't color cargo tree
* remove debug prints
* remove unsupported os handling that didn't exist before
Cx API:
- Implement existing ShowClipboardActions
- Add HideClipboardActions
- Cross-platform API ready for iOS implementation
Android Implementation:
- Native ActionMode integration with floating toolbar (API 23+)
- JNI bindings for showing/hiding menu and handling clipboard actions
- Event system for Copy/Cut/Paste/Select All actions
- Smart menu state management based on selection and clipboard
TextInput Integration:
- Long press selects word and shows menu
- Double tap and long press selects word and shows menu
- Selection preservation when tapping selected text
* Fix button clicks during keyboard dismissal on mobile
When tapping a button while the keyboard is visible on iOS/Android, the keyboard dismisses and shifts the layout mid-press. This caused the event system to treat the finger as no longer "over" the button, preventing the click from registering.
Fixed by treating taps as "over" if the finger didn't move significantly, even if the widget moved underneath due to layout shifts.
* Make is_over conditionals clearer
* Ensure that LongPress (LongClick) on Android properly uses touch slop
Previously, we assumed that multiple touch action events would occur
before a LongClick, but that is not necessarily true.
It is possible to just have one down touch immediately followed by a
LongClick, so we now account for that.
This also fixes the tracking of touch event locations such that
stale values aren't accidentally used when calculating if a finger movement
exceeded the allowable touch slop for considering a touch as a long press.
(Rust would've caught that... thanks Java)
* remove excess log stmt
* Fix modal event handling behavior
Everything now works as expected, *except* for Scroll events that seem to
still be received by views beneath the modal, e.g., an underlying PortalList.
* Enable widgets to block scrolling, except within a certain area
* This is important for Modals to prevent scrolling of background widgets
whilst still allowing the inner `content` view to be scrolled.
* Modals are now forcibly full-screen (or rather, full-window)
in order to properly ensure that scrolling-allowed areas
always stay relevant, as the Modal can no longer be contained
within a non-full-window parent widget/view.
* remove errant log statement
- Fixed missing comma after CopyToClipboard match arm
- Fixed premature closing brace in match statement
- Corrected OpenglCx import path from opengl_x11 to opengl_cx
- Fixed incorrect use of 'self' instead of 'cx' in ShowTextIME, CheckPermission, and RequestPermission handlers
These changes resolve compilation errors that prevented building makepad-studio on Linux X11.
TextInput now self-detects when user taps outside its area and dismisses the keyboard.
The tap event is not consumed.
Fixes keyboard staying open when tapping widgets that don't grab focus.
- Main loop timer is no longer running unconditionally but rather armed/disarmed on demand
- Removed unnecessary repaint_windows() call
- File watcher is now only included for debug builds
* initialize the event loop for wayland backend
Signed-off-by: drindr <dreamchancn@qq.com>
* feat: hidpi wayland and mouse event
- hidpi support with wayland
- mouse event support
- refactor some code
Signed-off-by: drindr <dreamchancn@qq.com>
* wayland xkbcommon for key pressing event
Signed-off-by: drindr <dreamchancn@qq.com>
* add support for IME in with wayland
---------
Signed-off-by: drindr <dreamchancn@qq.com>
Co-authored-by: makepaddev <20386332+makepaddev@users.noreply.github.com>
* Add permission handling for audio input
- Introduced `CheckPermission` and `RequestPermission` operations in `CxOsOp` for managing audio input permissions.
- Implemented permission status checks and requests across iOS, macOS, and Android.
- Added a new `Permission` module to define permission types and statuses.
- Updated event handling to include permission results in the event system.
- Updated manifest files to include necessary permissions for audio input on Android.
* Add audio example project
- Added a new example project for audio processing and permission handling
- Implemented UI components for audio device selection, microphone capture, and playback controls.
- Integrated permission handling for audio input and updated the workspace configuration to include the new example.
* Implement audio permission in Web, catch up Linux and Windows
* Refactor iOS permission handling to avoid ios_app re-entrancy
* Implement iOS mic capture and add sample rate to AudioInfo
* Ensure iOS audio output uses loudspeaker, only when not using external devices
* Enhance audio example by handling sample rates and resampling
* Implement IME composition handling in WasmWebBrowser
This update introduces support for handling Input Method Editor (IME) composition events in the WasmWebBrowser class. The changes include:
- Added event listeners for `compositionstart`, `compositionupdate`, and `compositionend` to manage the composition state and data.
- Skipped normal input events during composition to prevent interference.
- Sent the final IME input result to the WebAssembly module upon composition end, ensuring proper handling of user input.
These enhancements improve the text input experience for users utilizing IME, particularly for languages requiring composition.
* chore: remove log
This allows an app dev/user to create and populate a new widget
within the PageFlip parent widget *before* waiting for it to be drawn.
Previously, the child widgets within PageFlip were either created
upon app load (which is inefficient) or upon draw (which is too late).
* Refactor stack navigation into a proper stack
- Replace show_stack_view_by_id with push/pop/popToRoot methods
- Add proper navigation stack with history tracking
- Fix animation transitions between consecutive views
- Add stack inspection methods (depth, can_pop, current_view)
- Maintain backward compatibility with deprecated methods
- Update action enum to use Push/Pop/PopToRoot variants
* Add support for multiple instances of StackNavigation.
- Introduced full-screen flag necessary to disable full-screen positioning and sliding animations when needed.
* Update old `windows-targets` dep version to reduce lockfile duplicates
All other crates in the Rust ecosystem depend on `windows-targets`
v0.52.*, so this small change vastly reduces the number of duplicate
`windows-*` crate dependencies that cargo must download and track in the lockfile.
Also address minor compiler warnings in AdaptiveView.
* Add `PortalList::is_filling_viewport()`
This is needed in order for an app to be able to take action
upon a portal list's viewport not being completely full.
For example, if you're showing a chat room, you can fetch older events
until there is enough history to fill the entire viewport.
All other crates in the Rust ecosystem depend on `windows-targets`
v0.52.*, so this small change vastly reduces the number of duplicate
`windows-*` crate dependencies that cargo must download and track in the lockfile.
Also address minor compiler warnings in AdaptiveView.
* Added create_window, remove is_created check
* Added Event::Shutdown for window when shutting down
* reduce code redundancy with assignment
Co-authored-by: Kevin Boos <1139460+kevinaboos@users.noreply.github.com>
* allow macos fullscreen
* add window_fullscreen
* window_fullscreen
* linux_x11, support set fullscreen during initalisation
---------
Co-authored-by: Kevin Boos <1139460+kevinaboos@users.noreply.github.com>
* Allow users of `TextInput` to know when a keypress had no effect
This PR makes the `TextInput` widget emit a `TextInputAction::KeyDownUnhandled`
action upon keypresses that don't actually make any change to the widget's selection.
This action is currently only emitted for the Up, Down, Left, and Right arrow keys,
but we can certainly add it to others like Backspace, Delete, etc if desired.
Use case: if you want to enable custom behavior when the user uses arrow keys
to navigate an empty TextInput widget, such as jumping up out of the TextInput focus
to edit the most recent message you just sent in a chat app, then this PR
is necessary. Otherwise there's no way to know whether a given input actually had
any effect on the TextInput's inner cursor/selection state.
* Add the FaceID usage description to iOS Info.plist
Allows Makepad apps to use biometric auth on iOS
* Fix DrawList generation mismatch in overlay cleanup
Prevents "Drawlist id generation wrong index" errors when AdaptiveView
switches between variants by using checked_index() instead of direct
indexing to safely handle recycled DrawList IDs in overlay.end().
* Dispatch WindowGeomChange on web upon CreateWindow
* Move display context updates to window and improve default selector in AdaptiveView
* Add `Dock::replace_tab()`: change the inner widget content of a tab
without having to remove and recreate the actual tab itself.
* Fix StackNavigation to forward non-visibility events to all subviews
* Allow saving/restoring the state of the `TextInput` widget
This redoes the changes introduced in #533, but modified for the new version of TextInput
* Add KeyModifiers parameter to `TextInputAction::Returned`
* Fix bug in text centering
* Properly restore the state of the TextInput
* undo version bumps for `makepad-android-state` and `makepad-jni-sys`
---------
Co-authored-by: Eddy Bruel <me@eddybruel.com>
* Allow saving/restoring the state of the `TextInput` widget
This redoes the changes introduced in #533, but modified for the new version of TextInput
* Properly restore the state of the TextInput
- Visual UI programs must be launched and controlled through the Makepad Studio remote protocol.
- Always use release builds for runtime validation, profiling, benchmarks, timing checks, or any performance-sensitive command. Use `--release` unless the user explicitly asks for a debug build.
- Do not use mount observation or runnable discovery from the bridge client. The bridge must not claim mount ownership from Studio desktop.
- Do not launch UI programs with raw `cargo run`, `cargo makepad`, or ad hoc cargo invocation when a runnable item exists.
- Do not use bridge `Cargo` requests to run applications. Only launch apps from runnable items via bridge `RunItem`.
- For UI runnable targets, do not prebuild or precheck the app from the shell before launching it in Studio. Let the Studio `RunItem` build be the single build path so Cargo fingerprints, env vars, target dirs, and flags stay identical.
- Before starting a new UI run for the same target, send `ClearBuild` for the previous build so Studio stops it and removes its run/log/profiler tabs.
- `cargo check` or `cargo build` never counts as UI verification. After changing UI/runtime code, you must clear the old build and start a fresh Studio run before trusting screenshots, widget dumps, or interaction results.
- Do not keep inspecting an older already-running app after code changes. Re-run the target and verify against the new `build_id`.
- Command-line-only tasks (builds, tests, linting, file ops, grep/ripgrep, etc.) can be run directly in the shell.
- Prefer studio remote control for any workflow that needs screenshots, widget queries, clicks, typing, or runtime UI inspection.
- Before using Studio protocol tools (`FindInFiles`, `ReadTextRange`, `WidgetTreeDump`, `WidgetQuery`, `Screenshot`, `Click`, `TypeText`, `Return`), always start one persistent Studio remote bridge process and reuse it for the entire interaction.
- When adding a new example crate, update both the Cargo workspace and `makepad.splash` so Studio exposes the new example as a runnable item.
## Assumptions
- Studio is started manually by the user.
- Studio remote target is `ip:port` only (no `http://`, no `ws://`), normally `127.0.0.1:8001`.
- Use `127.0.0.1:8002` only if Studio reports fallback because `8001` is occupied.
- Keep one persistent studio remote process for the whole interaction.
## Start Studio Remote
- Command:
- `target/release/cargo-makepad studio --studio=127.0.0.1:8001`
- Send newline-delimited JSON requests on stdin.
- Read newline-delimited JSON responses on stdout.
- Protocol shape is raw `ClientToHub` requests on stdin and filtered `HubToClient` responses on stdout.
- Do not send `ObserveMount` from the bridge. It can take `primary` UI ownership for the mount and divert RunView/framebuffer traffic away from Studio desktop.
## Request Protocol (JSON Lines)
- `{"ListBuilds":[]}`
- `{"ClearBuild":{"build_id":[6]}}` stops a running build and immediately clears its Studio UI tabs; use this before rerunning the same app.
- `{"StopBuild":{"build_id":[6]}}` stops/kills a running build but does not clear Studio tabs.
- `BuildCleared` is a Studio frontend cleanup signal routed to the primary UI for the build's mount; bridge clients should not wait for it before starting the next run.
- `RunViewFrame` and the terminal stream are not exposed by the bridge.
- `Screenshot` responses include file metadata (`path`, `width`, `height`) and not inline PNG bytes.
- `WidgetTreeDump` responses include text dump content keyed by `request_id`.
- `FindInFiles` responds as `SearchFileResults` with concise entries (`path`, `line`, `column`, `line_text`) and `done`.
- `FindInFiles` defaults to searching only `.rs`, `.md`, `.toml` files unless `glob` is provided.
- `ReadTextRange` responds as `TextFileRange` with `path`, requested `start_line`/`end_line`, `total_lines`, and `content`.
- Query-scoped responses are lane-filtered by `query_id.client_id`; only this bridge client's query results are emitted.
- Build ids and query ids are `QueryId` tuple structs, so JSON encodes them as one-element arrays like `[6]`.
- `FindInFiles`/`SearchFiles` execution is worker-pooled in backend (not main dispatch thread).
## Recommended Control Flow
1. Start studio remote process once.
2. Determine the target runnable item name locally from the repo or from the user request.
3. Call `ListBuilds` and find any existing build for the same runnable item.
4. Send `ClearBuild` for that old `build_id`; do not wait for an acknowledgment before the next launch.
5. Start the new UI app through `RunItem`, and wait for `BuildStarted` and `AppStarted`.
6. After any code change that affects runtime/UI behavior, repeat steps 3-5 before doing screenshots, widget dumps, clicks, or visual conclusions.
7. For code search, use `FindInFiles` first, then `ReadTextRange` to window exact regions.
8. Use direct shell cargo commands for non-launch tasks such as `check`, `build`, `test`, or `bench`.
9. Use `WidgetQuery` / `WidgetTreeDump` to get click targets.
10. For text input, click field first, then send text, then return.
11. Keep control packets compact (`auto_dump:false` on click/type/return for low latency).
## `RunItem` Launch
- `RunItem` executes a Studio-defined runnable item by name.
- Use the runnable item name shown in Studio, not a Cargo package name.
- `RunItem` does not implicitly replace an older build tab; agents should clear the old build themselves first with `ClearBuild`.
## One-Flow Input Burst
- Send this as one stdin write (multiple JSON lines, no sleeps):
- `Click` (input field center)
- `TypeText`
- `Return`
- Then request `WidgetTreeDump` or `Screenshot` to confirm.
## Coordinates
- Use coordinates from dump as-is.
- `W3` dump uses integer pixel coordinates in the same space expected by `Click`.
- Do not apply extra DPI math in the agent loop.
## Reliability Notes
- `Screenshot` can arrive before visible redraw after rapid input bursts.
- If screenshot looks stale, request a follow-up `WidgetTreeDump`/`Screenshot`.
- If input does nothing:
- Verify `build_id` with `ListBuilds`.
- Refresh dump and retry click on input before typing.
- If request errors with no active websocket:
- app is not connected yet; wait for startup completion and retry.
## CLAUDE.md Body
The following is the current body of CLAUDE.md included verbatim for agent guidance parity.
# Makepad Project Guide
## Important: When Converting Syntax
**Always search for existing usage patterns in the NEW crates (widgets, code_editor, studio) before making syntax changes.** The old `widgets` and `live_design!` syntax is deprecated. When unsure about the correct syntax for something, grep for similar usage in `widgets/src/` to find the correct pattern.
```bash
# Example: find how texture declarations work in new system
grep -r "texture_2d" widgets/src/
```
**Critical: Always use `Name: value` syntax, never `Name = value`.** The old `Key = Value` syntax no longer works. For named widget instances, use `name := Type{...}` syntax.
## Running UI Programs
Use the Studio bridge runnable-item flow instead of launching UI apps directly from the shell:
1. Start the Studio remote bridge once.
2. Determine the runnable item name locally.
3. If an older instance is still running, clear it with `{"ClearBuild":{"build_id":[N]}}` and launch the replacement immediately without waiting for an acknowledgment.
4. Launch it with `{"RunItem":{"mount":"makepad","name":"<runnable-name>"}}`.
5. After editing UI/runtime code, do not inspect the previously running build. Always verify against the newly started build id from step 4.
Do not use `ObserveMount` from the bridge. That call is for mount ownership/subscription and can steal RunView/framebuffer routing away from Studio desktop.
Use direct shell cargo commands only for non-UI tasks such as library checks, tests, and file/search operations. Do not run shell `cargo check`, `cargo build`, or `cargo run` for UI runnable targets that will be launched via Studio.
When those non-UI tasks are used for runtime behavior or performance measurements, prefer their release variants (`cargo run --release`, `cargo test --release`, `cargo build --release`).
## Cargo.toml Setup
```toml
[package]
name = "makepad-example-myapp"
version = "0.1.0"
edition = "2021"
[dependencies]
makepad-widgets = { path = "../../widgets" }
```
## Widgets DSL (script_mod!)
The new DSL uses `script_mod!` macro with runtime script evaluation instead of the old `live_design!` compile-time macros.
### Imports and App Setup
```rust
use makepad_widgets::*;
app_main!(App);
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 here
}
}
}
}
}
impl App {
fn run(vm: &mut ScriptVm) -> Self {
crate::makepad_widgets::script_mod(vm); // Register all widgets
// Platform-specific initialization goes here (e.g., vm.cx().start_stdin_service() for macos)
- **`vec`**: Contains named template items (via `:=` syntax)
This distinction is important when working with `on_after_apply` or inspecting script objects directly.
### Templates in List Widgets (PortalList, FlatList)
In list widgets, named IDs (using `:=`) define **templates** that are stored in the widget's `templates` HashMap. These are NOT regular properties - they go into the script object's vec and are collected via `on_after_apply`.
```rust
// In script_mod! - defining templates for a list
my_list := PortalList {
// Regular properties (go into struct fields)
width: Fill
height: Fill
scroll_bar: mod.widgets.ScrollBar {}
// Templates (named with :=) - stored in templates HashMap, NOT struct fields
**Key distinction**: Regular properties like `scroll_bar: mod.widgets.ScrollBar {}` are applied directly to struct fields. Template definitions like `Item := View {...}` are stored separately for dynamic instantiation.
1. **Missing `#[source]`**: All Script-derived structs need `#[source] source: ScriptObjectRef`
2. **Template scope**: Templates defined inside Dock aren't available outside; use `let` at script level
3. **Uniform vs Instance**: Use `instance()` for per-widget varying colors (like hover states on backgrounds)
4. **Forgot `+:`**: Without `+:`, you replace the entire property instead of merging
5. **Theme access**: Always `theme.color_x`, never `THEME_COLOR_X` or `(theme.color_x)`
6. **Missing widget registration**: Call `crate::makepad_widgets::script_mod(vm)` in `App::run()` before your own `script_mod`. Note: the old `live_design!` system and its crates are archived under `old/`
7. **Draw shader repr**: Custom draw shaders need `#[repr(C)]` for correct memory layout
8. **DefaultNone derive**: Don't use `DefaultNone` derive - use standard `#[derive(Default)]` with `#[default]` attribute on the `None` variant
9. **Script_mod call order**: Widget modules must be registered BEFORE UI modules that use them. Always call `lib.rs::script_mod` before `app_ui::script_mod`
10. **`pub` keyword invalid in script_mod**: Don't use `pub mod.widgets.X = ...`, just use `mod.widgets.X = ...`. Visibility is controlled by the Rust module system, not script_mod.
11. **Syntax for Inset/Align/Walk**: Use constructor syntax - `margin: Inset{left: 10}` not `margin: {left: 10}`, `align: Align{x: 0.5 y: 0.5}` not `align: {x: 0.5, y: 0.5}`
12. **Cursor values**: Use `cursor: MouseCursor.Hand` not `cursor: Hand` or `cursor: @Hand`
13. **Resource paths**: Use `crate_resource("self://path")` not `dep("crate://self/path")`
14. **Texture declarations in shaders**: Use `tex: texture_2d(float)` not `tex: texture2d`
15. **Enums not exposed to script**: Some Rust enums like `PopupMenuPosition::BelowInput` may not be exposed to script. If you get "not found" errors on enum variants, just remove the property and use the default
17. **Shader `mod` vs `modf`**: The Makepad shader language uses `modf(a, b)` for float modulo, NOT `mod(a, b)`. Similarly, use `atan2(y, x)` not `atan(y, x)` for two-argument arctangent. `atan(x)` (single arg) is also available. `fract(x)` works as expected.
16. **Draw shader struct field ordering**: In `#[repr(C)]` draw shader structs that extend another draw shader via `#[deref]`, NEVER place `#[rust]` or other non-instance data AFTER `DrawVars` and the instance fields. The system uses an unsafe pointer trick in `DrawVars::as_slice()` that reads contiguously past the end of `dyn_instances` into the subsequent `#[live]` fields. Any non-instance data between `DrawVars` and the instance fields will corrupt the GPU instance buffer. Put all extra data (like `#[rust]`, `#[live]` non-instance fields such as resource handles, booleans, etc.) BEFORE the `#[deref]` field, and only `#[live]` instance fields (the ones that map to shader inputs) AFTER.
```rust
// CORRECT - non-instance data before deref, instance fields after
#[derive(Script, ScriptHook)]
#[repr(C)]
pub struct MyDrawShader {
#[live] pub svg: Option<ScriptHandleRef>, // non-instance, BEFORE deref
#[rust] my_state: bool, // non-instance, BEFORE deref
#[live] pub tint: Vec4f, // instance field, AFTER deref - OK
}
// WRONG - rust data after instance fields breaks the memory layout
#[derive(Script, ScriptHook)]
#[repr(C)]
pub struct MyDrawShader {
#[deref] pub draw_super: DrawVector,
#[live] pub tint: Vec4f, // instance field
#[rust] my_state: bool, // BAD: sits between tint and the next shader's fields
}
```
18. **Don't put comments or blank lines before the first real code in `script!`/`script_mod!`**: Rust's proc macro token stream strips comments entirely — they produce no tokens. This shifts error column/line info because the span tracking starts from the first actual token. Always start with real code (e.g., `use mod.std.assert`) immediately after the opening brace.
19. **WARNING: Hex colors containing the letter `e` in `script_mod!`**: The Rust tokenizer interprets `e` or `E` in hex color literals as a scientific notation exponent, causing parse errors like `expected at least one digit in exponent`. For example, `#2ecc71` fails because `2e` looks like the start of `2e<exponent>`. **Use the `#x` prefix** to escape this: write `#x2ecc71` instead of `#x2ecc71`. This applies to any hex color where a digit is immediately followed by `e`/`E` (e.g., `#1e1e2e`, `#4466ee`, `#7799ee`, `#bb99ee`). Colors without `e` (like `#ff4444`, `#44cc44`) work fine with plain `#`.
20. **Shader enums**: Prefer `match` on enum values with `_ =>` as the catch-all arm, not `if/else` chains over integer-like values. If enum `match` fails in shader compilation, treat it as a compiler bug: add or extend a `platform/script/test` case and fix the shader compiler path instead of rewriting shader logic to `if/else`.
Rik Arends: @rikarends on twitter, https://fosstodon.org/@rikarends#
Eddy Bruel: @ejpbruel on twitter
Sebastian Michailidis: @SebMichailidis on twitter
Our discord channel for Makepad:
https://discord.gg/adqBRq7Ece
Most recent talk about makepad: https://www.youtube.com/watch?v=rC4FCS-oMpg
# Makepad
## Overview
## Socials
This is the repository for Makepad, a new way to build UIs in Rust for both native and the web.
- Discord: https://discord.gg/adqBRq7Ece
- Rik Arends: https://twitter.com/rikarends
- Eddy Bruel: -
- Sebastian Michailidis: https://bsky.app/profile/okpokpokp.bsky.social
Makepad consists of Makepad Framework and Makepad Studio.
Makepad is an AI-accelerated application development environment for Rust. It combines a high-performance UI runtime, a live-editable design language, and a fast iteration loop so you can build native and web apps with a tight feedback cycle.
Makepad Framework is our UI framework. It consists of multiple crates, but the top level crate is [makepad-widgets](https://crates.io/crates/makepad-widgets). For a further explanation of Makepad Framework, please see the README for that crate.
This repository contains the core engine, widgets, tools, and examples.
Makepad Studio is a prototype of an IDE that we've built using Makepad Framework. It's still under heavy development, but our eventual goal with Makepad Studio is to create an IDE that enables the design of an application to be changed at runtime. The main crate for Makepad Studio is [makepad-studio](https://crates.io/crates/makepad-studio). Please see the README for that crate for more.
## What Makepad Is
Demo links:
- A cross-platform UI runtime for native and web targets.
- A Rust-first framework with a scriptable UI DSL.
- A studio app for running, inspecting, and iterating on examples and projects.
- An AI-accelerated workflow: structure and tooling aimed at making code generation, refactoring, and iteration faster and safer.
Our native builds work on the stable Rust toolchain. However, some of the errors generated by Makepad at runtime (particulary those originating in our DSL) do not contain line information unless you use the nightly Rust toolchain. Moreover, our web builds only work on nightly for now. For this reason, we recommend that you build Makepad using the nightly Rust toolchain.
For the non standard build targets (apple ios, apple tvos, android, wasm) we have a buildtool called 'cargo-makepad' that you need to install.
Install it from the repo:
```cargo install --path=./tools/cargo_makepad```
Or install it from cargo (might be behind the repo)
```cargo install cargo-makepad```
Now this tool can be used to install toolchains per platform needed
Makepad studio allows you to easily build and view the examples, and it uses cargo-makepad internally so be sure to install cargo-makepad as shown above.
```cargo run -p makepad-studio --release```
Or install it from cargo (might be behind the repo)
```cargo install makepad-studio```
If you build the wasm applications, you can open it on:
<http://127.0.0.1:8010>
## Build & Run Commands
[Makepad](https://github.com/makepad/makepad) is a cross-platform UI framework written in Rust.
It is in active development, but is already usable to build quick prototypes and simple (or even complicated UI) applications.
One of the key features of the Makepad is its ability to simply, and quickly, build and run applications on multiple platforms, including MacOS, Windows, Linux, Android, iOS, and WebAssembly.
Here are the current/latest instructions on how to build and run Makepad applications on the different platforms.
## Assumptions
We will assume the following:
Name of application: **`makepad-example-simple`**
It can be changed to any one of the existing example apps in the [*Makepad* examples folder](https://github.com/makepad/makepad//tree/master/examples).
## Build & Run Instructions
Follow step 1 commands below for initial setup of the Makepad build and run environment.
After step 2, you may choose any one or more of the platforms you're interested in building for.
## 1. Setup Makepad
Replace `projects` with your own directory name.
- Rust toolchain (stable works for native).
- For non-standard targets (iOS, tvOS, Android, wasm), install the Makepad build tool:
```bash
cd ~/projects
cargo install --path=./tools/cargo_makepad
```
### Clone the Makepad repository
Then install target toolchains as needed:
```bash
git clone https://github.com/makepad/makepad.git
```
or
```bash
git clone git@github.com:makepad/makepad.git
```
### Change to latest 'rik' branch (Optional)
```bash
cd ~/projects/makepad
git branch rik
```
### Install makepad subcommand for cargo
```bash
cd ~/projects/makepad
cargo install --path ./tools/cargo_makepad
```
### Install platform toolchains
```bash
rustup toolchain install nightly
```
## 2. Go To Examples folder (Optional)
```bash
cd ~/projects/makepad/examples
ls -l
```
All examples in this directory have the application name of `makepad-example-` prefix plus the name of directory.
## 3. MacOS / PC
Running on Desktop is the quickest way to try out an example app.
```bash
cd ~/projects/makepad/examples/simple
cargo run
```
or
```bash
cd ~/projects/makepad
cargo run -p makepad-example-simple
```
And there should be a desktop application window now running (may need to click on the icon on MacOS's Dock to show it)
### Building for the Linux direct target
To build and run for the Linux direct target (which bypasses X11), first install the following dependencies:
and then run the same cargo command with the `MAKEPAD` environment variable set:
```shell
MAKEPAD=linux_direct cargo run -p makepad-example-simple
```
## 4. Android Build
### Install Android toolchain (First time)
```bash
cargo makepad android install-toolchain
```
### Install app on Android device or Android emulator
Open either the Android emulator or connect to a real Android device
use `adb` command to make sure there's a single device connected properly, then install and run as below:
```bash
cd ~/projects/makepad
cargo makepad android run -p makepad-example-simple --release
```
The application will be installed and launch on either the emulator or device.
## 5. iOS Setup & Install
### Install iOS toolchain (First time)
```bash
xcode-select --install
cargo makepad wasm install-toolchain
cargo makepad apple ios install-toolchain
cargo makepad apple tvos install-toolchain
cargo makepad android --abi=all install-toolchain
```
### Install app on Apple devivce or iOS simulator
## Linux Dependencies
### iOS Setup
For iOS, the process is slightly more complicated. The steps involved are:
1. Enable your iPhone's Developer Mode, please see instructions here: [Enable Developer Mode](https://www.delasign.com/blog/how-to-turn-on-developer-mode-on-an-iphone/)
1. Setup an Apple Developer account
1. Setup an empty skeleton project in XCode
1. File -> New -> Project to create a new "App"
1. Set the Product Name as **`makepad-example-simple`** (used in --app later)
1. Set the Organization Identifier to a value of your choice, for this example we will use **`my.test`** (used in --org later)
1. Setup the Project Signing & Capabilities to select the proper team account
1. In XCode, Build/Run this project to install and run the app on the simulator and device
1. Once the simulator and device has the "skeleton" app installed and running properly, then it is ready for Makepad to install its application.
### Makepad Install
We will run the `cargo makepad apple ios` command, similar to Android build above, but there are some 2 to 6 additional parameters that need to be filled in:
**`--org`**
First few parts of the organization identifier. Usually in the form of *com.somecompany* or *org.orgname*, etc.
This is the same value used to setup the initial skeleton app above. For this example:
> `my.test`
**`--app`**
The name of the application or the project. This is the same as the Product Name used to setup the initial skeleton app above. In this case:
> `makepad-example-simple`
### Install app on iOS simulator
Linux build/runtime dependencies are listed in `./tools/linux_deps.sh`:
Use the apt-get command below, or run the script on Ubuntu/WSL2:
For installing on real device, the process is more involved due to possibility of multiple profiles and signing identities and target devices. For this reason, Makepad provides a list command to show all the local provisioning profiles, signing identities and connected devices, which can be used as the value for the subsequent command arguments.
For example, first run the following command:
Makepad Studio is the main entry point for exploring examples and iterating on UI.
```bash
cd ~/projects/makepad
cargo makepad apple list
cargo run -p makepad-studio --release
```
This command will print out the list of all provisioning profiles, signing identities, and device identifiers on the current system. The user has to decide and choose the ones that he/she needs to use for each type.
Once decided, run the folloiwng command and fill in the **unique starting characters** chosen from the output.
If you want a local install (note: may lag the repo):
The application will be installed and launched on either the emulator or real device. (Make sure the device is connected and unlocked)
## Examples
## 6. WASM Build
Run a few representative apps directly from the repo:
Running the Makepad application as a WASM build is as simple as a single command. The sript will automatically generate the necessary index.html and other files and also start a local webserver at port 8010. After running the command below, just open your browser to <http://127.0.0.1:8010/> in order for the app to load and run.
```bash
# Splash (simple animated demo)
cargo run -p makepad-example-splash --release
### Install WASM toolchain (First time)
# 3D rendering (glTF)
cargo run -p makepad-example-gltf --release
# Maps
cargo run -p makepad-example-map --release
```
## Maps And Voice Assets
For built-in maps and voice support, download the assets first:
```bash
./download_map.sh
./download_voice.sh
```
## Run A WASM App
1. Install toolchain:
```bash
cargo makepad wasm install-toolchain
```
### Install app as WASM binary for browsers
2. Run an example:
```bash
cargo makepad wasm run -p makepad-example-simple --release
cargo makepad wasm run -p makepad-example-splash --release
```
### If you need `wasm-bindgen` compatibility
By default, Makepad uses it's own bridge. Web crates outside of Makepad
usually depend on `wasm-bindgen` for web integration so they will fail
at runtime if added to your project.
However, we can build with opt-in `wasm-bindgen` support to solve this
issue.
To enable wasm-bindgen integration you should build like this:
1. Ensure wasm-bindgen CLI is installed.
For smaller shipped wasm output, use the shipping-size optimization pass. It keeps the post-link size reduction behavior and pairs well with the existing `small` profile:
2. Add `wasm-bindgen` dependency to your `Cargo.toml` file.
To split the wasm payloads, add `--split`. Bare `--split` uses an automatic cold-first policy: it first moves defer-safe cold functions into a secondary wasm so startup can begin on the primary first, and if there are no useful cold candidates it falls back to the normal function split. To override the function-splitting threshold directly:
3. Ensure you are using the crate somewhere like in `main.rs`.
```rust
use wasm_bindgen::prelude::*;
```
> **Note:** If you forget this, you may see a compile error like `failed to find __wbindgen_malloc`.
4. Now, run with the `--bindgen` option.
For maximum size reduction, combine `--wasm-opt` (Binaryen IR optimization) and `--brotli` (compression). Install Binaryen for `--wasm-opt` (e.g. `brew install binaryen` or `apt install binaryen`):
```bash
cargo makepad wasm --bindgen run -p makepad-example-hello-widgets --release
- **Input** through named actions only (`ui_left/ui_right/ui_accept`) so keyboard, input
tapes, and gamepad (AgentEye binds the A button) all work through one vocabulary.
This is the complete capability envelope a kids' game engine needs. It is small.
## 2. How the agent remote-operates the game and looks at pictures
Three mechanisms, all file-based (Claude only has `tools/gd` as a shell surface):
| Verb | Mechanism | What the AI gets |
|---|---|---|
| `gd peek` | Drops `.agent/peek_request`; the **AgentEye autoload** in the *live* game polls (250ms), grabs 4 viewport screenshots over ~1.2s via `get_viewport().get_texture().get_image()`, writes player pos/vel/`is_on_floor()` | `.agent/sheet.png` + `state.txt` — sees what the kid sees, zero interruption |
| `gd shot <scene> [frames] [tape]` | Second Godot instance boots `tools/harness.tscn`; harness loads the target scene, **replays a JSON input tape by frame number** (`{"f":30,"press":"ui_accept"}` → `Input.action_press`), prints `[probe]` pos/vel/floor lines every 15 frames; recorded with `--write-movie --fixed-fps 60` (deterministic: same tape ⇒ same frames). Since 2026-07-09 it launches via `open -g` + an unfocusable offscreen window so it never steals the kid's focus | contact sheet + numeric probe log — "the jump feels floaty" becomes a number |
| `gd errors` | Greps the run log for `SCRIPT ERROR` / `ERROR:` | error text |
**The pictures:** `tools/sheet.py` tiles N evenly-spaced frames into one labelled contact
sheet (`.agent/sheet.png`) so the agent reads *one* image showing motion over time instead
of 120 frames.
**Weaknesses inherent to the Godot backend** (what the migration removes):
1. **Separate process.** Applying `.tscn` changes needs a full game restart (state loss,
focus management, pid babysitting, zombie processes). We just spent a day making this
tolerable; in-process it disappears entirely.
2. **Capture needs a real (hidden) window** — Godot's `--headless` crashes with
`--write-movie`. Makepad has a true headless CPU renderer.
3. **Opaque runtime.** The only introspection is print statements the harness happened to
include; errors come from log-grepping.
4. **Two unfamiliar languages** (GDScript + tscn) and a giant API surface the model can
misuse. A curated DSL of ~25 constructs is easier to prompt and to verify.
## 3. Building blocks in makepad (surveyed 2026-07-09)
### 3.1 Splash script VM — the hosting/hot-reload story is already built
- `widgets/src/splash.rs`: the `Splash` widget evaluates DSL **strings** in a dedicated
isolate VM (`cx.alloc_splash_vm_with_network`), with instruction limits
(`with_instruction_limit(200_000, …)`), and re-evaluates **incrementally** via
`eval_with_append_source` parser checkpoints — this is how aichat streams a growing
`runsplash` block into a live widget. Per-isolate `let`/`fn` state persists across events.
- The DSL is a real language: `let`/`fn`, templates, `for`, `if/else`, closures,
`on_click`-style handlers, struct arrays, `promise()/.await()`, HTTP. `splash.md` is the
authoring manual the AI already follows; `examples/splash_preview/` is an offline
generate-and-verify corpus harness (drives the `claude` CLI, evals every generated app,
flags empty widget trees).
- **Gaps for games:** no script-facing frame tick or timers, no keyboard events routed
into isolates, and eval errors are *not* fed back (a broken block renders blank; errors
only reach stderr via `ScriptVm::drain_errors`). All three are core workstreams below.
### 3.2 box3d — the physics core (`libs/box3d`, pure Rust port of Erin Catto's Box3D)
`XrCamera`, ~200 lines) into the GameHost. It draws exactly the corpus art style.
**Later: converge with xr** — adopt `XrNode`/`XrView` for scene+HUD and port xr's physics
from Rapier to box3d (justified independently: box3d is faster on 8/9 scenes and
deterministic; one physics engine in the tree instead of two). That convergence buys
Quest/hands/multiplayer for the *same game scripts* — a kid's game playable in VR — but
it is explicitly not on the critical path.
## 5. The agent harness on makepad (remote-operate, tier by tier)
Same three verbs, better substrate. `tools/ag` (or a `gd`-compatible shim so the prompt
barely changes):
| Verb | Godot today | aigame |
|---|---|---|
| `ag test [frames] [tape]` | hidden Godot instance, --write-movie, probe prints | **headless run of the same GameHost** (`MAKEPAD=headless`): eval `game.splash`, feed the tape into the ActionMap, `world_step` N frames, render PNGs on CPU, emit probe lines (engine reads pos/vel/on_floor directly — no print statements needed). box3d determinism ⇒ bit-exact repeatability, stronger than Godot's fixed-fps movie |
| `ag peek` | file-RPC into live game (AgentEye), viewport screenshots | in-process: the app screenshots its own game pass texture + dumps entity state on request (file trigger kept for CLI compat, or a local socket). Kid keeps playing, same as now |
| `ag errors` | grep run logs | **drain the script VM error queue** — precise parse/runtime errors with line numbers, returned as text. Also auto-attached to the turn when a shadow-eval fails, so the AI often self-corrects *without* running anything |
| pictures | `sheet.py` contact sheet | keep `sheet.py` verbatim (it's engine-agnostic: dir of PNGs → one labelled sheet) |
| tapes | JSON `{"f":N,"press":"ui_accept"}` | same format, actions renamed; probe list = tags |
Bonus unlocked by box3d: `ag test --record` / snapshot scrubbing — the AI can capture a
deterministic recording once and re-probe it at different frames without re-running.
| 1 | First-class SFX bank + synth | **Confirmed hard.** The corpus had invented `squeak` and `roar` beyond the bank we shipped — the bank grew twice in one day. Named-bank + beep/jingle is the right shape; expect the bank to keep growing from corpus usage. |
| 2 | Tags + broadcast/duck-dispatch | **Half-confirmed.**`find`/`tag`/`distance` + script-side state objects covered everything the port needed — cross-actor "messages" (enrage/zap/heal) became plain field writes on shared script objects, which is *better* than Godot's group+has_method bus. A native broadcast API is NOT needed while one script owns all actors. It becomes needed only if games ever split across isolates. **Deferred, deliberately.** |
| 3 | Script raycast | **Dodged, honestly.** Ledge-probe AI was replaced by heightmap lookup (`h_at`) because terrain heights are script data anyway. Fine for this game; a shooter that needs line-of-sight will force `game.raycast`. Keep on the roadmap, don't build speculatively. |
| 4 | Camera-relative helper | **Confirmed, split in two.** The missing primitive wasn't a movement helper — it was `game.cam_yaw()` (the orbit camera's yaw was engine-private). With yaw readable, two lines of cos/sin in script do the rest; documented as a pattern instead of an API. |
| 5 | `attach/detach` (seats/carrying) | **Confirmed emphatically.** Replaced ~100 lines of per-actor teleport-following + collision toggling in GDScript with 2 calls. Vehicles, passengers, and the dismount-pop all fell out. |
| 6 | Mover platform carry | Already in the engine (kinematic floor_id carry); the port didn't stress it (no moving platforms in the 3D sandbox — they're in the 2D game). |
## New findings (not predicted)
1. **No RNG in the script language** — the single most-used Godot facility
(randf/randi everywhere in wander AI) simply didn't exist. Added
`game.rand()`/`game.rand_range(a,b)`, xorshift **seeded per eval**: wander AI
now replays identically under input tapes, which Godot's `randomize()`
corpus could never do. Determinism became a feature of the port.
2. **No noise either** — the terrain wants value noise. A 12-line script
`hash/smooth/noise2` worked fine at 31×31. At the original's 256×256 it
wouldn't (65k columns ≈ 3M+ instructions, and 65k entities would swamp both
the O(statics×movers) physics and the instanced renderer). The Godot game
itself had to weld chunks — scale is an *engine* concern in any engine.
"Start turn-by-turn navigation on the current trip: maneuver banner, follow camera, live progress. Uses real GPS when a fix exists, otherwise a simulated drive.",
r#"{"type":"object","properties":{
"simulate":{"type":"boolean","description":"force the simulated drive even with GPS"}},
"required":[]}"#,
));
out.push(def(
"nav_stop",
"End turn-by-turn navigation (keeps the planned trip).",
r#"{"type":"object","properties":{}}"#,
));
out.push(def(
"images_search",
"Search DuckDuckGo images and show up to 4 thumbnails as cards (sights, places, buildings). Returns the image titles. Rate limited; needs network.",
r#"{"type":"object","properties":{
"query":{"type":"string"}},
"required":["query"]}"#,
));
out.push(def(
"cloud_ask",
"Escalate to the cloud model for world knowledge you don't have: what places are famous for, rankings, reviews, opening-hour customs, history. Costs a network round-trip and is unavailable offline — use only when local tools cannot answer.",
r#"{"type":"object","properties":{
"question":{"type":"string"},
"context":{"type":"string","description":"compact digest of relevant app state (trip, candidates)"}},
"Search places, streets, addresses and categories (e.g. 'supermarkt', 'Zaanse Schans', 'Groningen'). Netherlands has full detail; the rest of Europe has settlements and major places. Returns candidates with coordinates to use in other tools.",
r#"{"type":"object","properties":{
"query":{"type":"string"},
"near_lon":{"type":"number","description":"bias results towards this point; defaults to current map center"},
"Drop ad-hoc markers on the map (search results, sights, candidates). Replaces previous ad-hoc markers; trip stop markers stay. Pass an empty list to clear.",
"Plan a new trip. Waypoints are place names (geocoded via search) or 'lon,lat' pairs; 'here' means the current map view. Replaces any existing trip, draws it on the map, and returns the trip digest with stable stop/leg ids.",
Replace the current single overlay channel with numbered overlay levels, then add two Gauss capture levels:
- Gauss level 0: captures the base window scene before any overlay.
- Gauss level 1: captures the base scene plus overlay level 0.
This lets glass on overlay level 0 blur/lens the app background, while glass on overlay level 1 can blur/lens the already-composited level-0 UI. It also keeps the rule clear: a glass surface samples only completed lower levels, never itself or peers in the same level.
## Current State
The current overlay path is singular:
- `draw/src/cx_2d.rs` stores one `overlay_id`, one `overlay_pass_id`, and `overlay_draw_depth`.
- `draw/src/overlay.rs` owns one overlay root `DrawList`.
- `draw/src/draw_list_2d.rs::begin_overlay_reuse()` appends a widget draw list to that one overlay root.
- `widgets/src/window.rs` owns one `overlay: Overlay`.
- `widgets/src/gauss_view.rs::request_window_gauss()` only succeeds while `cx.is_drawing_overlay()` is true.
- `Window::begin()` chooses either the normal pass or the `gauss_scene` capture pass, then points the one overlay root at the final window pass.
That means all overlay widgets are in one ordering bucket. A glass popup can sample the base app scene, but another glass layer above it cannot sample the popup as part of its blurred backing. Overlapping glass in the same overlay level also cannot produce nested blur because all glass is sampling the same lower snapshot.
Within a level, existing append order and `begin_overlay_last` behavior still apply. Across levels, numeric level decides ordering.
## Public API Shape
Keep existing calls as level-0 compatibility:
```rust
draw_list.begin_overlay_reuse(cx); // means level 0
draw_list.begin_overlay_last(cx); // means level 0, last within level 0
```
Add explicit level APIs:
```rust
draw_list.begin_overlay_level_reuse(cx, level);
draw_list.begin_overlay_level_last(cx, level);
```
Widgets that own overlay draw lists should gain a live property:
```rust
#[live(0)]
overlay_level: u8,
```
Then `Tooltip`, `PopupNotification`, `PopupMenu`, `Modal`, dock ghost overlays, and the new glass layer can choose where they belong without custom render code.
## Render Context Changes
Replace the single overlay target fields in `Cx2d` with a small overlay target table plus current overlay draw state:
5. Build blur chain 1 from `gauss_stack[1].scene`.
6. Draw scene 1 texture to the window pass.
7. Draw overlay level 1+ to the window pass. Level-1 glass samples snapshot 1.
This avoids feedback loops:
- Level 0 never samples itself.
- Level 1 samples base plus completed level 0.
- Same-level glass overlap is still not nested blur, by design.
## Gauss Stack Ownership
`Window` currently owns one `GaussStack`. Change that to two stacks:
```rust
gauss_stacks: [GaussStack; WINDOW_GAUSS_LEVELS],
```
To reduce duplicated code:
- Keep `GaussStack` as the render-texture/mip-chain owner.
- Add a helper that renders one stack from an input draw phase.
- Reuse existing `DrawGaussDownsample`, `DrawGaussUpsample`, and `DrawGaussScene`.
The second stack needs a way to start with the previous level's scene texture. A simple first implementation can draw `gauss_stack[0].scene_texture` into `gauss_stack[1].scene` with `DrawGaussScene`, then draw overlay level 0 over it.
## Widget Migration
Add `overlay_level` to:
- `PopupNotification`
- `Tooltip`
- `PopupMenu`
- `Modal`
- Dock drag ghost overlay
- `mod.widgets.glass.Layer`
Defaults:
- Existing behavior stays level 0.
- Menus/tooltips that must float over glass app chrome can opt into level 1.
- Debug/drag/cursor-like overlays can use level 2 or `begin_overlay_level_last()`.
## Compatibility Rules
- Existing apps keep working because `begin_overlay_reuse()` maps to level 0.
- Existing `GaussRoundedView` keeps working because `request_window_gauss()` maps from current overlay level.
- Non-overlay Gauss requests should still return `None`.
- Existing single-level popup demos should look unchanged.
## Tests And Validation
Add focused tests or demos:
1. Base scene only: no overlay, no Gauss request, same as current render.
2. Level-0 glass over detailed background: samples Gauss level 0.
3. Level-1 glass popup over level-0 glass nav: samples Gauss level 1 and visibly blurs/lenses the nav.
4. Same-level overlapping glass: does not recursively blur peers.
5. `begin_overlay_last()` remains last only within its level.
6. Stale overlay draw lists are removed independently per level.
Runtime validation should use Studio release runs and screenshots, because this is a UI/render-stack change.
## Implementation Phases
1. Add numbered overlay plumbing to `Cx2d`, `DrawList2d`, and `OverlayStack`, keeping old APIs as level-0 wrappers.
2. Migrate `Window` from one `Overlay` to `OverlayStack` with one level enabled first.
3. Add `overlay_level` fields to overlay widgets and verify old demos are unchanged.
4. Refactor Gauss window global state to indexed capture states.
5. Add two `GaussStack`s to `Window` and implement level-1 capture/composite.
6. Update `request_window_gauss()` to choose a snapshot from the current overlay level.
7. Build a small demo with level-0 app chrome and level-1 popup glass to prove nested blur.
## Open Decisions
- Maximum overlay levels: fixed small array versus dynamic vector. A fixed small array is simpler and likely enough initially.
- Whether level 2 should get a third Gauss capture later. For now it should render above level 1 while sampling level-1's snapshot if it uses glass.
- Whether pass compositing should draw full scene textures at each capture level or draw only overlay deltas. Full scene textures are simpler and safer first; delta compositing can be optimized later.
Makepad's foundational alignment property for layout elements.
## align (Align)
Sets the alignment of child elements within their parent container along the horizontal (`x`) and vertical (`y`) axes.
The `align` property is an object with `x` and `y` fields, each accepting a floating-point value between `0.0` and `1.0`, representing the alignment along the respective axis.
**Options**
- **Horizontal Alignment (`align.x`):**
- `0.0`: Left-aligned
- `0.5`: Center-aligned horizontally
- `1.0`: Right-aligned
- **Vertical Alignment (`align.y`):**
- `0.0`: Top-aligned
- `0.5`: Center-aligned vertically
- `1.0`: Bottom-aligned
## Example
```rust
<Label> {
text: "Hello world"
align: { x: 0.0, y: 0.5 } // Positioned on the left and vertically centered
}
```
In this example, the `Label` is aligned to the left edge (`x: 0.0`) of its parent container and vertically centered (`y: 0.5`).
## Notes
- The `align` values are floating-point numbers between `0.0` and `1.0`.
- Values outside the range of `0.0` to `1.0` can be used for alignment beyond the edges of the parent container.
- The default alignment is typically `x: 0.0` and `y: 0.0`, which places the element at the top-left corner of the parent.
The `Animator` defines how visual elements of widgets respond to various user interactions, such as hover and focus states, by controlling animations between different states.
## Attributes
### KeyFrame
A `KeyFrame` represents a specific point in an animation timeline, defining the target value at a given time with an optional easing function.
- **ease** ([Ease](#ease) = Ease::Linear): The easing function used to interpolate between the previous keyframe and this keyframe.
- **time** (f64 = 1.0): The normalized time for this keyframe in the timeline, ranging from `0.0` to `1.0`.
- **value** (LiveValue = LiveValue::None): The target value at this keyframe.
### Play
Defines the playback mode of an animation.
- **duration** (f64): Duration of the animation in seconds.
- **end** (f64): The end time for looping animations. It defines the point at which the animation loops back or reverses, typically ranging from `0.0` to `1.0`.
#### Modes
- **Forward**: Plays the animation forward from start to end once, then stops.
- `{ duration: f64 }`: Duration in seconds.
- **Snap**: Instantly changes to the final state without any interpolated animation.
- **Reverse**: Plays the animation backward from end to start once, then stops.
- `{ duration: f64, end: f64 }`: Duration in seconds, end time (usually `1.0`).
- **Loop**: Continuously loops the animation from start to `end`.
- `{ duration: f64, end: f64 }`: Duration of one loop cycle, end time where the loop restarts.
- **ReverseLoop**: Continuously loops the animation in reverse from `end` to start.
- `{ duration: f64, end: f64 }`: Duration of one loop cycle, end time where the loop restarts.
- **BounceLoop**: Continuously plays the animation forward and backward between start and `end`, creating a ping-pong effect.
- `{ duration: f64, end: f64 }`: Duration of one full forward and backward cycle, end time where the bounce reverses.
## Types
### Ease
Defines the easing functions available for animations. Easing functions determine how the animation progresses over time, affecting the acceleration and deceleration of the animation.
- **Linear**: Animates at a constant speed.
- **None**: No animation; the value changes instantly.
- **Constant(f64 = 1.0)**: Maintains a constant value during the animation.
- **InQuad**: Eases in with a quadratic (x²) curve, starting slow and accelerating towards the end.
- **OutQuad**: Eases out with a quadratic (x²) curve, starting fast and decelerating towards the end.
- **InOutQuad**: Combines easing in and out with a quadratic curve.
- **InCubic**: Eases in with a cubic (x³) curve.
- **OutCubic**: Eases out with a cubic (x³) curve.
- **InOutCubic**: Combines easing in and out with a cubic curve.
- **InQuart**: Eases in with a quartic (x⁴) curve.
- **OutQuart**: Eases out with a quartic (x⁴) curve.
- **InOutQuart**: Combines easing in and out with a quartic curve.
- **InQuint**: Eases in with a quintic (x⁵) curve.
- **OutQuint**: Eases out with a quintic (x⁵) curve.
- **InOutQuint**: Combines easing in and out with a quintic curve.
- **InSine**: Eases in with a sinusoidal curve.
- **OutSine**: Eases out with a sinusoidal curve.
- **InOutSine**: Combines easing in and out with a sinusoidal curve.
- **InExp**: Eases in with an exponential curve.
- **OutExp**: Eases out with an exponential curve.
- **InOutExp**: Combines easing in and out with an exponential curve.
- **InCirc**: Eases in with a circular curve.
- **OutCirc**: Eases out with a circular curve.
- **InOutCirc**: Combines easing in and out with a circular curve.
- **InElastic**: Eases in with an elastic curve with a bounce at the start.
- **OutElastic**: Eases out with an elastic curve with a bounce at the end.
- **InOutElastic**: Combines easing in and out with an elastic curve.
- **InBack**: Eases in with a "backward" overshoot effect before moving forward.
- **OutBack**: Eases out with a "backward" overshoot effect after moving forward.
- **InOutBack**: Combines easing in and out with a "backward" overshoot effect.
- **InBounce**: Eases in with a bounce effect.
- **OutBounce**: Eases out with a bounce effect.
- **InOutBounce**: Combines easing in and out with a bounce effect.
- **ExpDecay**: Uses an exponential decay curve, creating a natural slow-down effect.
In this example, a custom `Button` component is defined with various states (`hover`, `pressed`, etc.) and animations when transitioning between these states. The `animator` block specifies how properties like `draw_bg`, `draw_icon`, and `draw_text` should animate using keyframes and easing functions defined in `KeyFrame` and `Ease`. The `Play` modes dictate how the animations play out during these transitions.
`DrawColor` is a drawing component that fills an area with a solid color. It is a basic drawable element that can be customized or extended by overriding its shader.
## Properties
### color (Vec4)
Controls the fill color of elements. This property can be overridden using a custom shader.
## color (Vec4)
Specifies the RGBA color value to be displayed.
## draw_super ([DrawQuad](DrawQuad.md))
An optional `DrawShader` that allows for more complex designs.
## Example
```rust
<View> {
show_bg: true, // Enables the display of a background
draw_bg: {
color: #f00 // Sets the background color to red
},
// LAYOUT PROPERTIES
width: 200.0, // Sets the width to 200 units
height: 200.0, // Sets the height to 200 units
flow: Down, // Sets the layout flow direction to downward
}
```
In this example, a `View` is created with the background enabled (`show_bg: true`). The background is drawn using `DrawColor` by specifying `draw_bg` with the `color` property set to red (`#f00`). The view has dimensions of `200` units in width and height, and its layout flow is set to `Down`, meaning child elements would stack vertically.
The `DrawIcon` component is used to draw vector icons in Makepad, supporting rendering of SVG files and inline SVG data. It allows for various customizations such as scaling, coloring, brightness adjustment, and more.
## Properties
### brightness (`f32`, default `1.0`)
Controls the brightness of the icon. A higher value increases the brightness, while a lower value decreases it.
### curve (`f32`, default `0.6`)
Adjusts the rendering curve of the icon, affecting the sharpness and contrast of edges.
### linearize (`f32`, default `0.5`)
Determines the level of linearization applied to the icon rendering, which can help smooth out details.
### color (`Vec4`)
Specifies the tint color of the icon using an RGBA color vector. This allows you to change the color of the icon to match your application's theme.
### scale (`f64`, default `1.0`)
A uniform scale factor that maintains the original aspect ratio of the icon. Increasing the scale will make the icon larger, while decreasing it will make it smaller.
### svg_file (`LiveDependency`)
Specifies the path to an SVG vector file. This should be a `LiveDependency` pointing to the desired SVG file to render.
### svg_path (`ArcStringMut`)
Allows for inline embedding of SVG data. This is useful if you need to include SVG content directly without referencing an external file.
### translate (`DVec2`)
Specifies a translation vector to move the graphic. This can be used to adjust the position of the icon within its container.
## Examples
### Basic
In this example, we draw an icon with a red tint and default brightness.
```rust
draw_icon: {
color: #ff0000, // Red tint
brightness: 1.0, // Default brightness
svg_file: dep("crate://self/resources/icons/icon_image.svg"), // Path to SVG file
}
```
### Advanced
Here, we customize the `DrawIcon` within a `Button`, overriding the `get_color` function to implement state transition animations.
`DrawQuad` is a versatile graphical component used for rendering 2D quads. It is essential for drawing rectangular shapes on the screen and managing their transformation, clipping, and layout within the user interface.
## geometry (`GeometryQuad2D`)
Specifies the geometric properties of the quad, including its size and shape. This defines how the quad is rendered in terms of its dimensions and form.
## Example
```rust
<Button> {
// Allows instantiation of custom-styled elements, e.g., <MyButton> {}.
// BUTTON SPECIFIC PROPERTIES
draw_bg: { // Shader object responsible for drawing the background.
fn get_color(self) -> vec4 { // Overrides the shader's fill method.
return mix( // Implements state transition animations.
mix(
self.color,
mix(self.color, #f, 0.5),
self.hover
),
self.color_pressed,
self.pressed
)
}
},
text: "I can be clicked", // Text label displayed on the button.
// LAYOUT PROPERTIES
height: Fit,
width: Fit,
}
```
In this advanced example, the `<Button>` component customizes the `draw_bg` shader object to create interactive animations based on the button's state, such as `hover` and `pressed`. This allows for dynamic visual feedback, enhancing the user experience.
`DrawScrollShadow` is a graphical component in the Makepad Rust UI framework used to render shadow effects along the edges of scrollable areas. It enhances visual feedback by drawing shadows at the boundaries of scrollable containers, simulating depth and indicating scrollable content. This is particularly useful for highlighting content boundaries when scrolling horizontally or vertically.
## Attributes
### [DrawQuad](DrawQuad.md)
Inherits from `DrawQuad`, leveraging its core rendering functionality for drawing 2D quadrilaterals, including vertex transformations, clipping, and instance-based rendering.
### `shadow_size` (f32)
Specifies the thickness of the shadow in logical pixels. It controls how prominent the shadow appears at the edges of the scrollable content.
### `shadow_is_top` (f32)
A flag that indicates the orientation of the shadow. A value of `0.0` means the shadow is drawn on the left or bottom edge, while a value of `1.0` means the shadow is drawn on the top or right edge. This attribute is dynamically adjusted based on the scrolling direction during rendering.
### `scroll` (f32)
Represents the current scroll offset of the container along the relevant axis (horizontal or vertical). This value is used to calculate the shadow's position relative to the scrolling content.
## Notes
- Ensure that the `DrawScrollShadow` component is integrated within a scrollable container to function correctly.
- The shadows automatically adjust based on the scroll positions and the dimensions of the scrollable area.
Controls the layout of child elements within a container by specifying the direction in which child elements are placed and how they behave when the container's dimensions are constrained.
## Flow Types
### Down
A vertical layout where children are placed one below the other from top to bottom.
#### Description
In `Down` flow, each child element is positioned below the previous one along the y-axis. The width of each child can be controlled separately, and the container's height grows to accommodate its children unless specified otherwise.
#### Example
```rust
<View> {
width: Fill,
height: Fill,
flow: Down,
spacing: 5.0,
<Label> { text: "First Item" }
<Label> { text: "Second Item" }
<Label> { text: "Third Item" }
}
```
In this example, three `Label` elements are stacked vertically with a spacing of 5 units between them.
### Right
A horizontal layout where children are placed next to each other from left to right.
#### Description
In `Right` flow, child elements are positioned side by side along the x-axis. The height of each child can be controlled independently, and the container's width increases to accommodate the children unless specified otherwise.
#### Example
```rust
<View> {
width: Fill,
height: Fixed(50.0),
flow: Right,
spacing: 10.0,
<Button> { label: "Option A" }
<Button> { label: "Option B" }
<Button> { label: "Option C" }
}
```
This will display three buttons in a horizontal row with 10 units of spacing between them.
### Overlay
A layout where children are stacked on top of each other, overlaying in the z-order from back to front, meaning later elements are drawn over earlier ones.
#### Description
In `Overlay` flow, all child elements occupy the same position in the container, effectively superimposing them. This is useful for creating composite views where elements need to overlap.
In this example, an `Image` is used as a background, an `Icon` is placed at a specific position, and a `Label` is overlaid on top, all within the same container.
### RightWrap
A horizontal layout where children are placed next to each other from left to right, wrapping to the next line when the maximum width is reached.
#### Description
In `RightWrap` flow, child elements are arranged horizontally until they exceed the container's maximum width, at which point they wrap to a new line starting from the left. This flow type is useful for layouts that need to adapt to varying widths, like responsive grids.
**Note:** `Flow::RightWrap` does not support child elements with `width: Fill`; child elements should have fixed or fit widths.
In this example, buttons are arranged in a horizontal row until they exceed the container's width of 300 units, after which they wrap to the next line.
## Additional Notes
- The `flow` property is defined in the `Layout` struct and determines the primary direction of the layout.
- Spacing between child elements can be adjusted using the `spacing` property for horizontal spacing and `line_spacing` for vertical spacing in wrapped layouts.
- `Flow::RightWrap` currently does not support child elements with `width: Fill`. Ensure child elements have a fixed or fit width when using this flow type.
Determines how an image is fitted within its container, controlling aspect ratio preservation and sizing behavior.

## Variants
### Size
Displays the image at its original size based on its intrinsic dimensions, adjusted by any specified scaling factors. The image maintains its aspect ratio and does not stretch to fill the container.
### Stretch
Stretches the image to fill the entire container, ignoring its original aspect ratio. Both width and height are scaled independently to match the container's dimensions.
### Horizontal
Scales the image to fit the width of the container while maintaining its aspect ratio. The height is adjusted proportionally based on the image's aspect ratio.
### Vertical
Scales the image to fit the height of the container while maintaining its aspect ratio. The width is adjusted proportionally based on the image's aspect ratio.
### Smallest
Scales the image to the smallest possible size that fits entirely within the container while maintaining its aspect ratio. Both width and height are adjusted to ensure the image does not overflow the container.
### Biggest
Scales the image to the largest possible size that covers the entire container while maintaining its aspect ratio. Portions of the image may overflow the container if the aspect ratios do not match.
## Example
```rust
// Display an image using different fit strategies
<Image> {
// Set the fitting strategy
fit: Stretch,
// Minimum dimensions in device-independent pixels
Defines different sizing modes for widgets in the Makepad UI framework.
```rust
pub enum Size {
Fill,
Fixed(f64),
Fit,
All,
}
```
## Variants
### Fill
Expands the element to fill the available space within the parent container, respecting the parent's constraints, padding, and margins.
### Fixed(f64)
Sets the element's size to an absolute value specified by the `f64` parameter.
*Example:* `Fixed(200.0)` sets the size to 200 units.
### Fit
Sizes the element based on the combined dimensions of its children. The element adjusts to tightly enclose its content.
### All
Makes the element take up the full size specified by the parent container, ignoring any padding, margins, or constraints. Unlike `Fill`, which fills the available space considering constraints, `All` stretches the element to match the exact dimensions of the parent container.
## Example
```rust
// A view that adjusts its height to fit its children and fills the available width.
<View> {
flow: Down, // Sets the layout flow direction to vertical.
height: Fit, // The view's height adjusts to fit its children.
width: Fill, // The view's width fills the available space in the parent container.
margin: { // Adds spacing around the view.
top: 10.0,
right: 5.0,
bottom: 10.0,
left: 5.0,
},
// ... child elements ...
}
```
In this example, the `<View>`:
- Uses `flow: Down` to stack its child elements vertically.
- Sets `height: Fit` so its height adjusts based on its children's heights.
- Sets `width: Fill` to occupy all available horizontal space within its parent.
- Applies margins to add spacing outside the view's borders.
`TextFlow` is a versatile widget in the Makepad UI framework that facilitates rich text rendering with various formatting options, including bold, italic, underline, strikethrough, inline code, and block quotes. It supports the rendering of text blocks with customizable layouts and decorations, enabling developers to create complex and visually appealing text interfaces.
## Types
### FlowBlockType
Defines the different types of blocks that can be rendered within the `TextFlow` widget.
#### Code
Highlights text as code, typically rendered in a monospace font within a block that has a distinct background to differentiate it from regular text.
#### InlineCode
Highlights text as inline code, rendered with a monospace font and a subtle backdrop, allowing the code to seamlessly integrate within the flow of regular text.
#### Quote
Formats text as a quote, usually by indenting it and applying specific background and foreground colors to distinguish it from the surrounding content.
#### Sep
Represents a separator block, such as a horizontal line or another visual divider, to separate different sections or types of content within the text flow.
#### Strikethrough
Applies a strikethrough decoration to text, commonly used to indicate deleted or deprecated content.
#### Underline
Applies an underline decoration to text, emphasizing the content without altering its appearance significantly.
## Attributes
### DrawFlowBlock
Represents a drawable block within the `TextFlow` widget. This struct handles the rendering of individual block types (e.g., code, quote, separators) within the text flow.
Determines the type of block being rendered. This affects the styling and layout applied to the block.
#### code_color (Vec4)
Specifies the color used to render both code blocks and inline code, ensuring consistent theming across different code segments.
#### draw_super ([DrawQuad](DrawQuad.md))
Inherits properties from the `DrawQuad` component, enabling the rendering of quads behind the text or block content to serve as backgrounds or borders.
#### line_color (Vec4)
Defines the color used for rendering underlines and strikethroughs, allowing for customizable text decorations.
#### quote_bg_color (Vec4)
Sets the background color for quote blocks, providing visual separation from the main text and enhancing readability.
#### quote_fg_color (Vec4)
Specifies the foreground color of text within quote blocks, ensuring that quoted text stands out appropriately against its background.
#### sep_color (Vec4)
Determines the color used for rendering separators, allowing them to blend or contrast with the surrounding content based on design requirements.
### TextFlow
The primary widget struct that manages the rendering of text and text blocks with various decorations and layouts.
#### code_layout ([Layout](Layout.md))
Defines the layout properties specifically for code blocks within the `TextFlow`, controlling aspects like padding, margins, and alignment.
#### code_walk ([Walk](Walk.md))
Specifies the flow behavior of code blocks within the layout, determining how they wrap and align relative to other content.
`TextStyle` is a configuration structure that defines various properties used to control the appearance of text. These properties include font, size, spacing, and other stylistic elements. This structure is essential for customizing how text is displayed within a widget, allowing for flexible and rich text formatting options.
## Attributes
### brightness (f32 = 1.0)
Controls the overall brightness of the rendered text. Higher values make the text brighter, while lower values make it darker.
### curve (f32 = 0.5)
Adjusts the curve of the text's anti-aliasing. A higher curve value increases the sharpness at the edges of glyphs, whereas lower values soften them.
### font ([Font](Font.md))
Specifies the font used to render the text. This can be set to a specific font family or font style, allowing for customization of text appearance.
### font_size (f64 = 9.0)
Determines the size of the text in points. Larger values increase the size of the text, and smaller values reduce it.
### height_factor (f64 = 1.3)
Defines the height factor applied to the text. This scales the vertical space occupied by each line of text, often used to adjust spacing relative to the font size.
### line_spacing (f64 = 1.4)
Specifies the amount of space between lines of text. A higher value increases the gap between lines, while a lower value reduces it.
### top_drop (f64 = 1.1)
Controls the vertical adjustment of the text relative to its baseline. It is typically used to fine-tune the position of text, especially when rendering at different sizes or with varying font styles.
// Additional properties can be added here as needed
}
```
*Example Explanation:*
This example demonstrates how to configure a `TextInput` widget with customized text styling. The `text_style` block specifies the font path, size, line spacing, top drop, and height factor to control the appearance of the text label within the input field.
Makepad’s foundational layout properties for the inner properties of elements.
## abs_pos (`Option<DVec2>`)
An optional absolute position for the element. When set, the element is positioned at the specified coordinates, bypassing the normal layout flow. Use sparingly, as it can disrupt responsive layouts.
```rust
// Positions the element at (100.0, 50.0)
abs_pos: Some(dvec2(100.0, 50.0))
```
## height ([Size](Size.md))
Determines the height of elements.
**Options**
- `Size::Fixed(f64)` - An absolute height specified in pixels.
- `Size::Fill` - Expands the element to fill the remaining height of its parent container.
- `Size::Fit` - Shrinks the element to fit the height of its child elements.
- `Size::All` - Spans the element to the full height, potentially beyond its parent.
```rust
height: Size::Fixed(200.0) // Sets the height to 200 pixels.
height: Size::Fill // Fills the remaining height of the parent.
height: Size::Fit // Fits the height to its content.
height: Size::All // Spans the full height.
```
## margin (Margin)
Sets the margin area on all four sides of an element.
- *Fields* are properties that define the state and behavior of widgets. They allow you to customize widgets by setting values for size, color, layout, and more.
- *DrawShaders* are shaders used to render the visual appearance of widgets. They define how the widget is drawn on the screen.
- [Layouting](Layouting.md)
- [Expressions](expressions.md) // i.e. for centralizing values
- [Inheritance](inheritance.md) // i.e. for custom widgets. Explain attribute overwriting as a strategy for building custom DSL-only widgets.
- Inheritance allows you to create custom widgets by extending existing ones. You can overwrite attributes to modify behavior and appearance, enabling the creation of complex widgets with minimal code.
- Animation
- [Base Theme](Basetheme.md)
## Unsorted notes
<!-- * commas are optional. useful when not wanting to use line breaks. -->
The `CheckBox` widget provides a control for user input in the form of a checkbox. It allows users to select or deselect options.
## [Layouting](Layouting.md)
Complete layouting feature set support.
## Draw Shaders
### `draw_check` (`DrawCheckBox`)
References the `DrawShader` that determines the appearance of the `CheckBox`.
#### CheckType
The available graphical representations of the checkbox:
- **Check**: A checkmark.
- **Toggle**: A pill-shaped toggle control with an animated circle that signifies the state.
- **None**: No graphic, used for textual checkboxes.
### `draw_icon` (`DrawIcon`)
Displays a monochrome SVG vector next to the checkbox's text or instead of it.
### `draw_text` (`DrawText`)
Allows styling of the checkbox's text with all of the attributes supported by [`DrawText`](DrawText.md), including colors, font, font size, and so on.
## Fields
### bind (`String`)
Binds the checkbox state to a data model, enabling two-way data binding. This synchronizes the `CheckBox` with a variable in the application's data model, so changes in the data model update the checkbox state, and interactions with the checkbox update the data model.
### text (RcStringMut)
The text label of the checkbox.
### icon_walk (`Walk`)
Controls the icon's inner layout properties as supported by [`Walk`](Walk.md).
### label_align (`Align`)
Controls the placement of the label in its parent container according to the attributes supported by [`Align`](Align.md).
### label_walk (`Walk`)
Controls the label's inner layout properties as supported by [`Walk`](Walk.md).
The `Dock` widget provides a dockable layout that supports tabs and drag-and-drop docking. It allows users to organize content in a flexible and customizable interface.
It is the main component for creating dockable layouts. It manages tabs, splitters, and the drag-and-drop behavior necessary for docking panels.
A widget that creates a panel which can expand and contract based on touch gestures, specifically swipe gestures.
## Inherits
- [[View]]
## [Layouting](Layouting.md)
Complete layouting feature set support.
## Fields
### initial_offset (f64)
The initial vertical offset of the panel, specifying the starting position of the panel relative to the body.
## Examples
### Basic
```rust
<ExpandablePanel> {
body = <MyBodyView> {}
panel = <MyPanelView> {}
// LAYOUT PROPERTIES
height: Fill,
width: Fill,
flow: Down
}
```
*Creates an `ExpandablePanel` with custom body and panel views, filling all available space and stacking elements vertically.*
### Typical
```rust
<ExpandablePanel> {
body = <View> {}
panel = <View> {
flow: Down,
width: Fill,
height: Fit,
scroll_handler = <RoundedView> {
width: 40.0,
height: 6.0,
show_bg: true,
draw_bg: {
color: #333,
radius: 2.0
}
}
}
// LAYOUT PROPERTIES
height: Fill, // Expand to fill all available vertical space
width: Fill, // Expand to fill all available horizontal space
flow: Down // Stack children vertically
}
```
*An `ExpandablePanel` where the `panel` contains a `scroll_handler` for user interactions. The panel adjusts its height based on content, and the layout properties ensure it fills the available space.*
### Advanced
```rust
MyExpandablePanel = <ExpandablePanel> {
initial_offset: 400.0,
body = <View> {}
panel = <View> {
flow: Down,
width: Fill,
height: Fit,
show_bg: true,
draw_bg: { color: #FFF },
align: { x: 0.5, y: 0 },
padding: 20.0,
spacing: 10.0,
scroll_handler = <RoundedView> {
width: 40.0,
height: 6.0,
show_bg: true,
draw_bg: {
color: #333,
radius: 2.0
}
}
}
// LAYOUT PROPERTIES
height: Fit,
width: Fit,
margin: 10.0,
padding: 5.0,
flow: Overlay,
spacing: 5.0,
align: { x: 0.5, y: 0.5 },
line_spacing: 1.25
}
<MyExpandablePanel> {}
```
*Defines a custom `MyExpandablePanel` with an increased `initial_offset`, customized `panel` appearance, and specific layout properties for fine-tuned control over positioning and spacing.*
A widget for displaying and managing a list of items. It supports scrolling, flick scrolling, and drag scrolling.
## [Layouting](Layouting.md)
Complete layouting feature set support.
## Fields
### align_top_when_empty (bool = true)
If true content will be displayed at the top when the widget is not fully filled.
### capture_overload (bool = false)
Allows for multiple events to occur simultaneously. For instance, touch scrolling will still work while buttons are actively used by the user.
### drag_scrolling (bool = true)
If true dragging the content area scrolls it, which is mostly useful for mobile interfaces.
### grab_key_focus (bool = false)
Determines whether the list should capture the keyboard focus when it is interacted with.
### max_pull_down (f64 = 100.0)
The maximum distance one can pull down the list from the top before it bounces back.
## Subwidgets
### scroll_bars ([ScrollBars](ScrollBars.md))
References to the `ScrollBars` widget. To display scroll bars, one needs to reference them here. This allows for referencing specifically styled and configured `ScrollBars`.
## Examples
### Basic
```Rust
<FlatList> {
scroll_bars: <ScrollBars> {}
Target = <BuildItem> {
padding: 0,
check = <RunButton> { margin: {left: 23} }
}
Binary = <BuildItem> {
flow: Right
fold = <FoldButton> {
height: 25, width: 15,
margin: { left: 8.0 }
animator: { open = { default: off } },
}
check = <RunButton> {}
}
Empty = <BuildItem> {
height: Fit, width: Fill,
cursor: Default
}
// LAYOUT PROPERTIES
height: 500.0,
// Element is 500. high.
width: Fill,
// Element expands to use all available horizontal space.
}
```
### Typical
```Rust
<FlatList> {
align_top_when_empty: true,
capture_overload: true,
drag_scrolling: true,
flick_scroll_decay: 0.98,
flick_scroll_maximum: 80.0,
flick_scroll_minimum: 0.2,
flick_scroll_scaling: 0.005,
grab_key_focus: false,
max_pull_down: 100.0,
swipe_drag_duration: 0.2,
scroll_bars: <ScrollBars> {}
Target = <BuildItem> {
padding: 0,
check = <RunButton> { margin: {left: 23} }
}
Binary = <BuildItem> {
flow: Right
fold = <FoldButton> {
height: 25, width: 15,
margin: { left: 8.0 }
animator: { open = { default: off } },
}
check = <RunButton> {}
}
Empty = <BuildItem> {
height: Fit, width: Fill,
cursor: Default
}
// LAYOUT PROPERTIES
height: 500.0,
// Element is 500. high.
width: Fill,
// Element expands to use all available horizontal space.
}
```
### Advanced
```Rust
MyFlatList = <FlatList> {
align_top_when_empty: true,
capture_overload: true,
drag_scrolling: true,
flick_scroll_decay: 0.98,
flick_scroll_maximum: 80.0,
flick_scroll_minimum: 0.2,
flick_scroll_scaling: 0.005,
grab_key_focus: false,
max_pull_down: 100.0,
swipe_drag_duration: 0.2,
scroll_bars: <ScrollBars> {}
Target = <BuildItem> {
padding: 0,
check = <RunButton> { margin: {left: 23} }
}
Binary = <BuildItem> {
flow: Right
fold = <FoldButton> {
height: 25, width: 15,
margin: { left: 8.0 }
animator: { open = { default: off } },
draw_bg: {
uniform size: 3.75;
instance open: 0.0
fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
The `FoldButton` widget is used to indicate and control expandable sections within the UI. It displays a button that can toggle between open and closed states, often represented with an arrow or triangle that rotates based on its state.
## Layouting
Support for [Walk](Walk.md) layout features which define how the widget positions itself in its parent container.
No support for layouting child elements via the feature subset defined in [Layout](Layout.md).
## Fields
### abs_size (`DVec2`)
The absolute size of the `FoldButton`.
### abs_offset (`DVec2`)
The absolute offset position of the `FoldButton` from its parent.
## DrawShaders
### `draw_bg` ([DrawQuad](DrawQuad.md))
Determines the appearance of the `FoldButton`, including visual representation and animations for different states like hover and open.
The `FoldHeader` widget is a composite widget that provides expandable and collapsible sections within the UI. It consists of a header that is always visible and a body that can be toggled open or closed, typically controlled by a `FoldButton` within the header.
## Layouting
Complete layouting feature set support.
## Fields
### header (WidgetRef)
Reference to the widget that makes up the persistent header of the `FoldHeader`. This typically includes the `FoldButton` and any header content.
### body (WidgetRef)
Reference to the widget containing the content to be toggled. This is usually a `<View>` with child elements that you want to show or hide.
### body_walk ([Walk](Walk.md))
Controls the body widget's layout properties, such as size and positioning, as supported by the [`Walk`](Walk.md) layout system.
| `opened` (f32) | When the `FoldButton` is clicked to open or close the body content |
## Examples
### Basic Usage
```Rust
<FoldHeader> {
header: <View> {
fold_button = <FoldButton> {}
<Label> { text: "Fold me!" }
width: Fill, height: Fit
}
body: <View> {
<Label> { text: "This is the body that can be folded away" }
width: Fill, height: Fit
}
width: Fill,
height: Fill,
}
```
*In this basic example, the `FoldHeader` contains a header with a `FoldButton` and a label. The body includes content that can be toggled open or closed.*
### Typical
```Rust
MyFoldHeader = <FoldHeader> {
header: <View> {
height: Fit,
align: { x: 0.0, y: 0.5 },
fold_button = <FoldButton> {}
<Label> { text: "Fold me!" }
}
body: <View> {
width: Fill, height: Fit,
padding: 5.0,
<Label> { text: "This is the body that can be folded away" }
}
body_walk: { width: Fill, height: Fit },
// LAYOUT PROPERTIES
width: Fill,
// The widget expands to use all available horizontal space.
height: Fill,
// The widget expands to use all available vertical space.
flow: Down,
// Stacks children vertically from top to bottom.
spacing: 10.0,
// Sets a spacing of 10.0 between the header and body.
}
```
*This typical example demonstrates additional layout properties and padding for the body content, providing a more refined UI component.*
### Advanced
```Rust
MyFoldHeader = <FoldHeader> {
header: <View> {
height: Fit,
align: { x: 0.0, y: 0.5 },
fold_button = <FoldButton> {}
<Label> { text: "Fold me!" }
}
body: <View> {
width: Fill, height: Fit,
show_bg: false,
padding: 5.0,
<Label> { text: "This is the body that can be folded away" }
*The advanced example includes custom animations for opening and closing, as well as additional layout adjustments, margins, and padding to fine-tune the appearance and behavior of the `FoldHeader`.*
---
By utilizing the `FoldHeader`, you can create interactive UI components that enhance the organization and user experience of your application by allowing sections of content to be expanded or collapsed as needed.
The `Html` widget relies on its parent's layout properties.
## Fields
### `body` (`RcStringMut`)
Contains the HTML code to be rendered by the widget.
### `ul_markers` (`Vec<String>`)
A vector of markers used for unordered lists, indexed by the list's nesting level. Each marker can be an arbitrary string, such as a bullet point or a custom icon.
### `ol_markers` (`Vec<OrderedListType>`)
A vector of markers used for ordered lists, indexed by the list's nesting level. Allows customization of numbering styles, such as numbers, letters, or Roman numerals.
### `ol_separator` (`String`)
The character or string used to separate an ordered list's item number from the content. For example, using `")"` would format list items as `1)`.
## HtmlLink
### Fields
#### `href` (`String`)
The URL to which the link points.
#### `link` ([LinkLabel](LinkLabel.md))
The textual representation of the link to be displayed.
## Examples
### Typical Usage
```rust
MyHtml = <Html> {
body: "<h1>H1 Headline</h1><h2>H2 Headline</h2><h3>H3 Headline</h3><h4>H4 Headline</h4><h5>H5 Headline</h5><h6>H6 Headline</h6>This is <b>bold</b> and <i>italic text</i>. <b><i>Bold italic</i></b>, <u>underlined</u>, and <s>strikethrough</s> text. <p>This is a paragraph.</p> <code>A code block</code>. <br/> And this is a <a href='https://www.google.com/'>link</a><br/><ul><li>lorem</li><li>ipsum</li><li>dolor</li></ul><ol><li>lorem</li><li>ipsum</li><li>dolor</li></ol><br/><blockquote>Blockquote</blockquote> <pre>preformatted text</pre><sub>subscript</sub><del>deleted text</del>",
// Layout Properties
height: Fill, // Fills the available vertical space
width: Fill, // Fills the available horizontal space
This field allows styling of the label's text with all attributes supported by [DrawText](DrawText.md), including colors, font, font size, and more.
## Fields
### `align` (Align)
Controls where the text is placed within the parent container.
- **x**: `float` (0.0 - 1.0)
- **y**: `float` (0.0 - 1.0)
### `padding` ([Padding](../ft/ft_padding.md))
Defines the space between the label's border and its content.
### `text` (`RcStringMut`)
The text to be displayed by the label.
### `hover_actions_enabled` (`bool`)
Indicates if this label responds to hover events.
*Note:* It is not enabled by default because it will consume finger events and prevent other widgets from receiving them if not considered carefully. The primary use case for this is displaying tooltips.
## Widget Presets & Variations
The base theme includes a typographic system following the HTML convention for text formats:
- `<H1>`: Headline
- `<H1italic>`: Headline (Italic)
- `<H2>`: Headline
- `<H2italic>`: Headline (Italic)
- `<H3>`: Headline
- `<H3italic>`: Headline (Italic)
- `<H4>`: Headline
- `<H4italic>`: Headline (Italic)
- `<P>`: Paragraph
- `<Pbold>`: Bold paragraph text
- `<Pitalic>`: Italic paragraph text
- `<Pbolditalic>`: Bold italic paragraph text
## Examples
### Basic Usage
```rust
<Label> {
text: "Hello World", // Simple label with default settings
}
```
### Typical
```rust
<Label> {
text: "Hello world",
align: { x: 0.0, y: 0.5 }, // Aligns text to the left and vertically centered
// LAYOUT PROPERTIES
width: Fill,
// Element expands to use all available horizontal space.
This field holds the Markdown content that will be rendered by the widget.
### paragraph_spacing (f64)
The amount of space between displayed paragraphs.
### text_flow ([TextFlow](TextFlow.md))
Selects the desired [TextFlow](TextFlow.md) mode.
## Examples
### Typical
```rust
<Markdown> {
body: "# Headline 1\n## Headline 2\n### Headline 3\n#### Headline 4\nThis is standard text with a\n\nline break, a short ~~strike through~~ demo.\n\n*Italic text*\n\n**Bold text**\n\n- Bullet\n- Another bullet\n\n- Third bullet\n\n1. Numbered list Bullet\n2. Another list entry\n\n3. Third list entry\n\n`Monospaced text`\n\n> This is a quote.\n\nThis is `inline code`.\n\n```code block```"
// LAYOUT PROPERTIES
height: Fill, // Element expands to use all available vertical space.
width: Fill, // Element expands to use all available horizontal space.
}
```
**Explanation:** This example creates a `Markdown` widget that renders a sample Markdown text showcasing various formatting options such as headers, bold and italic text, lists, code blocks, and more.
### Advanced
```rust
MyMarkdown = <Markdown> {
body: "# Headline 1\n## Headline 2\n### Headline 3\n#### Headline 4\nThis is standard text with a\n\nline break, a short ~~strike through~~ demo.\n\n*Italic text*\n\n**Bold text**\n\n- Bullet\n- Another bullet\n\n- Third bullet\n\n1. Numbered list Bullet\n2. Another list entry\n\n3. Third list entry\n\n`Monospaced text`\n\n> This is a quote.\n\nThis is `inline code`.\n\n```code block```"
paragraph_spacing: 20.0, // Sets the spacing between paragraphs.
// LAYOUT PROPERTIES
height: 300., // Sets the widget height to 300 units.
width: Fill, // Element expands to use all available horizontal space.
margin: 10.0, // Adds a margin of 10 units around the widget.
padding: 10.0, // Adds padding of 10 units inside the widget.
flow: Right, // Sets the flow direction to right.
align: { x: 0.0, y: 0.0 }, // Aligns the content to the top-left.
line_spacing: 1.5 // Sets the spacing between lines.
}
<MyMarkdown> {
body: "# Headline 1\n\nMy Text"
}
```
**Explanation:** This advanced example defines a custom widget `MyMarkdown` based on `Markdown` with additional styling properties such as paragraph spacing, margin, padding, and line spacing. The custom widget is then instantiated with custom Markdown content.
This widget manages keyboard navigation, specifically handling focus traversal using the **Tab** key. It facilitates navigating through focusable elements (nav stops) in a structured and orderly manner.
## Layouting
No layouting support.
## DrawShaders
### draw_focus ([DrawQuad](DrawQuad.md))
This shader styles the focus indicator when a navigation stop gains focus. It allows customization of the focus appearance, such as color and size.
### draw_text ([DrawText](DrawText.md))
This shader allows styling of any text associated with the `NavControl`, supporting all attributes from [DrawText](DrawText.md), including colors, font, and font size.
The `PageFlip` widget manages and displays multiple pages, allowing users to flip between them dynamically. It's useful for creating multi-page interfaces within your application.
## [Layouting](Layouting.md)
Complete layouting feature set support.
## Fields
### active_page (LiveId)
Sets the active page. This determines which page is currently visible to the user.
### lazy_init (bool = false)
If set to `true`, pages will only be initialized when they are first displayed. This can improve performance by deferring the creation of pages until they are needed.
## Examples
### Basic
```Rust
example
```
### Typical
```Rust
example
```
### Advanced
```Rust
<PageFlip> {
width: Fit, height: Fit,
active_page: log
lazy_init: true,
wait = <Icon> {
draw_bg: {
fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
sdf.circle(5., 5., 4.)
sdf.fill(THEME_COLOR_LABEL_INNER)
sdf.move_to(3., 5.)
sdf.line_to(3., 5.)
sdf.move_to(5., 5.)
sdf.line_to(5., 5.)
sdf.move_to(7., 5.)
sdf.line_to(7., 5.)
sdf.stroke(#0, 0.8)
return sdf.result
}
}
},
log = <Icon> {
draw_bg: {
fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
sdf.circle(5., 5., 4.);
sdf.fill(THEME_COLOR_LABEL_INNER);
let sz = 1.;
sdf.move_to(5., 5.);
sdf.line_to(5., 5.);
sdf.stroke(#a, 0.8);
return sdf.result
}
}
}
error = <Icon> {
draw_bg: {
fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
sdf.circle(5., 5., 4.5);
sdf.fill(THEME_COLOR_ERROR);
let sz = 1.5;
sdf.move_to(5. - sz, 5. - sz);
sdf.line_to(5. + sz, 5. + sz);
sdf.move_to(5. - sz, 5. + sz);
sdf.line_to(5. + sz, 5. - sz);
sdf.stroke(#0, 0.8)
return sdf.result
}
}
},
warning = <Icon> {
draw_bg: {
fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
sdf.move_to(5., 1.);
sdf.line_to(9.25, 9.);
sdf.line_to(0.75, 9.);
sdf.close_path();
sdf.fill(THEME_COLOR_WARNING);
// sdf.stroke(#be, 0.5);
sdf.move_to(5., 3.5);
sdf.line_to(5., 5.25);
sdf.stroke(#0, 1.0);
sdf.move_to(5., 7.25);
sdf.line_to(5., 7.5);
sdf.stroke(#0, 1.0);
return sdf.result
}
}
}
panic = <Icon> {
draw_bg: {
fn pixel(self) -> vec4 {
let sdf = Sdf2d::viewport(self.pos * self.rect_size)
The `PortalList` widget efficiently handles large lists of items by only rendering the items currently visible in the viewport. It supports features like scrolling, flick scrolling, and alignment of items within the list. This is especially useful for implementing lists with a large number of items without compromising performance.
## [Layouting](Layouting.md)
Complete layouting feature set support.
## Fields
### align_top_when_empty (bool = true)
If `true`, content will be displayed at the top when the widget is not fully filled. This means that if there are not enough items to fill the viewport, they will be aligned to the top.
### auto_tail (bool = false)
If `true` and the list is scrolled to the bottom, any new items added to the list will cause the scroll position to stick to the last item. This is useful for applications like chat windows or logs where new items are continuously appended.
### capture_overload (bool = false)
If `true`, the widget will capture mouse events even if they are already captured by another widget. This ensures that scroll events are handled by the `PortalList` even when other interactive elements are present.
### drag_scrolling (bool = true)
If `true`, dragging the content area scrolls it. This is especially useful for touch interfaces and mobile devices where users expect to scroll by dragging.
### grab_key_focus (bool = false)
Determines whether the widget should capture the keyboard focus when it is interacted with. If `true`, interacting with the `PortalList` will grant it keyboard focus.
### keep_invisible (bool = false)
If `true`, items that are scrolled out of view are kept in memory instead of being removed. This can improve performance if recreating items is expensive, at the cost of higher memory usage.
### max_pull_down (f64 = 100.0)
Defines the maximum distance the content can be pulled down beyond the top of the list when overscrolling. It creates a "pull-to-refresh" or bounce-back effect when users scroll past the start of the list.
## Subwidget
### scroll_bar ([ScrollBar](ScrollBar.md))
Reference to the `ScrollBar` subwidget used for scrolling within the `PortalList`.
The `RadioButton` control allows users to select a single option from a set of choices. It can display text, icons, or images, and supports different styles like round buttons or tabs.
The `RotatedImage` widget displays an image that can be scaled, allowing for dynamic transformations. It's useful for creating interactive and visually engaging interfaces where images need to be manipulated.
## [Layouting](Layouting.md)
Complete layouting feature set support.
## DrawShader
### draw_bg ([DrawColor](DrawColor.md))
The `DrawColor` shader responsible for rendering the image with scaling transformations.
## Fields
### source (LiveDependency)
Path to the image file. This is a `LiveDependency` that specifies the image to be displayed.
### scale (f64)
The scaling factor for the image. A value of `1.0` displays the image at its original size.
## Examples
### Scaled Image
```Rust
<RotatedImage> {
source: dep("crate://self/resources/logo.png"),
scale: 0.5, // Half the original size
}
```
Displays the image scaled to half its original size.
The `ScrollBar` widget allows users to navigate through content that overflows the visible area, either horizontally or vertically.
## Layouting
No layouting support.
## Structure
The `ScrollBar` consists of a track and a handle. The track represents the total scrollable area, while the handle represents the visible portion of the content.
The `ScrollBars` widget provides horizontal and vertical scroll bars that can be integrated into your UI components to enable content scrolling. It allows you to control the visibility and behavior of the scroll bars individually.
## Layouting
No layouting support.
## Fields
### show_scroll_x (bool)
Determines whether the horizontal scroll bar is visible.
### show_scroll_y (bool)
Determines whether the vertical scroll bar is visible.
### scroll_bar_x ([ScrollBar](ScrollBar.md))
An instance of the `ScrollBar` widget representing the horizontal scroll bar.
### scroll_bar_y ([ScrollBar](ScrollBar.md))
An instance of the `ScrollBar` widget representing the vertical scroll bar.
## Examples
### Advanced
```Rust
MyScrollBars = <ScrollBars> {
show_scroll_x: true,
show_scroll_y: true,
scroll_bar_x: <ScrollBar> {},
scroll_bar_y: <ScrollBar> {}
}
<MyScrollBars> {}
```
Some files were not shown because too many files have changed in this diff
Show more