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>
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
* 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.
* 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
* 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.
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
* 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
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