Update vello_cpu to 0.0.7, which produces slightly different
rasterization output. All snapshot tests have been regenerated.
Note: skrifa cannot be bumped to 0.41.0 in this PR because it
pulls in read-fonts 0.38, while harfrust 0.5.2 depends on
read-fonts 0.37. The two FontRef types are incompatible.
The text shaper doesn't handle tab stops — override the advance width
to TAB_SIZE × space width in layout_shaped_run, matching the previous
character-by-character behavior.
These were never updated on our branch. Text shaping changes
the rendered output for text-heavy side containers.
Local UPDATE_SNAPSHOTS=1 confirms only sides/ and rotated/
snapshots needed updating — layout/, visuals/ etc. pass as-is.
- Update GlyphAllocation.id docstring to reflect its actual usage
(overflow character kerning via legacy kern table)
- Replace planning artifact comment on shaper y_offset caching
- Document script-mixing limitation in segment_into_runs
Two bugs in layout_shaped_run:
1. When the shaper returned NOTDEF, glyph_info was resolved via font
fallback but the returned FontFaceKey was discarded. The glyph was
then allocated with run.font_key (the face that couldn't render it),
causing the glyph to silently render as invisible. Now uses the
correct fallback font face and its metrics for both allocation and
Glyph font_face_height/font_face_ascent.
2. prev_cluster was not reset between runs. Since harfrust cluster
values are byte offsets within each run's text, comparing clusters
across runs is semantically wrong and could skip extra_letter_spacing
at run boundaries (e.g. when both runs start at cluster 0).
This PR fundamentally solves the same problem as
https://github.com/emilk/egui/pull/5827 just implemented with a lot less
ambition and api surface on my end. It contains the bare minimum amount
of changes that I need in order to be able to solve my problem.
My Problem:
I am still suffering from the problem that the TypeIdMap blows up over a
very long time when using my application. (The user generally never
turns off the application, it is intended to be just kept running
forever, some users also never restart their computers) My application
generates a lot of content dynamically so it may for some time display
widgets with a certain set of TypeId's + Id's later hiding them. Some of
the elements that were hidden may turn visible again once an external
event occurs, some may forever be discarded.
I do know myself when which sections of the UI have to be purged because
they will never become visible again, so this PR contains the minimum
amount of necessary functions that allow me to implement this
housekeeping logic on my end.
The existing facilities are insufficient to handle this as the type T
which the TypeId and the hash is derived from are sometimes pub(crate)
privates of widget subcrates or even pub(crate) of egui internals
itself, so its impossible to manually remove those from the TypeIdMap
the only build-in method to remove them is to call "clear" on the
TypeIdMap, however that gets rid of everything, even the elements that
are still shown and should still be in the TypeIdMap.
If the changes in this PR are agreeable to you and you want me to write
unit test for the 4 functions that I have added then tell me and I will
write the tests for you.
If you need anything else changed please tell me.
I ran cargo clippy and cargo fmt, but your check.sh does not work on my
computer.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Remove copy-pasted docstring on segment_into_runs, and set
BEGINNING_OF_TEXT on the first run of every paragraph segment
(not just the first segment) to match END_OF_TEXT behavior.
Verify that letter pairs like AV, VA, AT are measurably tighter
when laid out together than the sum of their individual widths.
This test fails without harfrust text shaping (kern adjustment ≈ 0)
and passes with it (kern adjustment > 0.5px).
Replace the character-by-character glyph lookup in `layout_section()`
with a proper text shaping pipeline using harfrust (a Rust port of
HarfBuzz). This enables GPOS kerning, ligatures (fi, fl), and correct
positioning of combining diacritical marks.
The shaping pipeline works as follows:
1. Split text into font-fallback runs (grapheme-cluster-aware)
2. Shape each run with harfrust (GSUB + GPOS)
3. Allocate and position glyphs from the shaping output
Key changes:
- Add harfrust, unicode-segmentation, unicode-general-category deps
- Cache ShaperData on FontFace (parsed GSUB/GPOS tables)
- Add shape_text() with buffer flags and variable font support
- Add allocate_glyph_by_id() for shaper-produced glyph IDs
- Recycle harfrust UnicodeBuffer across layout calls
- Handle NOTDEF fallback (combining marks via unicode-general-category)
Addresses #2517.
Closes#7947
## Problem
`Visuals::interact_cursor` stopped working for buttons after the
`AtomLayout` refactor in commit 6eb7bb6e. Setting `interact_cursor` to
e.g. `CursorIcon::PointingHand` no longer changes the cursor when
hovering over a `Button`.
## Root Cause
When `Button` was rewritten to use `AtomLayout` in #5830, the
cursor-override block at the end of the old `Button::ui` was not carried
over to the new `Button::atom_ui` method.
The old code had:
```rust
if let Some(cursor) = ui.visuals().interact_cursor {
if response.hovered() {
ui.ctx().set_cursor_icon(cursor);
}
}
```
This was the only place `interact_cursor` was checked, so the setting
became entirely non-functional.
## Fix
Re-add the same `interact_cursor` check in `Button::atom_ui`, right
after painting and before `widget_info`, matching the original behavior.
---------
Co-authored-by: easonysliu <easonysliu@tencent.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
When using `egui::ViewportBuilder::with_fullsize_content_view` one must
be careful not to paint anything where the "traffic light" buttons are:
<img width="87" height="47" alt="image"
src="https://github.com/user-attachments/assets/0e878c8e-7141-4fed-bbc8-4d542ddb5251"
/>
`eframe::WindowChromeMetrics` helps you with that!
## Summary
* Closes#5229
* Closes#7776
On Windows, once a window is hidden with
`ViewportCommand::Visible(false)`, two problems occur:
1. **Window can never be shown again** — Windows stops sending
`RedrawRequested` events to invisible windows, and viewport commands are
only processed during `run_ui_and_paint`, which is triggered by
`RedrawRequested`. This creates a deadlock:
```
Visible(false) → window hidden → no RedrawRequested → run_ui_and_paint never called → Visible(true) stuck in queue → window stays hidden forever
```
2. **High CPU usage** — The event loop spins at full speed with
`ControlFlow::Poll` even for invisible windows, and repaint requests are
scheduled immediately, causing a tight loop that burns CPU.
## Fix
**For #5229:** In `check_redraw_requests`, after calling
`window.request_redraw()`, detect invisible windows via
`window.is_visible() == Some(false)` and call `run_ui_and_paint`
directly for them. This ensures pending viewport commands (including
`Visible(true)`) are still processed even when the OS doesn't send
redraw events.
**For #7776:** Three layers of throttling for invisible windows:
- **Heartbeat scheduling:** After painting an invisible window, schedule
the next repaint 100ms in the future (instead of immediately). This
keeps viewport commands flowing while limiting to ~10 repaints/sec.
- **Event throttling:** In `user_event`, throttle `RequestRepaint`
events for invisible windows to at least 100ms delay, preventing egui's
repaint callback from bypassing the heartbeat.
- **ControlFlow fix:** Only set `ControlFlow::Poll` for visible windows.
Invisible windows use `WaitUntil` instead of spinning.
- **Backend sleep:** Add `is_visible() == Some(false)` alongside the
existing `is_minimized()` sleep check in both wgpu and glow backends
(defense in depth).
The fix is platform-agnostic: `is_visible()` returns `Some(false)` only
when the platform can confirm invisibility, so it won't trigger on
platforms where invisible windows still receive `RedrawRequested`.
## Test plan
- [x] `cargo fmt` passes
- [x] `cargo clippy -p eframe --all-features` passes with no warnings
- [x] Manual test on Windows: window reappears after `Visible(true)`
when hidden
- [x] Manual test on Windows: CPU stays near 0% while window is
invisible (was ~16% before fix)
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
feat: Add left_text() to egui::Button
This PR introduces the `left_text()` method to `egui::Button`. It
enables placing additional text content on the left side of the button's
primary label, which is useful for displaying auxiliary information,
labels, and for facilitating left-aligned text within the button.
```rust
let is_selected = true;
let selectable_label_widget = egui::Button::selectable(
is_selected,
"",
).left_text("Left");
let desired_width = ui.available_width();
let desired_height = ui.spacing().interact_size.y;
interaction_response = ui.add_sized(
egui::vec2(desired_width, desired_height),
selectable_label_widget,
);
```