Commit graph

2,205 commits

Author SHA1 Message Date
Kevin Boos
3cea041f23 Optimize text layout and PortalList widget performance (#1041)
* Android: fix nondeterminstic crashes when using a bad surface

Makepad apps on Android were randomly crashing within the `Cx::render_view`
callstack when the host Activity surface was recycled, e.g., on
background/foreground transitions, rotation, IME show/hide, etc.

Details of the change are generated below:
----

`SurfaceHolder.Callback.surfaceDestroyed` posted a fire-and-forget message
to the render thread and returned to Android immediately, leaving two
overlapping races:

1. The render thread could drain `SurfaceDestroyed` from the mpsc channel,
   call `destroy_surface()` (which does `eglMakeCurrent(NULL, NULL, NULL,
   NULL)` and nulls the EGL surface), then in the *same* `RenderLoop`
   iteration call `handle_drawing()` → `render_view()` and issue GL calls
   with no current EGL context.
2. Even when our Rust side was well-behaved, Android was free to recycle
   the underlying buffer queue the moment `surfaceDestroyed` returned to
   Java, while the render thread was still mid-frame against it.

**Layer 1 — Surface validity tracking.** Added `CxOs::surface_alive`,
flipped synchronously in `SurfaceCreated`/`SurfaceChanged`/`SurfaceDestroyed`
handlers, plus a `CxOs::has_drawable_surface()` helper that gates every
GL/Vulkan dispatch entry point: `main_loop`'s `handle_drawing()`,
`draw_pass_to_window_for_active_backend`, `draw_pass_to_texture_for_active_backend`,
`draw_pass_to_fullscreen`, and `eglSwapBuffers` in `present_window_for_active_backend`.

**Layer 2 — Synchronous Java↔Rust handshake.** `FromJavaMessage::SurfaceDestroyed`
now carries an `Arc<(Mutex<bool>, Condvar)>` ack channel. The JNI binding
blocks the Android UI thread on the condvar (2-second budget, well under
the 5-second ANR threshold) until the render thread confirms it has
released the surface. This is the same pattern `android.opengl.GLSurfaceView`
uses, and it closes the underlying-buffer-recycled-mid-frame race.

**Layer 3 — Defense-in-depth re-bind.** `draw_pass_to_fullscreen` now calls
a new fallible `try_make_current()` every frame, recovering from any GL
context drift caused by foreign code or our own teardown path, and
bailing cleanly if the bind fails instead of crashing inside the driver.

Verified all 5 gated entry points against the `openxr_render_loop` →
`openxr_handle_repaint` path:

- 4 entry points are unreachable in XR mode (XR uses its own swapchains
  and `xrEndFrame`, never `eglSwapBuffers` or the popup overlay path).
- `draw_pass_to_texture_for_active_backend` IS reachable (off-screen UI
  textures composited into the XR scene). To prevent these from being
  wrongly skipped in Vulkan+XR mode after the host surface is recycled,
  added an explicit XR escape hatch to `has_drawable_surface()`: when
  `in_xr_mode && openxr.session.is_some()`, return `true` based purely on
  `vulkan.is_some()`, ignoring the (intentionally nulled) `display.window`.
  This matches the existing `keep_xr_backend_alive` logic in the
  `SurfaceDestroyed` handler.

- `destroy_surface` is now idempotent (safe to call when already null).
- `make_current` asserts on null surface with a descriptive panic message
  instead of crashing inside EGL.
- Lifecycle handlers verify surface creation actually succeeded before
  flipping `surface_alive` to `true` (no false-positive ready signal).

Tested working on my OnePlus Open w/ Android 15.

-------------
* Other fixes: minor change to logging format to include level indicator
* Fix warning in cargo makepad android

* Fix Android platform errors, mostly no draw on start/resume

Also a small related optimization on iOS

------------

* Android: JNI method ID caching (`ndk_utils.rs`)
  * Rewrote the `call_method!` macro to cache `jmethodID` in a per-call-site `static AtomicPtr`. Previously, every JNI call allocated two `CString`s and called `GetObjectClass` + `GetMethodID` from scratch. Now these are resolved once and reused for all subsequent calls. Also deletes the local class reference after first resolution.

* Android: `to_java_update_tex_image` uses cached macro (`android_jni.rs`)
  * Replaced manual `GetObjectClass`/`CString::new`/`GetMethodID` calls with the now-cached `call_bool_method!` macro, eliminating per-frame JNI overhead for video texture updates.

* Android: Touch event coalescing (`android.rs`)
  * When draining pending messages before a RenderLoop frame, consecutive pure-Move touch events are now coalesced — only the last position is dispatched. Start/Stop events are never dropped. This reduces redundant event dispatch during active touch scrolling.

* Android: Black screen fix (`android.rs`)
  * Added `needs_first_draw` flag to `CxOs`. When a `RenderLoop` callback arrives but the surface isn't drawable, the flag is set. When the surface later becomes available, `redraw_all()` is called to ensure the first frame is painted. The flag is also set on `SurfaceDestroyed` so resuming from background always gets a guaranteed first frame.

* iOS: Remove unnecessary `passes_todo.clone()` (`ios.rs`)
  * Removed a redundant `Vec::clone()` in the popup pass draw loop — both the outer and inner loops borrow `passes_todo` immutably.

* Optimize text layout and PortalList widget performance

Text layout: eliminate redundant HarfBuzz reshaping (layouter.rs)
- Replace `can_fit()` reshaping of cumulative multi-word substrings (always cache misses) with summing pre-computed per-word widths
- Replace `fit()` reshaping with concatenation of cached per-word `ShapedText` results, adjusting cluster offsets
- Cache each word's `Rc<ShapedText>` in the Fitter constructor for reuse

PortalList: O(1) reusable item pool lookup (portal_list.rs)
- Change `reusable_items` from `Vec<WidgetItem>` (O(n) scan + O(n) remove) to `HashMap<LiveId, Vec<WidgetItem>>` (O(1) lookup + O(1) pop)

PortalList: skip touch cursor hit-testing (portal_list.rs)
- Gate `point_hits_interactive_item()` behind `!e.device.is_touch()` to skip expensive recursive widget-tree walk on touch devices where there is no cursor

* undo unnecessary change to inter-word/ligature width estimation

avoid problems with words'/ligatures' kerning
2026-04-15 11:47:47 +02:00
Kevin Boos
e0c6e8b3c5 Fix emojis in Html by removing broken font loading optimization (#1044)
The font_member_is_needed_for_text() optimization pre-scanned text content
to decide whether to skip loading CJK/emoji font members. This was broken
in two ways:

1. The hardcoded Unicode ranges in is_emoji_char() were incomplete (e.g.,
   missing U+2B50 ), so emoji glyphs silently failed to render.

2. When members were skipped, font_ids.len() never matched
   expected_member_count, so is_font_family_complete() never returned true
   for i18n font families. This caused every draw call to hit the slow
   path — the opposite of the optimization's intent.

Fix: load all font family members unconditionally. The one-time load cost
is negligible, and the fast path (is_font_family_complete → early return)
now works correctly on subsequent draws.

Removed the now-unused helpers: font_member_is_needed_for_text(),
is_cjk_fallback_font_path(), is_emoji_fallback_font_path(),
resource_basename(), text_has_cjk(), is_cjk_char(), text_has_emoji(),
is_emoji_char(), and FontFamily::ensure_fonts_loaded_for_text().
2026-04-15 10:59:49 +02:00
Kevin Boos
899d410198 Ensure .notdef glyph for unsupported characters is visibly drawn (#1045)
* Fix emojis in Html by removing broken font loading optimization

The font_member_is_needed_for_text() optimization pre-scanned text content
to decide whether to skip loading CJK/emoji font members. This was broken
in two ways:

1. The hardcoded Unicode ranges in is_emoji_char() were incomplete (e.g.,
   missing U+2B50 ), so emoji glyphs silently failed to render.

2. When members were skipped, font_ids.len() never matched
   expected_member_count, so is_font_family_complete() never returned true
   for i18n font families. This caused every draw call to hit the slow
   path — the opposite of the optimization's intent.

Fix: load all font family members unconditionally. The one-time load cost
is negligible, and the fast path (is_font_family_complete → early return)
now works correctly on subsequent draws.

Removed the now-unused helpers: font_member_is_needed_for_text(),
is_cjk_fallback_font_path(), is_emoji_fallback_font_path(),
resource_basename(), text_has_cjk(), is_cjk_char(), text_has_emoji(),
is_emoji_char(), and FontFamily::ensure_fonts_loaded_for_text().

* Ensure .notdef glyph for unsupported characters is visibly drawn

When a character isn't in any font in the family (e.g., 🫪 U+1FAEA not in
the bundled NotoColorEmoji), the shaper exhausts all fallback fonts and
emits glyph id 0 (.notdef) from the last font tried. For bitmap-only fonts
like NotoColorEmoji (no glyf table), the .notdef has no outline, so the
rasterizer returns None and draw_glyph silently skips it — leaving
invisible blank space.

Fix: in shape_recursive, when all fallback fonts are exhausted and glyphs
remain unmapped (id == 0), reassign them to the primary font (IBM Plex
Sans), whose .notdef has visible contours (the standard "tofu" rectangle).
This is done at the point of emission rather than as a post-processing
scan, so the common case (all glyphs found) has zero overhead.
2026-04-15 09:28:21 +02:00
Kevin Boos
244c4cb7f5 Fully proper fix for black screen on Android start/resume (#1043)
* 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
2026-04-15 08:22:35 +02:00
Kevin Boos
e35635a423 Windows: fix perf and flicker/stretch artifacts when resizing a window (#1036)
* 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
2026-04-13 21:51:22 +02:00
Kevin Boos
94fe8f86fd Fix Android platform errors, mostly no draw on start/resume (#1035)
* 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.
2026-04-13 21:51:09 +02:00
Kevin Boos
8f20a63ca2 Avoid double-shutdown Event dispatch on WIndows (#1034)
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.
2026-04-13 21:50:55 +02:00
Admin
6cffd711c7 cuda otw 2026-04-13 11:09:08 +02:00
Admin
9809128567 cuda exact chat reuse and decode tuning 2026-04-13 11:09:08 +02:00
Admin
b5e31a352a wip cuda long-context profiling 2026-04-13 11:05:49 +02:00
Admin
77904dec0d cuda prefill metrics and chunked kernels 2026-04-13 11:05:49 +02:00
Admin
90ae117d84 cuda exact prefill to ~87 tok/s 2026-04-13 11:05:49 +02:00
Admin
d2d8e0459b Add FLUX warm pipeline and reference benchmarks 2026-04-13 11:01:13 +02:00
Admin
33261dcfc7 flux 1 works 2026-04-13 11:01:13 +02:00
Admin
43ac724e91 rotor quant for metal 2026-04-13 11:01:13 +02:00
Admin
8026215d2f Document Rotor divergence against BF16 baseline 2026-04-13 11:01:13 +02:00
Admin
f79724af4f Add optional Rotor-style K-cache compression for Gemma 4 2026-04-13 11:01:13 +02:00
alanpoon
5af1c812fc audio_input_panic_fix for macos (#1038) 2026-04-11 13:52:25 +02:00
Admin
b50c05dfe7 Add CUDA NVFP4 backend and GPU decode path 2026-04-11 11:44:04 +02:00
Admin
4d0a4c44f0 mlx image opt 2026-04-11 09:51:37 +02:00
Admin
a723c0f32a fix sutdio 2026-04-10 22:06:51 +02:00
Admin
bff75eb694 fix 2026-04-10 21:14:32 +02:00
Admin
0a8376e39f mlx multimodal 2026-04-10 15:10:25 +02:00
Admin
33c1dd0dfa tabs 2026-04-10 14:48:17 +02:00
Admin
9ca672088f mlx optimisations 2026-04-10 14:48:17 +02:00
Admin
8f542c0d8a mlx working 2026-04-10 14:48:17 +02:00
Admin
1d1e91dae7 cleanup 2026-04-10 14:48:17 +02:00
Admin
e19a79d5a4 mlx otw 2026-04-10 14:48:17 +02:00
Admin
69aa615947 mlx 5x->2x 2026-04-10 14:46:22 +02:00
Admin
3d0171c8d6 mlx 2026-04-10 14:46:22 +02:00
Admin
f30a62422f mlx otw 2026-04-10 14:46:22 +02:00
Admin
00eed2e268 mlx otw 2026-04-10 14:46:22 +02:00
Kevin Boos
3dfd8e7b97 Android: fix nondeterminstic crashes when using a bad surface (#1030)
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
2026-04-10 08:13:14 +02:00
Kevin Boos
17f2ef553c Batch/buffer tooltip hover in/out actions to avoid flicker (#1029) 2026-04-10 08:12:50 +02:00
Ruben Daniels
c1056ea4f8 fix(base64): handle single '=' padding in base64_decode (#1027)
The decoder only checked for double padding ('==') at input[len-2],
subtracting 1 output byte. Single padding ('=') at input[len-1] was
not handled, leaving 1 extra garbage byte in the decoded output.

This affected 2 out of 3 input lengths (any input where len % 3 == 2),
producing decoded output 1 byte longer than expected.

Fix: check input[len-1] for '=' first (subtract 1 byte), then check
input[len-2] for '=' (subtract another byte for double padding).

Added 7 roundtrip tests covering: no padding (3n bytes), single
padding (3n+2 bytes), double padding (3n+1 bytes), empty input,
lengths 1-20, all 256 byte values, and URL-safe alphabet.

Co-authored-by: prime intellect <prime@prime-intellects-Mac-Studio.local>
2026-04-09 20:05:45 +02:00
Kevin Boos
9e98210489 Avoid re-entrant borrows of the IOS_APP global (#1025)
* 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
2026-04-09 14:45:37 +02:00
Kevin Boos
175caf97ff Packaging directory-related fixes (for robius-packaging-commands to work) (#1024)
* Packaging directory-related fixes (to allow robius-packagin-commands to work)

* use proper resource loading for macOS packaged apps via NSBundle
2026-04-09 14:45:23 +02:00
Kevin Boos
e40ff6db49 Flow right wrap fix (#1023)
* Fix `flow: Right` with `wrap: true`

This tiny math bug was causing widgets that got wrapped to the next line
in a `Flow: Right { wrap: true}` view to not get properly drawn
(the left side would get cut off).

* Expose `wrap_spacing` in `Layout` and splash script

This allows you to set the vertical spacing between widgets when
they wrap to the next line in a Right wrap flow layout.
2026-04-08 07:36:19 +02:00
Kevin Boos
e4968b1e24 Fix mouse wheel scrolling on Linux Wayland (#1022)
Now the tab bar will scroll using all four mouse wheel directions
even on Wayland.
2026-04-08 07:36:04 +02:00
Kevin Boos
29ab9371de Fix key repeat timer issue, and Event::Shutdown delivery (#1020)
Key repeat (holding down a key) did not work at all on Linux Wayland. The `RepeatInfo` event from the compositor was commented out, and all key events had `is_repeat: false` hardcoded.

- **`xkb_sys.rs`**: Added `xkb_state_get_keymap` FFI binding and a `key_repeats()` method on `XkbState` to check whether a key supports repeat (e.g., modifiers don't repeat).
- **`wayland_state.rs`**:
  - Added `KeyRepeatState` struct and `KEY_REPEAT_TIMER_ID` constant.
  - Added `key_repeat_rate`, `key_repeat_delay`, and `key_repeat` fields to `WaylandState`.
  - Handled the previously-ignored `RepeatInfo` event to store the compositor's repeat rate/delay.
  - On key press: start a one-shot timer with the repeat delay if the key supports repeat.
  - On key release / keyboard leave: cancel the repeat timer.
  - Added `handle_key_repeat_timer()` which fires `KeyDown(is_repeat: true)` and `TextInput` events, transitioning from the initial delay to a steady-state repeating timer.
- **`wayland_app.rs`**: Intercept the key repeat timer ID in the event loop and route it to `handle_key_repeat_timer()` instead of sending a generic `Timer` event.

- **`raw_input.rs`**: The evdev backend already received `KeyAction::KeyRepeat` from the OS but hardcoded `is_repeat: false` and didn't emit `TextInput` events. Fixed both.

- **`select_timer.rs`**: Fixed a pre-existing bug in `stop_timer` where removing a timer from the delta chain didn't adjust the successor's `delta_timeout`. This caused successor timers to fire early by the removed timer's delta. Also changed `update_timers` to use `pop_front()` instead of `stop_timer()` internally, since `select_time_used` already accounts for the removed timer's delta.

--------------

On Linux Wayland (and several other platforms), Makepad apps never received
`Event::Shutdown` when the window was closed via the client-side decoration
close button or when the app called `cx.quit()`.

When the CSD close button is clicked, it pushes `CxOsOp::CloseWindow`, which
is processed by `handle_platform_ops()`. When the last window is removed (or
`CxOsOp::Quit` is handled), this function returns `EventFlow::Exit`. However,
most backends did **not** call `Event::Shutdown` before exiting — only macOS
did it correctly.

Added `call_event_handler(&Event::Shutdown)` in the `handle_platform_ops() → Exit`
path for all affected backends, matching the existing macOS behavior:

- **Linux Wayland** (`linux_wayland.rs`) — added Shutdown call
- **Linux X11** (`linux_x11.rs`) — added Shutdown call
- **Windows** (`windows.rs`) — added Shutdown call
- **Linux Direct** (`linux_direct.rs`) — added Shutdown call
- **OpenHarmony** (`open_harmony.rs`) — added Shutdown call after main loop exit
  (this backend uses `self.os.quit` instead of `EventFlow::Exit`)

- **macOS** — already correct
- **Android** — Shutdown is delivered via `FromJavaMessage::Destroy`
- **iOS / tvOS / Web** — different lifecycle models where explicit shutdown
  doesn't apply (suspended by OS, or no reliable browser mechanism)
2026-04-08 07:35:47 +02:00
Kevin Boos
50723966fb cargo-makepad: support builds that need cmake/bindgen (Android/iOS) (#1019)
* cargo-makepad: support proper NDK builds (and on iOS)

The newly-emerging `aws-lc-rs` crate is quite popular and is
gradually replacing `ring`, which means we need to support it,
which is especially difficult to get right on Android and iOS targets.

This changeset makes it really easy to build that crate as part of
your app, if desired. There's no cost to apps that don't use it.

Android: make the stripped NDK installation the default, and make the
`full-ndk` option actually install the FULL NDK, not just the full set
of prebuilts. Like the whole thing, including build tooling like cmake
and other libraries.

* cargo-makepad: make install-toolchain for android more robust

Overwrite an existing installation instead of erroring out
2026-04-08 07:35:29 +02:00
Kevin Boos
c0f49184c9 Support scrolling while centered, both vertically and horizontally (#1017)
* Support scrolling while centered, both vertically and horizontally

The previous turtle logic didn't allow you to center-align a view
while still making it scrollable. This small fix supports that now,
meaning that you can have:
* a vertically-centered view (`Align: { y: 0.5 }`) that is y-scrollable
* a horizontally-centered view (`Align: {x: 0.5 }`) that is x-scrollable

Also added some simple examples of this to `uizoo`

* fix iOS build
2026-04-07 09:32:47 +02:00
Kevin Boos
aed71d2b14 Smooth scroll the tab bar to the selected tab, if not visible (#1016)
* 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.
2026-04-06 23:00:23 +02:00
Kevin Boos
8d356ec85d Show "ghost" dock tab during drag animation. Fix drag-n-drop rules, target zones, etc (#1015)
* 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.
2026-04-06 23:00:13 +02:00
Kevin Boos
3a030e88a5 Add ellipsis text truncation support (text_overflow + max_lines) (#1014)
* Add ellipsis text truncation support (text_overflow + max_lines)

Re-implement ellipsis truncation for text that overflows its container,
following conventions from CSS (text-overflow), Android (TextOverflow), and
Flutter (TextOverflow). This was supported in Makepad 1.0 but removed in 2.0.

Full summary below:

-----------------------

- Add `max_rows: Option<usize>` and `ellipsis: bool` fields to `LayoutOptions`
- Implement `apply_ellipsis_truncation()` post-processing step that:
  - Detects when text was truncated (by max_rows or single-line overflow)
  - Shapes the "…" (U+2026) glyph using the same font family
  - Removes trailing glyphs from the last visible row to make room
  - Trims trailing whitespace before the ellipsis for clean appearance
  - Appends the ellipsis glyph(s) to the last row
  - Recalculates the text bounding box
- Add early-exit in `layout_by_word()` and `layout_by_grapheme()` when
  `max_rows` is exceeded, avoiding unnecessary layout work for long texts
- Add `is_truncated: bool` field to `LaidoutText` for consumer detection
- Fix pre-existing bugs in `LayoutOptions` Hash/PartialEq: `wrap` and
  `line_spacing_scale` were missing, which could cause stale cache hits

- Add `TextOverflow` enum with `Clip` (default) and `Ellipsis` variants
- Add `max_lines: usize` and `text_overflow: TextOverflow` live properties
  on `DrawText`, passed through to `LayoutOptions`
- Register `TextOverflow` in the script module for DSL access
- Resolve `Fit` width max bounds when ellipsis/max_lines is active, so
  text layout knows the width constraint even for Fit-sized containers

- **Label** (widgets/src/label.rs): Add top-level `max_lines` and
  `text_overflow` properties, forwarded to `draw_text` in `draw_walk()`
- **TextFlow** (widgets/src/text_flow.rs): Same top-level properties,
  forwarded before each text draw call
- **Html / Markdown**: Inherit from TextFlow via `#[deref]` automatically
- Add `..mod.text` to widget prelude (widgets/src/lib.rs) so `Ellipsis`
  and `Clip` are accessible in all widget DSL

- Make `FitBound::eval_width()` and `eval_height()` public (draw/src/turtle.rs)
  so DrawText can resolve Fit max bounds during layout

```rust
// Simple single-line ellipsis
Label {
    width: Fill
    max_lines: 1
    text_overflow: Ellipsis
    text: "Long text gets truncated…"
}

// Multi-line with ellipsis
Label {
    width: Fill
    max_lines: 3
    text_overflow: Ellipsis
    text: "Wraps up to 3 lines, then truncates…"
}

// Also works via draw_text for any widget with DrawText
Button {
    draw_text +: { max_lines: 1, text_overflow: Ellipsis }
}
```

See the demos I newly added to the `uizoo` example too.

* Fix TextFlow widget-level ellipsis for multi-run styled text (like Html)

The per-run forwarding of max_lines/text_overflow to DrawText caused each
styled run (bold, italic, etc.) to independently truncate with its own
ellipsis, producing double "……" artifacts in Html/Markdown content.

- Add `lines_drawn` and `content_truncated` fields to track visual lines
  across all text runs within a single TextFlow
- Compute per-run `max_rows` based on remaining visual lines instead of
  blindly forwarding the widget's `max_lines` to every DrawText call
- Handle continuation runs (starting mid-line) correctly: they get +1
  row allowance since their first row shares the current visual line
- Skip further text runs once a run reports `is_truncated` (ellipsis drawn)
- Skip non-continuation runs when no visual lines remain

- `draw_walk_resumable_with` now returns `(usize, bool)`: row count and
  whether the layout was truncated, so TextFlow can track state across runs

- Add three Html ellipsis examples: 1-line, 2-line styled, and emoji+styled
2026-04-06 16:29:24 +02:00
Kevin Boos
07c55f65a9 Dock: add touch support for Tab/Tab bar interactions (scroll, drag/drop) (#1013)
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.
2026-04-06 16:29:13 +02:00
Kevin Boos
f36f2e58ad TextInput: support single-line horizontal scrolling when text overflows (#1012)
* TextInput: support single-line horizontal scrolling when text overflows

When a single-line TextInput's text content is wider than its visible area
(due to Fill, Fixed, or parent-constrained Fit width), the text now
automatically scrolls horizontally to keep the cursor visible.

- Add `scroll_x` tracking with auto-scroll-to-cursor logic
- Push a clip rect for all TextInput modes (not just multiline) to prevent
  text from bleeding outside widget bounds
- Layout single-line text without max_width constraint so overflow is
  detectable via `size_in_lpxs.width`
- Handle mouse wheel/trackpad scroll events for single-line horizontal
  scrolling (maps both axes to horizontal)
- Account for `scroll_x` in IME position and selection rect calculations
- Add `Turtle::set_width()` and `Cx2d::compute_max_width_from_ancestors()`
  (width counterparts to existing height methods)
- Add UIZoo examples: Fill width, Fixed width, and Fit-in-container

* a bit more cleanup, no need for clip size to be an option
2026-04-06 16:28:35 +02:00
Admin
a2f3036d03 remove logs 2026-04-05 11:36:11 +02:00
Admin
f6c32eae92 stdin 2026-04-05 11:30:53 +02:00
Admin
d9d92a557e llama works 2026-04-05 11:30:53 +02:00