We had a ton of issues around `TexturesDelta` that weren't properly
applied because we early-out of some function:
* https://github.com/emilk/egui/pull/8313
* https://github.com/emilk/egui/pull/8250
* https://github.com/emilk/egui/pull/8279
This PR changes texture updates, so that we always store them after
taking them out of `FullOutput` and keep the delta around until it's
actually applied (by passing &mut refs and draining instead of
iterating). So even if we add a new early return somewhere, that can't
break texture updates.
It also optimizes `TexturesDelta::append` by dropping any previous
deltas if there's a new `whole` delta or a `free`.
It also adds a debug assert that any `TexturesDelta` is empty when
dropped, as an additional safeguard in case the bug sneaks back in.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Closes <https://github.com/emilk/egui/issues/8115>
* [x] I have followed the instructions in the PR template
## Summary
- Recalculate a menu popup's cached `Area` size when it reopens.
- Preserve cached sizing for continuously open menus and leave tooltips
and general popups unchanged.
- Add a headless regression test covering a wider item added while the
menu is closed.
## Root cause
`Area` keeps its cached size after a menu closes. When that menu
reopened with wider content, the
cached width constrained the new item and caused it to wrap instead of
allowing the popup to grow.
The fix requests the same invisible sizing pass used for a first-open
`Area` whenever a menu was not
open during the previous frame.
## User impact
Menus now expand to fit newly added wider items after reopening.
Existing wrapping, explicit-width,
alignment, screen-constraining, and continuously open menu behavior
remain unchanged.
## Validation
- `cargo test -p egui_kittest --test menu`
- `cargo check -p egui`
- `cargo fmt --all -- --check`
The three methods for showing a `Panel` are now:
* `panel.show`: always show the panel.
* `panel.show_collapsible`: show or hide the panel, with a slide
animation in between.
* `Panel::show_switched`: animate between two different panels: a
thin/collapsed one and a thick/expanded one.
* part of #7264
* based on https://github.com/emilk/egui/pull/8152
The resize fix allows use to really simplify how the Window Titlebar is
rendered. Previously it was using some complex flow to calculate and
allocate the height first and then render it later once we knew the
windows final width.
Since now windows can't shrink past their minimum content widths, I can
just show the titlebar inline with the regular content, just outside of
the `Resize` container so that it is always visible.
This does change what the size of a window means. Before, size was just
the size of the contents, while now size (e.g. via min_height) will
include the Frames margin and outline, title bar and the contents.
Also, the window label now truncates as you shrink the window (meaning
windows can now be smaller than their label allows).
---------
Co-authored-by: lucasmerlin <8009393+lucasmerlin@users.noreply.github.com>
Fixes some bugs that happen randomly when resizing horizontal_wrapped
texts:
https://github.com/user-attachments/assets/141392d2-0239-465a-ba7b-c864f7823319
Adds regression tests (I enjoy using claude to fix these bugs, first
have it create a minimal repro test case, then fix the bug by iterating
until it figures out a fix).
* Related to #56 (Improve text — tracking issue)
## Summary
This PR integrates [harfrust](https://crates.io/crates/harfrust) (a
pure-Rust port of HarfBuzz) into epaint's text layout pipeline,
replacing the character-by-character glyph positioning with proper
OpenType text shaping.
### What this enables
- **GPOS kerning**: most modern fonts only ship kerning in GPOS tables
(not the legacy `kern` table). Pairs like "AV", "VA", "AT" are now
properly tightened.
- **GSUB substitutions**: ligatures (fi, fl), contextual alternates, and
other OpenType features.
- **Combining marks**: diacritics (e.g. ɔ̃) are positioned via anchor
tables instead of being rendered as standalone replacement glyphs.
### Before/After
#### Kerning, etc.
<img width="838" height="726" alt="before_main"
src="https://github.com/user-attachments/assets/f0f26d5f-b117-43a6-b39c-ea40d2e73836"
/>
<img width="838" height="726" alt="after_harfrust"
src="https://github.com/user-attachments/assets/d983e5da-486c-4f39-bd4f-5782a90c6b39"
/>
#### Ligatures
<img width="1117" height="698" alt="before_closeup"
src="https://github.com/user-attachments/assets/7a3b08b4-cf6f-45b7-98ba-07c473cd3b02"
/>
<img width="1117" height="698" alt="after_closeup"
src="https://github.com/user-attachments/assets/6cfc5f21-d32f-4f09-be0c-59c8c553d44f"
/>
### Architecture
The shaping integrates into the existing pipeline without changing the
public API:
1. **`Font::segment_into_runs`** — segments text into contiguous runs by
font face (grapheme-cluster aware, never splits combining sequences)
2. **`FontFace::shape_text`** — calls harfrust to shape each run,
returning glyph IDs + positioned advances/offsets
3. **`layout_shaped_run`** — emits `Glyph` structs from the shaping
output, with NOTDEF fallback to other font faces for missing glyphs
4. **Buffer recycling** — `FontsImpl` pools a `harfrust::UnicodeBuffer`
to avoid per-layout allocations
### Disclaimer
I'm far from being a good Rust programmer. Claude Code did most of the
heavy lifting here. I did my best and used my limited knowledge to avoid
making too many mistakes. If this PR isn't up to quality standards,
please don't hesitate to close it.
## Test plan
- [x] `cargo test -p epaint` — all 18 text tests pass, including 6 new
ones
- [x] `cargo clippy -p epaint --all-features` — clean
- [x] `cargo fmt` — clean
- [ ] Snapshot tests need regeneration (expected: shaping changes glyph
positions)
- New tests added:
- `test_gpos_kerning` — verifies GPOS kerning tightens "AV", "VA", "AT"
pairs
- `test_combining_diacritics` — combining tilde doesn't add extra width
- `test_shaping_basic_latin` — sanity check for Latin text
- `test_shaping_empty_string` — empty input doesn't panic
- `test_shaping_multiple_newlines` — newline splitting works correctly
- `test_shaping_mixed_font_fallback` — Latin + emoji in same string
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Summary
This PR fixes submenu keyboard parity: pressing Enter/Space on an
already-open submenu button now collapses that submenu (matching
top-level menu button behavior).
What changed
Updated submenu interaction logic to distinguish pointer primary clicks
from keyboard/accessibility-triggered clicks.
Kept pointer/touch behavior unchanged (submenu button clicks still don’t
auto-close submenu).
Added regression tests for:
keyboard open of nested submenu,
keyboard close (second Enter) of nested submenu,
pointer clicks on submenu button keeping submenu open.
Validation
cargo test -p egui_kittest --test regression_tests
Breaking changes
None. Behavior change is limited to keyboard/accessibility activation of
already-open submenu buttons.
* Closes <https://github.com/emilk/egui/issues/7926>
* [ X ] I have followed the instructions in the PR template
---------
Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
- fix for https://github.com/rerun-io/reality/pull/1075
The galleys row size was calculated by looking at the last glyphs pos_x,
which got changed to be rounded to integers when we added subpixel
binning. This introduced a subtle bug which caused the width of galleys
to be slightly off.
This PR fixes this by looking at the actual cursor position instead,
which is not rounded.
Also added a test to ensure this is correct. Previously, for the second
and last line, the `x` was too close to the `0`.
<img width="48" height="67" alt="image"
src="https://github.com/user-attachments/assets/a69a4cc3-b3f3-4553-ab92-73cb2e7a358c"
/>
---------
Co-authored-by: lucasmerlin <8009393+lucasmerlin@users.noreply.github.com>
* Part of https://github.com/emilk/egui/issues/5605
This changes the default style of egui.
The problem with "expanding" widgets is that they now want to paint
outside their own bounds, which then requires all parent UIs to have
proper margins.
It also means hovered things are no longer properly aligned with every
other widget.
* Part of https://github.com/emilk/egui/issues/5113
* Part of https://github.com/emilk/egui/issues/3524
## What
This deprecates `eframe::App::update` and replaces it with two new
functions:
```rs
pub trait App {
/// Called just before `ui`, and in the future this will
/// also be called for background apps when needed.
fn logic(&mut self, ctx: &egui::Context, frame: &mut Frame) { }
/// Show your user interface to the user.
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame);
…
}
```
Similarly, `Context::run` is deprecated in favor of `Context::run_ui`.
`Plugin`s are now handed a `Ui` instead of just a `Context` in
`on_begin/end_frame`.
## TODO
…either in this PR or a later one
* [x] Deprecate `App::update`
* [x] Deprecate `Context::run`
* [x] Change plugins to get a `Ui`
* [x] Update kittest
* [x] Change viewports to get UI:s (`show_viewport_immediate` etc)
- https://github.com/emilk/egui/pull/7779
## Later PRs
* [ ] Deprecate `Panel::show`
* [ ] Deprecate `CentralPanel::show`
* [ ] Deprecate `CentralPanel` ?
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Closes N/A
* [x] I have followed the instructions in the PR template
I'll probably come back to this and clean it up a bit. This PR
reimplements ab_glyph's functionality on top of Skrifa, a somewhat
lower-level font API that's being used in Chrome now.
Skrifa doesn't perform rasterization itself, so I'm using
[vello_cpu](https://github.com/linebender/vello) from the Linebender
project for rasterization. It's still in its early days, but I believe
it's already quite fast. It also supports color and gradient fills, so
color emoji support will be easier.
Skrifa also supports font hinting, which should make text look a bit
nicer / less blurry.
Here's the current ab_glyph rendering:
<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/2385b66e-23f8-4c6e-b8c2-ea90e0eea4e4"
/>
Here's Skrifa *without* hinting--it looks almost identical, but there
are some subpixel differences, probably due to rasterizer behavior:
<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/a815f3e9-65ac-4940-bc00-571177bef53d"
/>
Here's Skrifa *with* hinting:
<img width="1592" height="1068" alt="image"
src="https://github.com/user-attachments/assets/d6cc0669-3537-4377-bba9-ed5ef09664db"
/>
Hinting does make the horizontal strokes look a bit bolder, which makes
me wonder once again about increasing the font weight from "light" to
"regular".
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* closes https://github.com/emilk/egui/issues/5674
This changes egui to create an AccessKit node for each `Ui`. I'm not
sure if this alone will directly improve accessibility, but it should
make it easier to create the correct parent / child relations (e.g.
grouping menus as children of menu buttons).
Instead of having a global stack of parent ids, they are now passed via
a parent_id field in `UiBuilder`.
If having all these `GenericContainer` nodes somehow is bad for
accessibility, the PR could also be changed to only create nodes if
there is actually some accessibility info with it (the relevant is
currently commented-out in the PR). But I think screen readers should
just ignore these nodes, so it should be fine? We could also use this as
motivation to git red of some unnecessary wrapped `Ui`s, e.g.
CentralPanel creates 3 Uis when 2 should be enough (the initial Ui and a
Frame, maybe we could even only show the `Frame` if we can give it an
UiBuilder and somehow show the Frame with `Ui::new`).
Here is a screenshot from the accessibility inspector
(https://github.com/emilk/egui/pull/7368) with this PR:
<img width="431" height="744" alt="Screenshot 2025-07-24 at 12 09 55"
src="https://github.com/user-attachments/assets/6c4e5ff6-5c38-450e-9500-0776c9018d8c"
/>
Without this PR:
https://github.com/user-attachments/assets/270e32fc-9c7a-4dad-8c90-7638c487a602
* Closes https://github.com/rerun-io/rerun/issues/11301
This fixes a bug where a menu could get stuck, not closing at all, when
the currently open submenu stops being shown.
I also added a way to reproduce this to the demo, as well as a test
ensuring that there is no race condition in the fix.
This adds a new mode, `UPDATE_SNAPSHOTS=force`, which will lower the
threshold to zero, overwriting every image that is not _exactly_ the
same.
Most comparisons has a threshold because different GPUs render slightly
differently. However, setting that threshold accurately can be hard.
Sometimes a test will pass locally, but fail on CI. In those cases you
want to force an update of the failing test. You can use
`UPDATE_SNAPSHOTS=force` for that.
And sometimes a small change _should_ update all images, but the change
is so tiny that it falls under the threshold. Still, you want to make a
point of showing that these images have changes. You can use
`UPDATE_SNAPSHOTS=force` for that.
The override_text_color is now used when rendering text from a String or
&str. This is consistent with the RichText variant and makes the option
behave as advertised, taking precedence over WidgetVisuals and
overriding the color for all text unless explicitly changed for a single
widget (via RichText or LayoutJob).
* Closes <https://github.com/emilk/egui/issues/7367>
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
I need to scroll in a snapshot test in my app, and kittest had no
utilities for this. Event::MouseWheel is error prone. This adds support
for some accesskit scroll actions, and uses this in kittest to add
helpers to scroll to a node / scroll the scroll area surrounding a node.
The accesskit code says down/up/left/right `Scrolls by approximately one
screen in a specific direction.`. Unfortunately it's difficult to get
the size of a "screen" (I guess that would be the size of the containing
scroll area)where I implemented the scrolling, so for now I've hardcoded
it to 100px. I think scrolling a fixed amount is still better than not
scrolling at all.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This adds a custom Node struct with proper support for egui types
(`Key`, `Modifiers`, `egui::Event`, `Rect`) instead of needing to use
the kittest / accesskit types.
I also changed the `click` function to do a proper mouse move / mouse
down instead of the accesskit click. Also added `accesskit_click` to
trigger the accesskit event. This resulted in some changed snapshots,
since the elements are now hovered.
Also renamed `press_key` to `key_press` for consistency with
`key_down/key_up`.
Also removed the Deref to the AccessKit Node, to make it clearer when to
expect egui and when to expect accesskit types.
* Closes#5705
* [x] I have followed the instructions in the PR template
Today each widget does its own custom layout, which has some drawbacks:
- not very flexible
- you can add an `Image` to `Button` but it will always be shown on the
left side
- you can't add a `Image` to a e.g. a `SelectableLabel`
- a lot of duplicated code
This PR introduces `Atoms` and `AtomLayout` which abstracts over "widget
content" and layout within widgets, so it'd be possible to add images /
text / custom rendering (for e.g. the checkbox) to any widget.
A simple custom button implementation is now as easy as this:
```rs
pub struct ALButton<'a> {
al: AtomicLayout<'a>,
}
impl<'a> ALButton<'a> {
pub fn new(content: impl IntoAtomics) -> Self {
Self { al: content.into_atomics() }
}
}
impl<'a> Widget for ALButton<'a> {
fn ui(mut self, ui: &mut Ui) -> Response {
let response = ui.ctx().read_response(ui.next_auto_id());
let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
ui.style().interact(&response)
});
self.al.frame = self
.al
.frame
.inner_margin(ui.style().spacing.button_padding)
.fill(visuals.bg_fill)
.stroke(visuals.bg_stroke)
.corner_radius(visuals.corner_radius);
self.al.show(ui)
}
}
```
The initial implementation only does very basic layout, just enough to
be able to implement most current egui widgets, so:
- only horizontal layout
- everything is centered
- a single item may grow/shrink based on the available space
- everything can be contained in a Frame
There is a trait `IntoAtoms` that conveniently allows you to construct
`Atoms` from a tuple
```
ui.button((Image::new("image.png"), "Click me!"))
```
to get a button with image and text.
This PR reimplements three egui widgets based on the new AtomLayout:
- Button
- matches the old button pixel-by-pixel
- Button with image is now [properly
aligned](https://github.com/emilk/egui/pull/5830/files#diff-962ce2c68ab50724b01c6b64c683c4067edd9b79fcdcb39a6071021e33ebe772)
in justified layouts
- selected button style now matches SelecatbleLabel look
- For some reason the DragValue text seems shifted by a pixel almost
everywhere, but I think it's more centered now, yay?
- Checkbox
- basically pixel-perfect but apparently the check mesh is very slightly
different so I had to update the snapshot
- somehow needs a bit more space in some snapshot tests?
- RadioButton
- pixel-perfect
- somehow needs a bit more space in some snapshot tests?
I plan on updating TextEdit based on AtomLayout in a separate PR (so
you could use it to add a icon within the textedit frame).
With kittest it was difficult to wait for images to be loaded before
taking a snapshot test.
This PR adds `Harness::with_wait_for_pending_images` (true by default)
which will cause `Harness::run` to sleep until all images are loaded (or
`HarnessBuilder::with_max_steps` is exceeded).
It also adds a new ImageLoader::has_pending and
BytesLoader::has_pending, which should be implemented if things are
loaded / decoded asynchronously.
It reverts https://github.com/emilk/egui/pull/6901 which was my previous
attempt to fix this (but this didn't work since only the tested crate is
compiled with cfg(test) and not it's dependencies)
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Closes https://github.com/emilk/egui/issues/5507
* [x] I have followed the instructions in the PR template
Continuation of #5713
**Silently breaking changes:**
- Menus now close on click by default, this is configurable via
`PopupCloseBehavior`
**Additional additions:**
- `Button::right_text`
- `StyleModifier`
This is a rewrite of the egui menus, with the following goals:
- submenu buttons should work everywhere, in a popup, context menu,
area, in some random Ui
- remove the menu state from Ui
- should work just like the previous menu
- ~proper support for keyboard navigation~
- It's better now but requires further work to be perfect
- support `PopupCloseBehavior`
* Closes#4607
* [x] I have followed the instructions in the PR template
This introduces new `Tooltip` and `Popup` structs that unify and extend
the old popups and tooltips.
`Popup` handles the positioning and optionally stores state on whether
the popup is open (for click based popups like `ComboBox`, menus,
context menus).
`Tooltip` is based on `Popup` and handles state of whether the tooltip
should be shown (which turns out to be quite complex to handles all the
edge cases).
Both `Popup` and `Tooltip` can easily be constructed from a `Response`
and then customized via builder methods.
This also introduces `PositionAlign`, for aligning something outside of
a `Rect` (in contrast to `Align2` for aligning inside a `Rect`). But I
don't like the name, any suggestions? Inspired by [mui's tooltip
positioning](https://mui.com/material-ui/react-tooltip/#positioned-tooltips).
* Part of #4607
* [x] I have followed the instructions in the PR template
TODOs:
- [x] Automatic tooltip positioning based on available space
- [x] Review / fix / remove all code TODOs
- [x] ~Update the helper fns on `Response` to be consistent in naming
and parameters (Some use tooltip, some hover_ui, some take &self, some
take self)~ actually, I think the naming and parameter make sense on
second thought
- [x] Make sure all old code is marked deprecated
For discussion during review:
- the following check in `show_tooltip_for` still necessary?:
```rust
let is_touch_screen = ctx.input(|i| i.any_touches());
let allow_placing_below = !is_touch_screen; // There is a finger below. TODO: Needed?
```
* Closes <https://github.com/emilk/egui/issues/5690>
* [x] I have followed the instructions in the PR template
It still isn't ideal, since you have to remember to call key_up on a
separate frame.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
I got annoyed by all the slightly different variations of "collect
snapshot results and unwrap them at the end of test" I've written, so I
added a struct to make this nice and simple.
One controversial thing: It panics when dropped. I wanted to ensure
people cannot forget to unwrap the results at the end, and this was the
best thing I could come up with. I don't think this is possible via
clippy lint or something like that.
* [x] I have followed the instructions in the PR template