1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00
Commit Graph

1976 Commits

Author SHA1 Message Date
Emil Ernerfeldt
49d69a19dd Run no egui pass in the web backend when the tab is hidden
Same as the native backends: tick `App::logic` via `Context::run_logic`
and leave all ui state alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 17:24:45 +02:00
Emil Ernerfeldt
3f3882612b Never run an egui pass when nothing will be shown
Add `Context::run_logic`, for ticking app logic without running a pass,
and use it in the native eframe backends when a viewport is minimized or
occluded (and has no visible descendant viewport).

Since no pass runs, all ui state is left untouched: nothing to
special-case inside egui, and the app finds everything where it left it
once the window is visible again.

`App::logic` is now always called outside of the egui pass. Any viewport
commands it sends (e.g. `ViewportCommand::Focus`) come out of
`LogicOutput` when there is no pass to carry them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 17:23:12 +02:00
Emil Ernerfeldt
5347b0a4ac Fix window with a Grid being widenable but not shrinkable again (#8386)
## Related

* Fixes a regression from #8152
* Part of #2921

Reported symptom: you can widen the Widget Gallery window, but it won't
shrink again.

I'm not sure this fix is the best one, but it does work.

# Claude says
## Cause

A `Grid` gives its **last** column all the available width, so a
width-filling widget in it (`Separator`, `TextEdit`, `ProgressBar`, …)
makes the `Grid` remember a `col_width` that is really just "however
wide we happened to be".

At the start of a resize drag, `Resize` runs a one-frame sizing pass
(#8152) to measure the minimum content width and clamps the drag against
it. But `GridLayout::next_cell` inflated every cell to
`prev_state.col_width`, so the `Grid` reported its previous width as its
minimum — even though it was only offered `min_size.x`. The clamp is a
lower bound, so widening kept working while shrinking was blocked at the
widened width.

## Fix

During an enclosing sizing pass, don't inflate the stretchy last column
to its remembered width, and don't store the measured (narrow) widths.

Minimal repro (fails before, passes after — added as a regression test):

```rust
Window::new("x").default_width(280.0).show(ctx, |ui| {
    egui::Grid::new("grid").num_columns(2).show(ui, |ui| {
        ui.label("Separator");
        ui.separator(); // fills the last column
        ui.end_row();
    });
});
```

`Panel` is unaffected — it clamps only against the user's `min_size`,
with no content-min sizing pass.

* [x] I have followed the instructions in the PR template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
2026-08-04 16:54:06 +02:00
Emil Ernerfeldt
622bbbeccc Add drag-to-open for collapsible panels (#8363)
A fully collapsed `show_collapsible` panel now leaves a thin grab handle
at its fixed edge, invisible until hovered. Dragging it out past
`min_size` (or double-clicking it) reopens the panel. Opt out with
`panel.drag_to_open(false)`.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 09:20:25 +00:00
Emil Ernerfeldt
98eab50577 Treat a press that leaves a widget as a drag (#8365)
A widget that senses both clicks and drags postpones the
click-versus-drag decision until the pointer moves past `max_click_dist`
or is held for `max_click_duration`. But a click has to be released *on*
the widget — so once the pointer leaves, the gesture can only be a drag,
and there is nothing left to wait for.

This matters for widgets thinner than `max_click_dist` (6px), such as
panel resize handles. The pointer leaves such a widget almost
immediately, which hands the hover to whatever is underneath, while
`dragged()` was not true yet. So a handle highlighting on `hovered() ||
dragged()` blinked out mid-gesture.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 11:09:27 +02:00
rustbasic
78c0e39d1d Fix ScrollArea failure by handling horizontal and vertical scrolling separately in the missing place (#8275)
Fix ScrollArea failure by handling horizontal and vertical scrolling
separately in the missing place

Everywhere in `ScrollArea`, horizontal and vertical scrolling are
handled separately.
However, because there is a single place where they are not handled
separately, when trying to process horizontal and vertical scrolls
independently, one of the dimensions fails to scroll.

This Pull Request ensures that horizontal and vertical scrolling are
handled separately in this area, just like in the rest of the codebase.

* Closes #5289
* Closes #5307
* Closes #8274
2026-08-04 10:10:34 +02:00
rustbasic
5c0b690dab Add extra_text_line_spacing to control vertical spacing between text lines (#8040)
Add `extra_text_line_spacing` to control vertical spacing between text
lines

**Description**
This PR adds a new `Spacing::extra_text_line_spacing` field to control
additional vertical spacing between lines of text.

The spacing is applied to text layout by adjusting
`TextFormat::line_height` based on the font row height plus the
configured extra spacing.

This improves text readability and allows consistent line spacing
customization for widgets such as `TextEdit` and `Label`.

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-08-04 10:06:47 +02:00
Emil Ernerfeldt
dae9adf307 Rename failed_pixel_count_threshold to max_failed_pixels (#8383)
It was confusing that both tolerances had "threshold" in the name

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 16:02:41 +00:00
Jochen Görtler
49d4befe6b Store web_sys::File inside of DroppedFile (#8354)
* Closes #4654
* Related #4667
* [x] I have followed the instructions in the PR template

This PR avoids materializing the contents of a file that was dragged
into an egui application on the web. It does so by storing the
`web_sys::File` handle directly on WASM.

This breaks the existing API of `DroppedFile` on the web, because there
is no way to retrieve the bytes synchronously form a `DroppedFile`
anymore, forcing handling call sites to become asynchronous.

The native API remains the same.

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-08-03 15:07:37 +00:00
limo520
2e7a92bc37 Fix incorrect feature name in the code editor demo (#8330)
Change the feature name from syntax_highlighting to syntect.

<!--
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!
-->

* [x] I have followed the instructions in the PR template

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-08-03 14:51:17 +02:00
Sybrand Aarnoutse
ef846f53e6 Remove dependency on memoffset (#8304)
Hi, I may or may not have used your crate but I'd like to say a quick
thank you for it anyway!
I'm going down the list of reverse dependencies on `memoffset`.

This PR aims to remove the `memoffset` crate from your dependencies.

[`core::mem::offset_of`](https://doc.rust-lang.org/core/mem/macro.offset_of.html)
was stabilised in rustc 1.77 which I believe is at or below your MSRV.

The `memoffset` crate 0.9.1 says that

> If you're using a rustc version greater or equal to 1.77,
> this crate's offset_of!() macro simply forwards to
core::mem::offset_of!().

I consider it very unlikely (see
[here](https://github.com/rust-lang/rust/issues/111839)) for any usage
of the `offset_of!` macro to break but please check anyway.
I hope we can all enjoy the benefits of one less dependency :)

---
<!--
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!
-->

* [x] I have followed the instructions in the PR template *except for
`./scripts/check.sh` which doesn't run in my environment* (I'm unwilling
to chase it down because I'm firing off a whole bunch of these PRs to
various repositories, sorry.)

`cargo clippy` gives 1 unrelated warning.

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-08-03 12:37:09 +00:00
Calbabreaker
5f75aa29d3 Remove dependency home (#8307)
Replaces `home::home_dir` with `std::env::home_dir` as these functions
do the exact same thing

<!--
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!
-->

Removes dependency home from eframe by replacing `home::home_dir` with
`std::env::home_dir`. `home` was probably originally used since
`std::env::home_dir` was once deprecated because of a bug. Post Rust
version 1.87 this has been fixed and now these two functions do exactly
the same thing.

* [X] I have followed the instructions in the PR template

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-08-03 12:29:11 +00:00
n4n5
fa608a1b40 Add egui::Window::title_frame (#8353)
* [X] I have followed the instructions in the PR template


Add a way to set the frame for the content and for the title of the
window
- `self.frame` will be used for the margins of the body
- `self.title_frame` will be used for the margins of the header (title)
2026-08-03 14:15:07 +02:00
Emil Ernerfeldt
eba2780dba Fix egui_kittest failing to compile without the wgpu feature (#8381)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 13:58:01 +02:00
oleflb
3d70aa1123 Make wgpu Instance public (#8321) 2026-08-03 11:53:28 +00:00
Emil Ernerfeldt
ddec5f3e4c Fix where Panel puts its separator line, and how much room it reserves (#8382)
Two fixes to the separator line of `Panel` (`resolve_frame` was added in
#8367):

* **Reserve room only when the line is always drawn.** Before,
`show_separator_line || resizable` reserved the line's thickness, so a
resizable panel that opted out still got a permanently visible gap along
its inner edge — space held for a line only drawn transiently, while
hovering or dragging the resize handle.
* **Paint the line outside the frame's outline**, in room reserved in
`Frame::outer_margin` rather than `inner_margin`, so going outwards from
the panel contents you get:

  `contents | inner_margin | stroke | separator line | outer_margin`

Previously the line landed on top of the frame's outline (or outside its
outer margin). Default panels — no stroke, no outer margin — are
unchanged pixel-wise.

Found in the Rerun viewer: the time panel is
`.resizable(true).show_separator_line(false)` and draws its own top
line, so the extra 1pt landed above the top bar's buttons, making them
look 1pt too low.

Tests in `tests/egui_tests/tests/test_panel_separator_line.rs`, both
spanning `show_separator_line` on/off × resize handle hovered/not:
snapshots of a top panel with a garish outline, plus a pixel probe
across the inner edge of a panel on each of the four sides. Both fail on
`main`.

* [x] I have followed the instructions in the PR template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 10:32:56 +00:00
Davy
dcd0c72d53 Fix TextEdit hint text not following horizontal_align/vertical_align (#8332)
## Summary

- Closes #8309
- [x] I have followed the instructions in the PR template

`TextEdit` hint text was always aligned to `Align2::LEFT_TOP`, ignoring
the alignment set via `TextEdit::horizontal_align` / `vertical_align`.
This caused the hint text, the cursor, and the typed text to disagree on
alignment: e.g. a centered `TextEdit` showed a left-aligned hint with a
centered cursor.

The hint text atoms now use the widget's `align`, so the hint matches
the input text alignment. The default `align` is still `LEFT_TOP`, so
multi line text edits (and the default styling) are unchanged.

### Root cause

In `crates/egui/src/widgets/text_edit/builder.rs`, the hint-text branch
hardcoded:

```rust
atoms.push_right(atom.atom_align(Align2::LEFT_TOP));
```

while the input-text branch used `.atom_align(self.align)`. The hint
path now uses `align` as well.

### Drive-by: silence `clippy::unnecessary_wraps` in
`egui_kittest::app_kind`

`AppKind::run` returns `Option<egui::Response>`. The `Option` wrap is
required when the `eframe` feature is enabled (the `Eframe` branch
returns `None`), but `clippy::unnecessary_wraps` fires when
`egui_kittest` is built standalone without the `eframe` feature (e.g.
`cargo clippy -p egui_kittest`). The workspace CI run doesn't hit it
because feature unification via `egui_demo_app` enables `eframe`, but
it's a real annoyance for anyone linting the crate on its own. Added a
scoped `#[cfg_attr(not(feature = "eframe"),
expect(clippy::unnecessary_wraps))]` with an explanatory comment.

## Test plan

- [x] Added `textedit_hint_text_should_follow_text_alignment` kittest
regression in `crates/egui_kittest/tests/regression_tests.rs`. It fails
before the fix (`hint_center_x=24.25` vs `edit_center_x=100`) and passes
after.
- [x] `cargo test -p egui`
- [x] `cargo test -p egui_kittest --all-features --test
regression_tests`
- [x] `cargo clippy -p egui_kittest --all-features --test
regression_tests -- -D warnings`
- [x] `RUSTFLAGS="-D warnings" cargo clippy -p egui_kittest --lib`
(pre-existing `unnecessary_wraps` now silenced)
- [x] `cargo clippy -p egui -- -D warnings`
- [x] `cargo fmt --check`

---------

Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
2026-08-03 08:55:18 +00:00
Emil Ernerfeldt
c676d939ca Report failing pixels by threshold when a kittest snapshot fails (#8360)
When an image snapshot fails, you get the number of pixels differing by
more than the `threshold` you happened to configure — which doesn't tell
you what threshold *would* have passed. So picking
`SnapshotOptions::threshold` / `failed_pixel_count_threshold` is trial
and error, one CI round-trip per guess.

This measures the failing pixel count at a sweep of thresholds (new
public `THRESHOLD_SWEEP`) and includes it in `SnapshotError::Diff`:

```
'sweep_demo' Image did not match snapshot. Diff: 293, …/sweep_demo.diff.png.
  Failing pixels by threshold: 0.0: 1522, 0.1: 1522, 0.2: 293, 0.4: 293, 0.6: 293, 1.0: 293, …
  Run `UPDATE_SNAPSHOTS=1 cargo test --all-features` to update the snapshots.
```

The sweep only runs for snapshots that already failed, so passing tests
are unaffected.

Breaking: `SnapshotError::Diff` gained a `failing_pixels_by_threshold`
field.

* [x] I have followed the instructions in the PR template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 10:08:09 +02:00
Emil Ernerfeldt
967aa1137a Re-add Visuals::clip_rect_margin as a deprecated no-op (#8380)
Follow-up to #8366, which removed `Visuals::clip_rect_margin` outright

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 07:57:35 +00:00
Lucas Meurer
998b413739 Sync window theme with egui theme (#8299)
Adds a new option to sync the window theme with the egui theme, enabled
by default. Works across viewports.

 


https://github.com/user-attachments/assets/513c2318-cd6e-4e2b-805d-04002c375a10

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 09:40:29 +02:00
Calin P
a80ed6bab7 Eframe: make webbrowser dependency optional (#8372)
<!--
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 #8371
* [x] I have followed the instructions in the PR template

Adds a `link` feature to eframe to allow disabling links on egui-winit.
The feature is enabled by default so nothing changes for existing users
of eframe.
2026-08-03 09:00:02 +02:00
Emil Ernerfeldt
65109a0da0 Update crates (#8379)
Routine dependency update.

Updated: `font-types` 0.12, `harfrust` 0.12, `jiff` 0.2.35, `open` 5.4,
`pollster` 1.0, `rand` 0.10.2, `self_cell` 1.3, `skrifa` 0.44, `tokio`
1.53, `toml` 1.1, `vello_cpu` 0.1.
2026-08-03 06:38:37 +00:00
Emil Ernerfeldt
cb2b306f11 Add LayoutJob::clear (#8376)
## Summary

- Add `LayoutJob::clear` to reuse layout settings while rebuilding text.
- Cover preservation of every layout setting.

## Test

- `cargo clippy -p epaint --all-features --all-targets`
- `cargo test -p epaint --all-features`

* [x] I have followed the instructions in the PR template
2026-08-02 19:26:27 +00:00
Emil Ernerfeldt
7f30623cff Add WidgetText::size (#8377)
## Summary

- Add `WidgetText::size` for sizing plain, rich, and layout-job text
uniformly.
- Preserve already-laid-out galleys.

## Test

- `cargo clippy -p egui --all-features --all-targets`
- `cargo test -p egui --all-features`

* [x] I have followed the instructions in the PR template
2026-08-02 19:25:32 +00:00
Emil Ernerfeldt
7fd54ef741 Add BoxedWidget: dynamically dispatched widgets (#8378)
## Summary

- Add `BoxedWidget` and `Widget::boxed` for heterogeneous widget
collections.
2026-08-02 19:22:59 +00:00
Emil Ernerfeldt
4471969a16 Panels: Take separator line width into account (#8367)
This fixes a small styling bug: the width of the panel's
resize-handle-line was not included in the outer width of the panel. Now
it is.
2026-08-01 10:13:52 +00:00
Emil Ernerfeldt
b06f5fea09 Remove clip_rect_margin (#8366)
* Closes https://github.com/emilk/egui/issues/5605
 
It has been zero by default for a few months now, and I do not wish to
support it. It was always an ugly hack, and it is no longer needed.
2026-07-31 23:21:17 +02:00
Lucas Meurer
36341c21fe Make non-interactive tooltips not interactable (#8362)
Fixes two bugs around tooltips:
- During sizing pass tooltips would render at a different size, at which
point they might overlap the interacted widget causing the hover state
to change and the tooltip to never be shown
- Fix: During sizing pass make the area `interactable: false` so events
pass through
- When hovering close to the boarder of a widget (in the area where
`interact_radius` takes effect), there could be a feedback loop where
the tooltip is shown one frame and hidden the next.
- Fix: Always make tooltips `interactable: false` when they don't have
interactive contents

This PR changes the behavior of `Area::interactable` to it's original
behavior: Now interactions will pass through the area background _and_
it's containing widgets. It worked this way initially but got changed in
#4026

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 14:30:46 +02:00
Umaĵo
c69834e65a Improve robustness of text input handling for eframe/web (#8045)
<!--
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!
-->

* Fix [the Samsung Keyboard Korean Cheonjiin layout
bug](https://github.com/emilk/egui/pull/7967#issuecomment-4098503570)
* Partially fix (does not close) #8046
* Supersedes #7914
* Supersedes #8047
* Related: #8068
* Related: #7983
* Related: #8078
* [x] I have followed the instructions in the PR template

This PR reworks the text input handling logic in eframe's web
integration, primarily in `text_agent.rs`.
It also adds a new `ImeEvent::DeleteSurrounding` variant, along with the
corresponding handling logic in `egui` to support the changes.

## Fix: Samsung Keyboard Cheonjiin issue

This PR fixes a bug reported by @rustbasic when using Samsung Keyboard's
Cheonjiin Korean layout.

Since Samsung Keyboard is only available on Samsung devices, I wasn't
able to verify it myself. The fix is based on @rustbasic's testing and
confirmation.

The root cause is that the layout relies on the preceding text to
correctly handle batchim composition. Previously, the text agent eagerly
cleared the text input after every IME composition, removing the context
too early. This PR makes that cleanup more conservative, preserving the
text when it may still be needed.

My understanding is that this is a quirk specific to Samsung Keyboard:
The IME reports that composition has finished even though it is
effectively still active and the composed text may continue to change.

## Fix: Keystrokes resetting keyboard layout (numpad/symbols/etc.)

Partially fixes #8046.

Previously, keystrokes would cause the on-screen keyboard to switch back
to its primary layout.

One remaining issue is that tapping within the active `TextEdit` to
reposition the cursor still resets the keyboard to its primary layout.

## About text suggestions

I originally planned to include text suggestion support in this PR
because #8068 implemented it.
 
However, adding text suggestion support would broaden the scope of this
PR, so I think it is better addressed in a separate PR.

For reference, [the reverted
implementation](8a4f70859c)
works fine on Android (Gboard), but not on iOS (iPadOS 17 + SwiftKey).
2026-07-29 18:55:23 +02:00
Lucas Meurer
6268c84d8a Prevent accidentally dropping TexturesDelta (#8356)
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>
2026-07-29 16:16:48 +00:00
Emil Ernerfeldt
d99b665ec0 Web: anchor the text agent to the canvas (#8297)
* Follow-up to #8296, part of
<https://github.com/emilk/egui/issues/8295>
* [x] I have followed the instructions in the PR template

Stacked on #8296 (base branch), kept as a separate PR so it can be
reverted independently.

The hidden text-agent `<input>` is now inserted as a *sibling of the
canvas* (instead of appended to `document.body`) and positioned with
`offsetLeft`/`offsetTop` instead of `getBoundingClientRect`. Since the
input and the canvas share the same containing block, the input stays
anchored to the canvas top-left corner no matter how the page is
scrolled or how the canvas is embedded.

Consequences:

* Fixes the IME popup position when the host page is scrolled —
`move_to` previously wrote *viewport* coordinates from
`getBoundingClientRect` into document-absolute `left`/`top`.
* Subsumes the mobile Safari virtual-keyboard workaround (it replaced
the flapping `getBoundingClientRect` y with `offsetTop`, which is now
used everywhere), so `is_mobile_safari()` is removed.
* Removes the special-casing of document vs shadow DOM roots — sibling
insertion works uniformly in both.
* The input is `position: absolute`, so it does not participate in
flex/grid layout of the canvas' parent and causes no layout shift.
Caveat: host CSS selectors like `div > canvas:only-child` would no
longer match.

Verified with `cargo clippy -p eframe --target wasm32-unknown-unknown
--all-features` and `cargo fmt --all`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 16:24:18 +02:00
Kevin Mehall
701de698d4 web: Avoid panic from lost texture updates when loaded on a background tab (#8313)
The `textures_delta` in `AppRunner::logic()` comes from
`TextureManager::take_delta()` via `end_pass()`. Previously, if
`is_visible` was `false`, these deltas would have been dropped and never
applied, so the `TextureManager` state gets out of sync with the
`Renderer`.

That causes https://github.com/emilk/egui/issues/8228: When the app is
loaded in a background tab and the first frame occurs via the
`setTimeout` path prior to the tab becoming visible, it loses the
`ImageDelta` representing the initial creation of the font atlas
texture. When the user then switches to the tab, it panics at
crates/egui-wgpu/src/renderer.rs:669:18 with `Tried to update a texture
that has not been allocated yet` when attempting to update the texture
that the wgpu renderer never saw when it was created.

Testing: To make it load in a background tab, put `data:text/html,<a
href=http://127.0.0.1:8765/>Click</a>` in the address bar and then
middle click the resulting link. On Chrome you also have to hover the
tab preview before switching to the tab to reproduce the issue.

<!--
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 <https://github.com/emilk/egui/issues/8228> and its duplicate
https://github.com/emilk/egui/issues/8278
* [x] I have followed the instructions in the PR template

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-07-28 11:33:43 +00:00
Magic Crazy Man
76442c870e fix: ensure mapped range is dropped before unmapping buffer in capture (#8337)
After upgrading to wgpu v30, it requires to drop the `BufferView` return
by `get_mapped_range` before unmap the buffer. Fix it in this pr, making
screenshot event crash no more
2026-07-28 09:46:07 +00:00
Emil Ernerfeldt
8c1711ff9f Disable warn_if_rect_changes_id, even in debug builds (#8349)
Too many false positives (spurious red rectangles)
2026-07-28 09:27:38 +00:00
Lucas Meurer
d06f5b5dfc Handle ViewportCommand::InnerSize in egui_kittest (#8350)
Useful to support resizing headless apps via `egui_inspection`
2026-07-28 10:54:45 +02:00
Lucas Meurer
5ca01cdbaf Fix missing modifier events on eframe web, handle physical keys (#8345)
* realized pressing just modifiers cause no key events on eframe web
when testing https://github.com/emilk/egui/pull/8336
* closes https://github.com/emilk/egui/issues/8308
* part of #3653
2026-07-28 10:52:01 +02:00
Emil Ernerfeldt
b730815797 Update MSRV from 1.92 to 1.95 (#8348)
No particular reason

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 08:37:03 +00:00
Lucas Meurer
8fc323fbcf Add egui_inspection::Request::Settle (#8344)
This allows inspection clients to know if the app is in a settled state
and/or run the app until it settles (similar to egui_kittests
`run`/`run_ok`).
2026-07-28 09:47:22 +02:00
Lucas Meurer
2cb071f7f6 Remove Modifiers from RawInput and make it a egui::Event (#8336)
- needs #8335 

Previously it was impossible to correctly set modifiers via egui
inspection, since that only passes egui::Event and egui::Event had no
way to generally express modifiers.
Now modifiers are passed to egui as a Event and not as a field of
`RawInput`.

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-07-27 14:17:22 +00:00
Davy
e6eb00a31c Fix comment style: add space after // (#8333)
## Summary

- [x] I have followed the instructions in the PR template

Fixes comment-style violations of the `CONTRIBUTING.md` code style rule:

> `// Comment like this.` and not `//like this`

### Files changed

- `crates/epaint/src/shapes/bezier_shape.rs`: `//temporary solution` →
`// temporary solution`, and 22× `//add the start point` → `// add the
start point`
- `crates/egui/src/animation_manager.rs`: `//start new animation…` → `//
start new animation…`
- `crates/eframe/src/web/web_painter_wgpu.rs`:
`//create_new.instance_descriptor…` → `//
create_new.instance_descriptor…`

No code logic changes — only adding the missing space after `//` in
inline and standalone comments.

## Test plan

- [x] `cargo fmt --check`
- [x] `cargo clippy -p epaint -p egui`
- [x] `cargo test -p epaint --lib`
2026-07-24 08:24:33 +00:00
Vitaly Kravchenko
d28929ec72 Rerun sizing_pass when reopening popup (#8315)
* 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`
2026-07-23 16:58:26 +02:00
Aarni Koskela
3fcadda5ba Upgrade wgpu to v30 (#8289) 2026-07-20 11:07:29 +02:00
Emil Ernerfeldt
b865da1942 Web: don't scroll host page when text agent or canvas grabs focus (#8296)
* Closes <https://github.com/emilk/egui/issues/8295>
* [x] I have followed the instructions in the PR template

When an eframe app is embedded in a scrollable host page, the host page
jumped to the top whenever the app booted or grabbed keyboard focus.
Cause: the hidden text-agent `<input>` sat at (0,0) of `<body>` with the
`autofocus` attribute, and all focus calls (text agent and canvas) used
plain `focus()`, so the browser scrolled the focused element into view —
i.e. to the top of the page.

Changes:

* All focus calls in `eframe` web (text agent, canvas, Gboard
blur/refocus workaround) now go through a new `focus_without_scroll()`
helper that uses `focus_with_options` with `preventScroll: true`
(supported by all evergreen browsers).
* The `autofocus` attribute is replaced with an explicit focus call
after DOM insertion — the browser-internal autofocus path always scrolls
the element into view and cannot be prevented by any focus option.
Boot-focus behavior is preserved.
* The text-agent input is initially parked at the canvas origin instead
of (0,0) of the page, so any residual scroll-into-view would target the
canvas rather than the top of the host page. (`move_to` keeps
repositioning it for IME as before.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 16:30:24 +02:00
Emil Ernerfeldt
f005960bdc Improve backtrace trimming for cranelift (#8294)
When using the cranelift backend we would get stack traces that would
contain a lot of egui callstacks. This should solve it.
2026-07-06 20:34:03 +02:00
Lucas Meurer
68b74530b7 Release 0.35.0 - Inspection, egui_mcp, classes and improved IME (#8268) 2026-06-25 18:48:18 +00:00
Andrew Farkas
a08630c996 Improve docs on some methods to clarify what counts as a "click" (#8251)
* [x] I have followed the instructions in the PR template

Just a small docs change, since I saw this trip someone up. The ones on
`Response` may be redundant.
2026-06-25 19:01:51 +02:00
Umaĵo
5bf62ca4b3 Implement proper visuals for IME composition (#8083)
<!--
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

This PR adds visual support for IME composition, including the cursor
and conversion segment.
These visuals works (mostly) well on native platforms (`egui-winit`). On
the web (`eframe/web`), support is limited by browser capabilities:
Chromium works well, Firefox shows partial improvement, and Safari
remains subpar.

> [!NOTE]
>
> For `eframe` on Windows, this feature is currently gated behind the
`windows_new_ime_composition_visuals` feature flag.

## Details

We extend `egui::ImeEvent::Preedit(String)` to `egui::ImeEvent::Preedit
{ text: String, active_range_chars: Option<std::ops::Range<usize>> }`.
The new `active_range_chars` field enables rendering of:
- the cursor (when the range is empty), and
- the conversion segment (when the range is non-empty)

in IME composition.

In `egui-winit`, we now use the range provided by
`winit::event::Ime::Preedit` instead of ignoring it.

In `eframe/web`, we derive the range from `selectionStart` and
`selectionEnd` on the text agent. This mapping is fully accurate only in
Chromium, but represents the best available approach for now.

## Demonstrations

### Chinese IMEs (Shuangpin)

We can see where the cursor is now.

| What | With this PR | Without this PR |
|-|-|-|
| macOS builtin |<video
src=https://github.com/user-attachments/assets/487c7e7c-ef6d-4a86-8dbc-8c71871b4470
/>|<video
src=https://github.com/user-attachments/assets/49bd5a60-4b90-4e4a-99e0-cd01d3f7030c
/>|
| macOS builtin (light)|<video
src=https://github.com/user-attachments/assets/e84546f6-947b-4cea-a87e-fda903f49164
/>|——|
| Windows builtin |<video
src=https://github.com/user-attachments/assets/fd331884-1f0c-4822-a99e-8140aed54815
/>|——|
| Wayland iBus Intelligent Pinyin |<video
src=https://github.com/user-attachments/assets/b6796c75-1c4e-45e5-b43a-5d8dea320485
/>|——|
| Chromium (Chrome) macOS | Identical to `macOS builtin`. |——|
| Safari macOS | We can now differentiate between IME composition and
text selection, but we still can't tell where the cursor is. |——|
| Firefox (Zen) macOS | Identical to `macOS builtin`. |——|

### Japanese IMEs

We can see where the conversion segment is now.

| What | With this PR | Without this PR |
|-|-|-|
| macOS builtin |<video
src=https://github.com/user-attachments/assets/f2994cd4-a966-4ff0-9590-d263c202ec76
/>|<video
src=https://github.com/user-attachments/assets/7cf5ff35-003d-4f60-8fbf-15c725c3ecb9
/>|
| macOS builtin (light)|<video
src=https://github.com/user-attachments/assets/6f562bdd-12fc-4486-b37b-8fcf11643295
/>|——|
| Windows builtin |<video
src=https://github.com/user-attachments/assets/f0905659-5335-4034-abda-c25cf8f2fd57
/>|——|
| Wayland iBus Anthy |<video
src=https://github.com/user-attachments/assets/94cd3a24-3158-4d79-ae02-d9b30fdfa738
/>|——|
| Chromium (Chrome) macOS | Identical to `macOS builtin`. |——|
| Safari macOS | We can now differentiate between IME composition and
text selection, but we still can't tell where the conversion segment is.
|——|
| Firefox (Zen) macOS | (Limited improvement.) <video
src=https://github.com/user-attachments/assets/3daf9b63-6e75-467b-8515-31c2a44adf61
/> |——|

### Korean IMEs

We can clearly tell whether we are in composition (in contrast to
selection) now.

| What | With this PR | Without this PR |
|-|-|-|
|macOS builtin|<video
src=https://github.com/user-attachments/assets/73ca28c7-22a0-493f-8f4d-c6e59a2dec54
/>|<video
src=https://github.com/user-attachments/assets/f582de7d-7ec0-48fe-910f-0139ef1620d3
/>|
|macOS builtin (light)|<video
src=https://github.com/user-attachments/assets/269f03bd-6f95-498b-9fb1-1adcb043c738
/>|——|
| Windows builtin| (With a workaround for [this `winit`
bug](https://github.com/emilk/egui/pull/8083#issuecomment-4206742668)
applied.) <video
src=https://github.com/user-attachments/assets/1e82583d-0c41-4f1c-98cf-0606bee5af05
/>|——|
| Wayland iBus Hangul |<video
src=https://github.com/user-attachments/assets/8c9a0de1-9027-4b37-93a3-e9da0251d176
/>|——|
| Chromium (Chrome) macOS | Identical to `macOS builtin`. |——|
| Safari macOS | Identical to `Windows builtin`. |——|
| Firefox (Zen) macOS | Identical to `macOS builtin`. (ignoring the fact
that the composition breaks when typing the second Hangul. (This bug
predates this PR.)) |——|

---------

Co-authored-by: lucasmerlin <hi@lucasmerlin.me>
2026-06-25 18:21:19 +02:00
Vitaly Kravchenko
a8d09eb60d Fix macOS wgpu live resize with low-latency surfaces (#8229)
## Summary

This fixes macOS live-resize behavior for the `eframe`/`egui-wgpu` path
when using the low-latency wgpu surface configuration.

The problem I was seeing is that native window resize can look visibly
below the baseline expected from a desktop GUI: stale or stretched
frames (manifesting as wobble/jitter), or severe lag while dragging a
window edge.

The fix has three parts:

- use `CAMetalLayer.presentsWithTransaction` during live resize to avoid
stale/stretched frames
- temporarily use at least `desired_maximum_frame_latency = 2` while
live resize is active, so transaction presentation does not stall when
the app normally uses `SurfaceConfig::LOW_LATENCY`
- treat macOS `WindowEvent::Moved` as part of the live-resize event
stream, since resizing from the top or left edge changes the window
origin

This PR depends on the winit-side AppKit live-resize timing fix in
[rust-windowing/winit#4588](https://github.com/rust-windowing/winit/pull/4588)

A renderer-only frame-latency change is not enough by itself. The
temporary latency bump only solves the drawable starvation caused by
combining `presentsWithTransaction` with `SurfaceConfig::LOW_LATENCY`.
It does not change when winit emits resize/redraw events, whether
redraws are delivered during AppKit's live-resize event-tracking loop,
or whether the surface size is derived from the current backing rect.

That is why the winit fix is needed first: it makes the windowing layer
report the current AppKit backing size and request redraws from the
live-resize/display callbacks. egui-wgpu still needs this PR on top
because winit does not own the wgpu `Surface` or the underlying
`CAMetalLayer` presentation policy.

In other words: winit fixes when the windowing layer reports
resize/redraw work, while this PR fixes how egui-wgpu presents
Metal-backed wgpu frames during that resize.

## Why change the existing feature?

The existing `macos-window-resize-jitter-fix` feature addresses one
symptom by enabling transaction presentation during resize, but it is
not enough for the low-latency wgpu path.

In particular, `presentsWithTransaction` and
`SurfaceConfig::LOW_LATENCY` interact poorly during AppKit live resize.
The old code avoids that by [skipping transaction presentation when
latency is
`1`](71c4ff3c33/crates/egui-wgpu/src/winit.rs (L417)),
but that means low-latency users get the resize jitter/wobble back.

This PR keeps the low-latency path normally, but temporarily bumps frame
latency only while live resize is active. That gives the resize path
enough drawable slack without changing normal interaction latency.

I removed the `macos-window-resize-jitter-fix` feature because this
seems like the behavior the macOS wgpu path should have by default, not
a separate opt-in. If keeping the feature as a no-op compatibility alias
is preferred, I can adjust the PR.

## Validation

I created a small demo app that somewhat resembles the layout of my
actual app and highlights both horizontal and vertical resize jitter:

- a borderless macOS window
- a simple toolbar
- a scrolling side list
- `SurfaceConfig::LOW_LATENCY`

The toolbar and list make stale or stretched frames easy to see during
native resize. The jitter is visible even on the traffic light buttons.

Recordings:

### Before 1: no transaction presentation, low latency

Shows jitter/wobble and stale/stretched frames during live resize.


https://github.com/user-attachments/assets/2cf4467b-e14c-4f41-8021-0b8c23f41004

### Before 2: transaction presentation with low latency

Shows the other failure mode: live resize can become severely laggy when
transaction presentation is used while keeping
`SurfaceConfig::LOW_LATENCY`.


https://github.com/user-attachments/assets/2f866790-f472-4ede-a3c0-480e8f0f041a

### After: patched egui-wgpu + patched winit, low latency

No visible wobble/jitter and no severe live-resize lag.


https://github.com/user-attachments/assets/59e46e9f-7906-4b5c-a6c7-1d09eae644cd

---------

Co-authored-by: lucasmerlin <hi@lucasmerlin.me>
2026-06-25 13:55:36 +00:00
Lucas Meurer
2e26b70ae9 Call logic even while browser tab is in background (#8257)
* Closes https://github.com/emilk/egui/issues/5112

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:59:45 +02:00
mike
26ead4af21 feat: add remove_string() to storage trait (#8264)
<!--
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!
-->

* [X] I have followed the instructions in the PR template

This PR adds a `remove_string()` API to the Storage trait and also
implements it in the `FileStorage` and `LocalStorage` stucts.
2026-06-25 12:34:12 +02:00