This avoids a strange case where Linux (both X11 and wayland but
in different ways) failed to properly shut down cleanly.
* Fix X11 close handling by removing destroyed windows from the map,
which helps avoid delivering duplicate window closed events.
* Restructure how close handling happens on Wayland too, and allow
the app to respond to a close request just like other platforms.
* Add a Linux second-signal hard exit so that repeated Ctrl+C
or `kill` comands can actually terminate a failed/hung shutdown.
* Also a tiny fix to windows too: clear Win32 `GWLP_USERDATA` during
`WM_DESTROY` to avoid accessing an old window pointer.
* Support standard keyboard navg shortcuts/keys in TextInput
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
* Add `CropToFill` image fit variant, improve ImageFit docs
This allows you to easily achieve the "centered cropped fit" that most apps
want for things like avatars or small thubmnails that get masked.
* Detect and support hardware keyboards, distinguish from soft/virtual kbd
Mimic desktop behavior on mobile systems as much as possible.
This is esp important for tablets like iPad OS where you're more likely
to have a real physical keyboard attached.
For iOS:
* Arrow keys and Home/End/PageUp/PageDown navigate and auto-repeat
at the system-defined rate (connected via `UIKeyCommand`)
* Cmd+Enter to submit a `TextInput` and Cmd+C/X/V clipboard shortcuts now work.
* Ensure the pop-up diacritic/accent menu is properly placed using a hidden
`UITextInput` native widget, which acts as a sort of "proxy"
* Proactively drain `ShowTextIME` after each draw so the IME position will be
properly updated after each keystroke.
* Importnatly, don't mark the IME dismissed when a hardware keyboard is attached.
For both iOS & Android:
* Add a `has_physical_keyboard()` detection mechanism across both backends,
and fix platform-specific key repeat behavior
For Android:
* Ensure clipboard cut/copy works using the same Ctrl shortcuts (API 26+)
Soft/virtual keyboard/IME changes:
* For multiline TExtInputs, a soft keyboard Enter/Return key will always just
insert a new line, to avoid complexity with keyboard shortcut cfgs.
* CJK keyboard character selection should also be properly positioned now
* minor optimization to avoid re-setting IME pos if it didn't change
* Image support: add bmp/qoi,ico, webp, SVG in `Image` widget, 16-bit png
Generally, this commit makes improvements to image decoding and rendering.
Added a bunch of functions for image discovery / metadata gathering:
`decode_image_from_data()`, `image_size_by_data()`, `looks_like_svg()`
Added more `Image[Ref]` functions for other image formats:
`ImageRef::load_{bmp,qoi,ico,gif,webp,svg}_from_data()`, plus a nice
convenience fn for auto-detec+load: `load_image_from_data()`.
Added cheap, lazily-init'd support for SVGs within the `Image` widget.
Fixed some issues with aspect ratio being clobbered during image rotation.
* iOS: fix hardware kbd behavior with "Full Keyboard Access" enabled
That accessibility setting messed with our previous version, but now
we've made it play nicely with FKA.
It's not *quite* perfect yet, we still don't get the nice little
system-native "pill" pop-up that allows you to easily switch between
the languages/IMEs you've enabled. But it sort of works.
* Image support: add bmp/qoi,ico, webp, SVG in `Image` widget, 16-bit png
Generally, this commit makes improvements to image decoding and rendering.
Added a bunch of functions for image discovery / metadata gathering:
`decode_image_from_data()`, `image_size_by_data()`, `looks_like_svg()`
Added more `Image[Ref]` functions for other image formats:
`ImageRef::load_{bmp,qoi,ico,gif,webp,svg}_from_data()`, plus a nice
convenience fn for auto-detec+load: `load_image_from_data()`.
Added cheap, lazily-init'd support for SVGs within the `Image` widget.
Fixed some issues with aspect ratio being clobbered during image rotation.
* Audit and harden image decoding stuff against huge inputs (DoS)
Bound the size of the decoded image, pixel count, frame counts (for animated),
range of SVG sniffing, and encoded file size.
Only once we run those checks do we actually alloc a buffer for the decoded image. before allocating decode buffers. Validate
Add various other checks within the vendored image decoding libraries too.
* Support standard keyboard navg shortcuts/keys in TextInput
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
* Add `CropToFill` image fit variant, improve ImageFit docs
This allows you to easily achieve the "centered cropped fit" that most apps
want for things like avatars or small thubmnails that get masked.
* Detect and support hardware keyboards, distinguish from soft/virtual kbd
Mimic desktop behavior on mobile systems as much as possible.
This is esp important for tablets like iPad OS where you're more likely
to have a real physical keyboard attached.
For iOS:
* Arrow keys and Home/End/PageUp/PageDown navigate and auto-repeat
at the system-defined rate (connected via `UIKeyCommand`)
* Cmd+Enter to submit a `TextInput` and Cmd+C/X/V clipboard shortcuts now work.
* Ensure the pop-up diacritic/accent menu is properly placed using a hidden
`UITextInput` native widget, which acts as a sort of "proxy"
* Proactively drain `ShowTextIME` after each draw so the IME position will be
properly updated after each keystroke.
* Importnatly, don't mark the IME dismissed when a hardware keyboard is attached.
For both iOS & Android:
* Add a `has_physical_keyboard()` detection mechanism across both backends,
and fix platform-specific key repeat behavior
For Android:
* Ensure clipboard cut/copy works using the same Ctrl shortcuts (API 26+)
Soft/virtual keyboard/IME changes:
* For multiline TExtInputs, a soft keyboard Enter/Return key will always just
insert a new line, to avoid complexity with keyboard shortcut cfgs.
* CJK keyboard character selection should also be properly positioned now
* minor optimization to avoid re-setting IME pos if it didn't change
Previously, `get_cache_dir()` returned None for Linux (X11, Wayland, Direct)
so there was no shader caching happening like there was on
android and windows.
Now we cache it and also handle removal of stale shader binaries
* video_debug
* Fix video pause being overridden by stall-recovery force-play on macOS
The native AVPlayer poll loop nudged a rate-0 player back into playing
whenever `autoplay` was true, intended as stall recovery but firing every
frame after a user-initiated pause. Once `begin_playback` latched
`autoplay = true` on first start, subsequent pauses were undone on the
next frame poll.
Split user playback intent into a `should_play` field that toggles on
play/pause/resume, and gate the force-play check on that instead of
`autoplay` (which is now a one-shot consumed in `check_prepared`).
Also restore the `Apply::Animate` early return in `Video::on_after_apply`
(needed so hover transitions don't re-decode the PNG/JPG thumbnail every
frame) and drop the redundant `Texture::new(cx)` allocation in
`apply_thumbnail_settings` that load_thumbnail_image immediately
overwrote anyway.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Implement platform-standard TextInput navigation and deletion behavior,
including Home, End, PageUp, PageDown, word movement, line/document
boundaries, and Shift-based selection.
* Use Apple Option/Cmd conventions on Apple targets
* Use Ctrl conventions on non-Apple targets
* Web accepts both shortcut styles for now, since we don't have a way
to query the host OS from within a makepad web env.
Also, be extremely careful to ensure that we respect Unicode grapheme boundaries
when doing all the selection/navigation logic.
Fix `Delete`, which was erroneously handled before.
Add lots of missing keys in Linux X11 & Wayland backends, e.g.,
Home, End, Delete, Insert, PageUp/PageDown, and arrow keys
There was a bug where apps on iPad would not always be properly redrawn
when being resized (in windowed mode). This fixes that (at least in my testing)
by NOT clearing the dirty state when the MTKView has no render pass descriptor
Thus, the next time it's valid, we repaint it as expected.
* Dock: avoid ID collisions in drag/drop; never delete dock root in unsplit_tabs
* Clean up and further harden dock logic around splitting/dragging
* cargo_makepad: Android App Bundle builds, API 26 support, stable toolchain
Overhaul the Android build pipeline. Three related build-tooling
changes that share compile.rs/sdk.rs and so are committed together.
Android App Bundle (.aab) support — required for Google Play uploads:
- New `build-aab` command: compile resources with aapt2, link a
proto-format APK, assemble the base module, run bundletool, and sign
with jarsigner.
- New `keystore-create` command wrapping keytool, with a reusable
keystore sidecar file; new `--keystore*`, `--no-sign`,
`--version-code`, `--version-name` flags.
- Version codes may be explicit or auto-generated as a monotonic
YYYYMMDDHH UTC integer.
- Read app id, version, and signing metadata from
`[package.metadata.packager]` / `[package.metadata.makepad.android]`
in Cargo.toml; support a custom AndroidManifest.xml template.
- Upgrade the bundled TOML parser for the dotted keys, inline tables,
and multi-line strings those metadata sections use.
- Download bundletool and copy jarsigner/keytool/aapt2 into the SDK.
minSdkVersion 26:
- Lower the default Android minimum SDK from 33 to 26 and track the
target SDK (35) separately, emitting minSdkVersion and
targetSdkVersion independently in the generated manifest; add a
`--min-sdk-version` override.
Stable Rust toolchain:
- Build Android and iOS on stable instead of nightly. tvOS still needs
nightly for `-Z build-std`, so the channel is resolved per target.
- Add `ensure_rust_toolchain_installed` (install only when missing).
* Android: load newer NDK symbols at runtime to support API 26
With the minimum SDK lowered to 26, NDK entry points that only exist
on newer API levels can no longer be declared with `extern "C"` —
doing so breaks `dlopen`/startup on API 26-28. Resolve them at
runtime instead:
- amidi_sys: lazily `dlopen` libamidi.so (API 29+) into a cached
vtable; the wrappers degrade to error/zero returns when the library
is absent on older devices.
- android_jni: `dlsym` the AChoreographer vsync callbacks, gated on
the running API level.
- ndk_sys: drop the `extern "C"` declarations for
`ANativeWindow_setFrameRate` and the Choreographer callbacks;
android.rs drops the now-unused frame-rate call.
- MakepadActivity: guard `setInitialSurroundingSubText` (API 30+) and
`layoutInDisplayCutoutMode` (API 28+) behind version checks.
- android_jni: the fallback render-loop thread now exits cleanly when
the app is torn down.
* Android: automatic and app-controlled system bar appearance
Add a way to control the tint of the status and navigation bar icons,
fixing white-on-white (invisible) icons when an app draws a light
background under a system dark-mode theme.
- New `Cx::set_system_bar_appearance(SystemBarAppearance)`. The default
`Auto` mode picks dark or light icons from the window background
luminance; `DarkIcons`/`LightIcons` force the choice.
- The `Window` widget resolves the setting each event cycle — for
`Auto`, the Rec.709 luma of `pass.clear_color` — and emits
`CxOsOp::SetSystemBarDarkIcons` only when the resolved value changes.
- On Android this drives `WindowInsetsController.setSystemBarsAppearance`
(API 30+) or the `SYSTEM_UI_FLAG_LIGHT_*` flags (API 26-29). The tint
is re-asserted after fullscreen toggles, since the legacy path
rewrites the whole `systemUiVisibility` bitmask.
* Android: fix soft-keyboard handling and edge-to-edge insets
Several related window-inset and IME fixes, mostly affecting devices
that are not edge-to-edge (Android versions before 15).
- Report safe-area and IME insets as the overlap with the render
surface, not the raw window-edge insets. On a non-edge-to-edge
window the surface already sits inside the system bars, so the raw
insets double-counted — leaving oversized gaps around content and
above the keyboard.
- Also drive safe-area insets from `onGlobalLayout`, so the app is
inset correctly from launch instead of drawing under the status bar
until the first keyboard show or rotation.
- While the keyboard animates, treat the `WindowInsetsAnimation`
callback as the authoritative per-frame inset source and have the
layout-driven callbacks defer to it. Read target IME visibility from
`getRootWindowInsets()` so a show animation is not misread as an
instant dismissal.
- Only reconfigure the Java IME when the `TextInputConfig` actually
changes, instead of on every show.
- `KeyboardView`: compute and apply the content shift at keyboard-show
event time, removing a one-frame lag and a tail-end jump; only
reconcile post-draw when the focused field actually redrew.
- `Modal::close()`: skip the focus revert when the modal is already
closed — it was stealing focus from a just-tapped text input and
causing a first-tap keyboard flicker.
- Hide the keyboard via `WindowInsetsController.hide(ime())` on API 30+.
* platform: don't panic posting actions during shutdown
post_action no longer unwraps the global action sender. It now
silently drops the action if the sender mutex is poisoned, no Cx
sender is installed, or the receiver has been dropped during app
teardown, and only raises the UI signal when the send succeeds.
(Also shortens an over-long field doc comment in cx.rs; no behavior
change.)
* cargo-makepad: link std statically in AAB builds (16 KB page-size fix)
`-C prefer-dynamic` ships std as a separate, 4 KB-aligned libstd.so that
fails Play's 16 KB page-size rule. AAB builds now link std statically;
APK/dev builds keep prefer-dynamic. Also documents {min_sdk_version} in help.
* Disable SLUG text band acceleration
* Load Android optional APIs dynamically
* Fixed the android-only Gauss-pane vertical flip by correcting render-target Y sampling in:
- widgets/src/window.rs:110
- widgets/src/gauss_view.rs:151
Root cause: Gauss captures the scene into render-target textures, then samples them back
into the UI. Those render-target textures need a Y flip when displayed, matching the
existing Image widget behavior.
* Support overriding the dpi factor at runtime, on all platforms
Add `Cx::set_window_dpi_override` for runtime UI zoom, but dispatch it
in a deferred manner so it's safe to call in an event handler.
For each platform, we connect the dpi override to the click/tap
coordinates to remap it properly, which was done on some platforms
but not most.
* Fix and restyle todo example
* cad
* Fix todo input styling and studio build env
* fix slides
* Don't apply UI scaling (dpi override) to safe inset areas / IME areas
Scaling those areas doesn't make sense, as it can lead to empty space
(extra unnecessary padding) on the border of the IME or the device inset area,
which looks bad and is basically objectively wrong
-------------
Centralize native/physical/layout DPI conversion on `CxWindow`, and use it at platform boundaries for window geometry, safe-area insets, input coordinates, soft keyboard spacing, clipboard and selection overlays, and camera preview rects.
Add runtime `set_window_dpi_override` support that rescales all window-local metrics and emits `WindowGeomChange`.
* Improve mobile IME and soft keyboard handling
- Add broader soft keyboard configuration for input modes, autocorrect, autocapitalization, return key types, multiline, and secure text entry.
- Improve iOS text input state handling, composition ranges, selection sync, and native/layout coordinate conversion.
- Improve Android InputConnection handling for composing text, selection updates, batch edits, editor actions, surrounding text, and programmatic text sync.
- Wire TextInput through the expanded IME configuration surface.
- Cover newer iOS and Android IME APIs while preserving fallbacks for older keyboard behavior.
* Fix iOS IME area DPI override scaling
* Remove Android-specific IME hack
* Further fix Android IME to avoid stale composition ranges being underlined
espeically after you tap elsewhere in the TextInput widget
---------
Co-authored-by: admin <info@makepad.nl>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Don't apply UI scaling (dpi override) to safe inset areas / IME areas
Scaling those areas doesn't make sense, as it can lead to empty space
(extra unnecessary padding) on the border of the IME or the device inset area,
which looks bad and is basically objectively wrong
-------------
Centralize native/physical/layout DPI conversion on `CxWindow`, and use it at platform boundaries for window geometry, safe-area insets, input coordinates, soft keyboard spacing, clipboard and selection overlays, and camera preview rects.
Add runtime `set_window_dpi_override` support that rescales all window-local metrics and emits `WindowGeomChange`.
* Fix pre-existing iOS and Android build breaks
iOS (platform/src/os/apple/ios/ios.rs): five `CxOsOp` arms had edits
that landed one match-arm late, so each block referenced bindings only
in scope on the preceding arm. Re-home them:
- `WindowGeomChange` now applies `native_window_geom_to_layout` (the
two stray lines previously sat inside the `Paint`/`prepared` arm).
- `ShowTextIME` now converts `pos` via `layout_vec2d_to_native_points`
(previously lodged inside the `SyncImeState` destructure pattern).
- `ShowClipboardActions` now converts `rect` / `keyboard_shift` to
native points (previously appended to `ShowSelectionHandles`).
- `ShowSelectionHandles` / `UpdateSelectionHandles` now convert `start`
and `end` (the `Update` arm had no conversion, and the `Show` arm's
conversion was actually the clipboard one).
- `FullscreenWindow` / `NormalizeWindow` drop the bogus `start` / `end`
conversions that didn't belong there.
Android (platform/src/ime.rs): `android_jni::to_java_configure_keyboard`
matches on `InputMode::None` and `ReturnKeyType::{Next, None, Previous,
Google, Yahoo, Join, Route, Continue, EmergencyCall}` — variants that
exist on the `ime_improvements` branch (commit 3dc039f0a) but weren't
pulled into this branch. Add them to the enum definitions so the
android target compiles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Support overriding the dpi factor at runtime, on all platforms
Add `Cx::set_window_dpi_override` for runtime UI zoom, but dispatch it
in a deferred manner so it's safe to call in an event handler.
For each platform, we connect the dpi override to the click/tap
coordinates to remap it properly, which was done on some platforms
but not most.
* Don't always set the app's icon (e.g., for packaged app bundles)
Add `Cx::set_window_dpi_override` for runtime UI zoom, but dispatch it
in a deferred manner so it's safe to call in an event handler.
For each platform, we connect the dpi override to the click/tap
coordinates to remap it properly, which was done on some platforms
but not most.
This normalizes the IME geometry calcs across iOS and Android.
The main difference is to make KeyboardView shift the content based on
the focused TExtInput instance instead of trying to shrink the viewport.
Also make sure that StackNavigation widget properly handles the
keyboard shift, even when it is drawing in full-screen mode.
* App menu bar: restore Quit option, use proper app name
The menu bar shows "MakepadStdInLoop" by default, which is really strange
especially for published apps. This fixes that and also allows the app
to set the name, as well as using a sensible default for it if the app
didn't specify it.
We also now restore the previous Makepad 1.0 behavior of having a default
"Quit" entry in the main app's first menu bar entry.
* ensure Exit flow actually makes it out, on macOS
* WIP: adding full app lifecycle event support, and Ctrl+C/signal catch
* iOS: don't always prompt the user for camera/mic access on app startup
This fixes two unrelated but similar-structure issues, both on iOS:
- `Cx::handle_repaint` ran a 32-slot encoder-capture sweep every frame,
which lazy-created `AvCaptureAccess` and fired
`requestAccessForMediaType:AVMediaTypeVideo` — i.e. the camera prompt —
on every Makepad app, even ones that never touch the camera. Gate the
loop on `av_capture.is_some()` and stop the three `video_encoder_*`
methods in `apple_media.rs` from lazy-creating it; they now return
`EncoderNotStarted` (or no-op for `push_frame`) when no encoder exists.
- `AudioUnitAccess::new()` unconditionally activated the shared
`AVAudioSession` with category `PlayAndRecord` + `VoiceChat` mode,
which is wrong for playback-only apps (mic prompt + forced VPIO).
Defer session config to a new `ensure_ios_session(for_input)` helper,
called from `use_audio_inputs` (PlayAndRecord) and `use_audio_outputs`
(Playback). Idempotent, never downgrades.
* Added fuller lifecycle event support, plus ability to catch sigquit
Tested working well on every platform except web/wasm, as I don't have
a working setup able to test that.
* WIP: adding full app lifecycle event support, and Ctrl+C/signal catch
* Added fuller lifecycle event support, plus ability to catch sigquit
Tested working well on every platform except web/wasm, as I don't have
a working setup able to test that.
* Fix portallist alignment handling so centering actually works
Now, alignment on PortalLists now only applies to the "cross-axis"
of each item, not the main axis.
So when you center a portallist itself, it won't add weird space
on the leading side of the list
* Fix text not drawing on top of a background, e.g., `<code>` tags
This worked in *most* but not all cases, e.g., if you had a ton of
inline code tags, some of them would rarely but deterministically
not show the actual text glyphs, but just an empty background.
* Avoid large margin on the left of `<code>` if it's on a new line
* Add a separate "touch" margin; use it on dock splitter and tab close
Without this, those dock UI elements are nearly impossible to grab
and press on a real touch screen device, even on my iPad.
Also, tweak cargo-makepad iOS and Android builds to use a polished
display name for the app (uppercase first character) by default.
* 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.
* 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
* 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.
Two independent iOS touch-input correctness fixes.
1. Hit-test: stop inflating widget bounds by touch radius
`Cx::hits_with_options_and_test` was inflating every widget's
clickable rect outward by `touch.radius` on all four sides, where
`radius` comes from `UITouch.majorRadius` on iOS (and the Android
equivalent). On real fingers this is 5-15pt, but the iOS Simulator
synthesizes mouse clicks as touches with `majorRadius` ~25-40pt, so
every button in the simulator captured clicks ~30pt outside its
visible bounds — a click well above or below the Upload Avatar
button still activated it.
Remove the inflation in both TouchState::Start and TouchState::Stop
and hit-test against the touch centroid only. This matches UIKit/
AppKit native behavior; Apple's HIG and Material Design already
require touch targets large enough that hidden inflation isn't
needed. Apps that want explicit hit padding can still pass it via
`HitOptions::margin`. `touch.radius` is still populated and
delivered to apps that want it for visual feedback.
Affected platforms: iOS, Android, web touch. Desktop mouse paths
(MouseDown/Up) never touched this code.
2. Window geometry: read from MTKView, not UIScreen
`IosApp::check_window_geom` was reading `inner_size` from
`UIScreen.mainScreen.bounds`, which describes the *physical screen*
rather than the app's drawing surface. On iPad Split View, Slide
Over, Stage Manager, and multi-scene apps the window is a fraction
of the screen, so `inner_size` could disagree with the MTKView's
actual bounds — introducing a constant offset between layout and
the touch coordinate space (UITouch `locationInView:` is always
view-local).
Read `bounds`, `contentScaleFactor`, and `safeAreaInsets` from the
MTKView itself, falling back to UIScreen only during the early-init
window before the view exists.
Also use the `size` parameter delivered to
`mtkView:drawableSizeWillChange:` directly (converting pixels→points
via `contentScaleFactor` from the same view) rather than re-querying.
UIKit gives us the authoritative new size synchronously with the
resize callback; re-reading any other source can race by one layout
pass during rotation or multitasking transitions.
Extract the common WindowGeom construction / first-draw / update_geom
/ callback-dispatch tail into `apply_new_window_geom`.
* Another proper fix for black screen on Android start/resume
* Fix all Android surface artifacts on pause, resume, and cold start
* cleanup: remove java-level logging in MakepadActivity
* additional cleanup/improvements for android lifecycle stuff
* WIP fixing performance issue on Windows OS when resizing app window
* Improved resize behavior (flickering/stretching) on Windows
* cleanup window-resize optimizations for Windows OS
* Android: fix nondeterminstic crashes when using a bad surface
Makepad apps on Android were randomly crashing within the `Cx::render_view`
callstack when the host Activity surface was recycled, e.g., on
background/foreground transitions, rotation, IME show/hide, etc.
Details of the change are generated below:
----
`SurfaceHolder.Callback.surfaceDestroyed` posted a fire-and-forget message
to the render thread and returned to Android immediately, leaving two
overlapping races:
1. The render thread could drain `SurfaceDestroyed` from the mpsc channel,
call `destroy_surface()` (which does `eglMakeCurrent(NULL, NULL, NULL,
NULL)` and nulls the EGL surface), then in the *same* `RenderLoop`
iteration call `handle_drawing()` → `render_view()` and issue GL calls
with no current EGL context.
2. Even when our Rust side was well-behaved, Android was free to recycle
the underlying buffer queue the moment `surfaceDestroyed` returned to
Java, while the render thread was still mid-frame against it.
**Layer 1 — Surface validity tracking.** Added `CxOs::surface_alive`,
flipped synchronously in `SurfaceCreated`/`SurfaceChanged`/`SurfaceDestroyed`
handlers, plus a `CxOs::has_drawable_surface()` helper that gates every
GL/Vulkan dispatch entry point: `main_loop`'s `handle_drawing()`,
`draw_pass_to_window_for_active_backend`, `draw_pass_to_texture_for_active_backend`,
`draw_pass_to_fullscreen`, and `eglSwapBuffers` in `present_window_for_active_backend`.
**Layer 2 — Synchronous Java↔Rust handshake.** `FromJavaMessage::SurfaceDestroyed`
now carries an `Arc<(Mutex<bool>, Condvar)>` ack channel. The JNI binding
blocks the Android UI thread on the condvar (2-second budget, well under
the 5-second ANR threshold) until the render thread confirms it has
released the surface. This is the same pattern `android.opengl.GLSurfaceView`
uses, and it closes the underlying-buffer-recycled-mid-frame race.
**Layer 3 — Defense-in-depth re-bind.** `draw_pass_to_fullscreen` now calls
a new fallible `try_make_current()` every frame, recovering from any GL
context drift caused by foreign code or our own teardown path, and
bailing cleanly if the bind fails instead of crashing inside the driver.
Verified all 5 gated entry points against the `openxr_render_loop` →
`openxr_handle_repaint` path:
- 4 entry points are unreachable in XR mode (XR uses its own swapchains
and `xrEndFrame`, never `eglSwapBuffers` or the popup overlay path).
- `draw_pass_to_texture_for_active_backend` IS reachable (off-screen UI
textures composited into the XR scene). To prevent these from being
wrongly skipped in Vulkan+XR mode after the host surface is recycled,
added an explicit XR escape hatch to `has_drawable_surface()`: when
`in_xr_mode && openxr.session.is_some()`, return `true` based purely on
`vulkan.is_some()`, ignoring the (intentionally nulled) `display.window`.
This matches the existing `keep_xr_backend_alive` logic in the
`SurfaceDestroyed` handler.
- `destroy_surface` is now idempotent (safe to call when already null).
- `make_current` asserts on null surface with a descriptive panic message
instead of crashing inside EGL.
- Lifecycle handlers verify surface creation actually succeeded before
flipping `surface_alive` to `true` (no false-positive ready signal).
Tested working on my OnePlus Open w/ Android 15.
-------------
* Other fixes: minor change to logging format to include level indicator
* Fix warning in cargo makepad android
* Fix Android platform errors, mostly no draw on start/resume
Also a small related optimization on iOS
------------
* Android: JNI method ID caching (`ndk_utils.rs`)
* Rewrote the `call_method!` macro to cache `jmethodID` in a per-call-site `static AtomicPtr`. Previously, every JNI call allocated two `CString`s and called `GetObjectClass` + `GetMethodID` from scratch. Now these are resolved once and reused for all subsequent calls. Also deletes the local class reference after first resolution.
* Android: `to_java_update_tex_image` uses cached macro (`android_jni.rs`)
* Replaced manual `GetObjectClass`/`CString::new`/`GetMethodID` calls with the now-cached `call_bool_method!` macro, eliminating per-frame JNI overhead for video texture updates.
* Android: Touch event coalescing (`android.rs`)
* When draining pending messages before a RenderLoop frame, consecutive pure-Move touch events are now coalesced — only the last position is dispatched. Start/Stop events are never dropped. This reduces redundant event dispatch during active touch scrolling.
* Android: Black screen fix (`android.rs`)
* Added `needs_first_draw` flag to `CxOs`. When a `RenderLoop` callback arrives but the surface isn't drawable, the flag is set. When the surface later becomes available, `redraw_all()` is called to ensure the first frame is painted. The flag is also set on `SurfaceDestroyed` so resuming from background always gets a guaranteed first frame.
* iOS: Remove unnecessary `passes_todo.clone()` (`ios.rs`)
* Removed a redundant `Vec::clone()` in the popup pass draw loop — both the outer and inner loops borrow `passes_todo` immutably.
Windows was issuing two shutdown events any time the window was closed,
so it no longer does that. Might've been my fault in a previous change,
not sure.
Makepad apps on Android were randomly crashing within the `Cx::render_view`
callstack when the host Activity surface was recycled, e.g., on
background/foreground transitions, rotation, IME show/hide, etc.
Details of the change are generated below:
----
`SurfaceHolder.Callback.surfaceDestroyed` posted a fire-and-forget message
to the render thread and returned to Android immediately, leaving two
overlapping races:
1. The render thread could drain `SurfaceDestroyed` from the mpsc channel,
call `destroy_surface()` (which does `eglMakeCurrent(NULL, NULL, NULL,
NULL)` and nulls the EGL surface), then in the *same* `RenderLoop`
iteration call `handle_drawing()` → `render_view()` and issue GL calls
with no current EGL context.
2. Even when our Rust side was well-behaved, Android was free to recycle
the underlying buffer queue the moment `surfaceDestroyed` returned to
Java, while the render thread was still mid-frame against it.
**Layer 1 — Surface validity tracking.** Added `CxOs::surface_alive`,
flipped synchronously in `SurfaceCreated`/`SurfaceChanged`/`SurfaceDestroyed`
handlers, plus a `CxOs::has_drawable_surface()` helper that gates every
GL/Vulkan dispatch entry point: `main_loop`'s `handle_drawing()`,
`draw_pass_to_window_for_active_backend`, `draw_pass_to_texture_for_active_backend`,
`draw_pass_to_fullscreen`, and `eglSwapBuffers` in `present_window_for_active_backend`.
**Layer 2 — Synchronous Java↔Rust handshake.** `FromJavaMessage::SurfaceDestroyed`
now carries an `Arc<(Mutex<bool>, Condvar)>` ack channel. The JNI binding
blocks the Android UI thread on the condvar (2-second budget, well under
the 5-second ANR threshold) until the render thread confirms it has
released the surface. This is the same pattern `android.opengl.GLSurfaceView`
uses, and it closes the underlying-buffer-recycled-mid-frame race.
**Layer 3 — Defense-in-depth re-bind.** `draw_pass_to_fullscreen` now calls
a new fallible `try_make_current()` every frame, recovering from any GL
context drift caused by foreign code or our own teardown path, and
bailing cleanly if the bind fails instead of crashing inside the driver.
Verified all 5 gated entry points against the `openxr_render_loop` →
`openxr_handle_repaint` path:
- 4 entry points are unreachable in XR mode (XR uses its own swapchains
and `xrEndFrame`, never `eglSwapBuffers` or the popup overlay path).
- `draw_pass_to_texture_for_active_backend` IS reachable (off-screen UI
textures composited into the XR scene). To prevent these from being
wrongly skipped in Vulkan+XR mode after the host surface is recycled,
added an explicit XR escape hatch to `has_drawable_surface()`: when
`in_xr_mode && openxr.session.is_some()`, return `true` based purely on
`vulkan.is_some()`, ignoring the (intentionally nulled) `display.window`.
This matches the existing `keep_xr_backend_alive` logic in the
`SurfaceDestroyed` handler.
- `destroy_surface` is now idempotent (safe to call when already null).
- `make_current` asserts on null surface with a descriptive panic message
instead of crashing inside EGL.
- Lifecycle handlers verify surface creation actually succeeded before
flipping `surface_alive` to `true` (no false-positive ready signal).
Tested working on my OnePlus Open w/ Android 15.
-------------
* Other fixes: minor change to logging format to include level indicator
* Fix warning in cargo makepad android
* Avoid re-entrant borrows of the IOS_APP global
I noticed this was happening any time the IME on iOS was used,
so I restructured those usages of IOS_APP to avoid them.
I then noticed that it could happen in other places, so I refactored
those as well.
* Fix missing iOS plist entry