Commit graph

213 commits

Author SHA1 Message Date
d6d1f99ca9 Update fork to upstream dev 5d4483f
- Sync with latest upstream dev branch
- Include all map improvements: 2D/3D toggle, shadows, labels, overlays
- Include platform updates: location API, audio echo cancellation
- Preserve fork-specific re-exports (gltf, csg, test)
2026-07-31 18:47:03 +00:00
Patrik Husfloen
434583def4
feat(draw): add sdf.arc_to circular-arc path segment (#1142)
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
2026-07-23 12:14:24 +02:00
Kevin Boos
3af2e93f0d
Honor X & Y align values for deferred Fill child widgets (#1143)
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.
2026-07-23 12:14:04 +02:00
Kevin Boos
a4fea8e259
New unified kinetic scrolling. Vastly improve draw shaprness on low-res screens. Fix rendering and text perf issues on CPU+GPU (#1127)
* 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
2026-07-21 09:59:12 +02:00
Admin
3a82d26045 hypothetical heap access fix 2026-07-01 14:04:23 +02:00
Admin
02592599ec aichat 2026-06-25 18:52:10 +02:00
Kevin Boos
82db45a00c
parse and use orientation info for all supported image formats (#1130) 2026-06-23 09:08:45 +02:00
Kevin Boos
af130e98fd
Limit cache growth for text/script, reclaim memory after gc (#1123)
* 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.
2026-06-16 09:07:53 +02:00
Kevin Boos
585475d02a
Fix modal behavior: prevent scroll behind it, make Fit{max} scrollable (#1117)
* 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.
2026-06-11 22:12:09 +02:00
Kevin Boos
bb1474003c
SVG: allow replacing and re-loading the SVG "doc" (#1116)
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).
2026-06-09 21:04:52 +02:00
Kevin Boos
8b03b0b2ad
Audit and harden image decoding stuff against huge inputs (DoS) (#1110)
* 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.
2026-06-09 00:31:36 +02:00
Kevin Boos
f5df1eb4d1
Layout/turtle fixes: right-wrap flows could cut off the right side of widgets (#1114)
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.
2026-06-09 00:10:47 +02:00
Kevin Boos
850edc8ca6
Image support: add bmp/qoi,ico, webp, SVG in Image widget, 16-bit png (#1108)
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.
2026-06-09 00:09:29 +02:00
Kevin Boos
0bc3c7798b
DrawText: avoid redrawing slug stuff on EVERY frame (#1104)
* 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).
2026-06-05 07:19:06 +02:00
Admin
4912f2690d update zune 2026-06-02 18:47:28 +02:00
Kevin Boos
12dcfc1e77
Fix subtle layout bug that ignored padding for text wrapping calc (#1094) 2026-05-24 06:34:08 +02:00
Edward Tan
8447f5666a
Fix Android rendering, loading issues (#1090)
* 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.
2026-05-21 09:08:13 +02:00
alanpoon
5416cdd25c
Added gif (#1083)
* added giphy

* added gif

* remove unnecessary file changes

* animated_image_git
2026-05-18 22:47:53 +02:00
Admin
55db1be071 gauss blurs 2026-05-03 23:45:59 +02:00
Kevin Boos
5ec03ff0ad
Add full app lifecycle event support, and Ctrl+C/signal catch (#1074)
* 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.
2026-05-01 22:30:24 +02:00
Kevin Boos
1a2453e7e4
Misc fixes for PortalList alignment and inline <code> tags (#1071)
* 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
2026-04-30 08:32:51 +02:00
Admin
61a3f53c5c ai manager otw 2026-04-28 12:24:40 +02:00
Kevin Boos
18669f5a58
Support runtime changes to script-level heap objects (#1063)
* 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
2026-04-22 23:36:56 +02:00
Kevin Boos
047e9cba21
Add RowAlign::Center, per-row FinishedWalk support, and inline widget alignment (#1053)
* 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
2026-04-18 10:30:04 +02:00
Kevin Boos
c7d7202551
Html/Markdown fixes: sub/superscript, table outlines/alignment, etc (#1051)
* 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 `&amp;` (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
2026-04-18 10:29:50 +02:00
Kevin Boos
4a576a7add
draw_text: adopt outer many_instances batch on linux/windows (#1049)
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.
2026-04-17 08:25:28 +02:00
Kevin Boos
85fbea9b0f
Switch to SLUG/DrawGlyph font drawing stack (#1042)
* 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.
2026-04-16 22:50:48 +02:00
Kevin Boos
93a5a875f3
Fix RTL/bidi text shaping panic and mis-layout (#1046)
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.
2026-04-16 20:51:02 +02:00
Kevin Boos
6102d4329b
Optimize text layout and PortalList widget performance (#1041)
* 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
2026-04-15 11:47:47 +02:00
Kevin Boos
007bf000a1
Ensure .notdef glyph for unsupported characters is visibly drawn (#1045)
* 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.
2026-04-15 09:28:21 +02:00
Admin
747ef9b2e8 mlx 5x->2x 2026-04-10 14:46:22 +02:00
Kevin Boos
7c25ded861
Flow right wrap fix (#1023)
* 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.
2026-04-08 07:36:19 +02:00
Kevin Boos
cc60726b35
Support scrolling while centered, both vertically and horizontally (#1017)
* 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
2026-04-07 09:32:47 +02:00
Kevin Boos
f2c02e878e
Add ellipsis text truncation support (text_overflow + max_lines) (#1014)
* 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
2026-04-06 16:29:24 +02:00
Kevin Boos
e2cf5592c9
TextInput: support single-line horizontal scrolling when text overflows (#1012)
* 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
2026-04-06 16:28:35 +02:00
Admin
d8883d1783 llama works atleast 2026-04-05 11:30:53 +02:00
Kevin Boos
17eb90842d
TextInput: explicitly support multiline mode with parent-relative max height (#1011)
* 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
2026-04-03 19:17:15 +02:00
Kevin Boos
10c33aca95
Fix text wrapping breaking before trailing punctuation marks (#1006)
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).
2026-04-02 08:31:20 +02:00
Admin
4fc9a06b06 variable weight fonts 2026-04-01 12:19:03 +02:00
Admin
3f2baab26d xr llama and slug 2026-04-01 12:19:03 +02:00
Kevin Boos
191ac72bf9
Introduce knowledge of device screen bounds/"safe inset areas" (#990)
* 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
2026-03-31 08:57:36 +02:00
Admin
a0732c853c cleanup 2026-03-29 23:21:47 +02:00
Admin
83a9fa2017 xr room mapping 2026-03-27 13:51:54 +01:00
Admin
78296f515b optimising quest renderpath with physics 2026-03-25 22:44:16 +01:00
Admin
5e04f6fb06 optimising quest renderpath with physics 2026-03-25 22:44:16 +01:00
Kevin Boos
66075ff67f
Improve font parsing and text drawing perf with a hybrid caching approach (#986)
* 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
2026-03-25 21:28:50 +01:00
Admin
6431464aa1 better physics 2026-03-25 17:39:00 +01:00
Admin
0fc2114f61 finally runs 2026-03-25 17:31:52 +01:00
Admin
06c3e1c0d1 xr otw 2026-03-23 20:52:29 +01:00
Admin
2b677c6da0 compiles 2026-03-23 12:56:39 +01:00