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

540 Commits

Author SHA1 Message Date
Sylvain
144b1a1f83 eframe: apply ViewportBuilder::with_monitor on the glow backend (#8302)
## Problem

`ViewportBuilder::with_monitor(idx)` is a **no-op on the glow backend**.
A window
built with `.with_monitor(...)` opens on the default monitor, not
fullscreen —
while the exact same code works on wgpu.

The monitor → `Fullscreen::Borderless(Some(monitor))` resolution only
lives in
`egui_winit::create_window`, which the **wgpu** backend uses. The
**glow**
backend builds its windows straight from
`egui_winit::create_winit_window_attributes`
at two sites in `glow_integration.rs` — the initial window and
per-viewport
deferred windows — so the monitor is never applied there.

## Repro

A multi-viewport app on the glow renderer with any viewport built via
`ViewportBuilder::default().with_monitor(1)` opens that window on
monitor 0,
un-fullscreen. Switching to the wgpu renderer places it correctly. Seen
on a
3-monitor GNOME/Wayland setup where the main window and secondary
viewports all
need to land borderless-fullscreen on specific outputs.

## Fix

Extract the resolution into a public
`apply_monitor_to_window_attributes` in
`egui-winit` and call it from `create_window` (unchanged behaviour) plus
both
glow window-creation sites, so `with_monitor` behaves the same on glow
and wgpu.

No public API change beyond the new helper; no behaviour change on wgpu.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-25 14:07:33 +02:00
rustbasic
7aa7f84858 Fix transparent child viewports on Windows with glow (#8423)
Fix transparent child viewports on `Windows` with `glow`

* Closes #3632
* Related #4451
* Related #5072
* Closes #7543
* Related #8116


Transparent native child viewports could become opaque on Windows when
the
selected GL config reports that it does not support transparency.

`glutin_winit::finalize_window` clears the native transparent window
attribute
in that case. However, on affected Windows GL paths, transparent native
windows
and their GL surfaces still composite correctly.

This change preserves the transparent window attribute for explicitly
transparent non-root viewports on Windows by creating those windows
directly.

Tested manually on Windows with glow:
- root transparent viewport
- deferred native child viewport
- immediate native child viewport
2026-08-24 12:49:33 +02:00
YouStones
97603fc082 Theme plugin system (experimental) (#8153)
<!--
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!
-->

# What it does

Addition of a new system of theme plugins which allow the user to use
different rules engine to compute the style for the available
specialised widget style.

# How to use

Create a engine implementing the trait `ThemePlugin` and `ThemeStyle<S:
StyleStruct>` and implement the necessary methods, then register this
way (example for `ButtonStyle`):
```ui.add_theme::<ButtonStyle>(&mycustomengine);```

Now all button will call the `ThemeStyle<ButtonStyle>` method to compute the correct style and later use the cached value to avoid the costly computation.

If no valid `ThemeStyle<S>` or engine is available then it fallback to the default style.

* Closes part of <https://github.com/emilk/egui/issues/3284>
* [x] I have followed the instructions in the PR template

---------

Co-authored-by: adrien <221212@umons.ac.be>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
2026-08-21 13:05:45 +00:00
Emil Ernerfeldt
34b39d564b Enable the clippy::pedantic lint group (#8429)
Instead of opting in to pedantic lints one by one, enable the whole
group and opt out of the noisy ones.

64% of the pedantic lints were already listed individually. This deletes
90 explicit lint lines, enables 51 pedantic lints we never listed, and
picks up new pedantic lints for free. Each opt-out carries its hit
count, so the cost of turning one back on is visible.

`restriction` and `nursery` stay opt-in per lint.

Stacked on top of #8430, which fixes the one real bug the new lints
found.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 06:55:53 +02:00
Emil Ernerfeldt
9bb36b0ac2 Match image file extensions case-insensitively (#8430)
`image.PNG` and `cat.SVG` were not recognized as images.

Adds `egui::load::has_extension(uri, extension)`, which ignores ASCII
case and any `#fragment`, and uses it for the `.svg`, `.gif`, `.webp`
and `.png` checks.

Note: gif/webp URIs like `a#b.gif` no longer match, since the fragment
is now excluded.

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

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 06:41:09 +02:00
42Pupusas
b42d2ef4f0 Don't busy-loop a CPU core while waiting for a redraw (#8398)
Closes #8326 

`check_redraw_requests` switched the event loop to `ControlFlow::Poll`
every time it called `request_redraw`, and only ever restored a sleeping
control flow when a *timed* repaint was still pending. Once the last
scheduled repaint had been consumed the `Poll` was never undone, so the
loop kept spinning.

This is most visible on Wayland, where `RedrawRequested` is only
delivered after the compositor sends a frame callback: between the
request and the callback eframe burns 100% of a CPU core, so simply
moving the mouse over a reactive app pegs a core.

`request_redraw` already wakes the event loop on its own, so the `Poll`
is not needed. Drop it, and always set an explicit sleeping control flow
at the end of `check_redraw_requests`: `WaitUntil` for the earliest
scheduled repaint, `Wait` when nothing is scheduled.

**Measured effect of this patch**

Two byte-identical eframe apps (a 400-row scrolling page, free-running
at 60fps), toggling only whether `eframe` resolves to stock 0.36.0 or
this patch. Same machine and session, native Wayland (niri,
wgpu/Vulkan). Whole-process CPU is `utime+stime` from `/proc/self/stat`,
so it counts every thread — what a system monitor sees.

| configuration | whole process |
|---|---|
| eframe 0.34.3, Wayland | ~12% of a core |
| stock 0.36.0, Wayland | **99–100% of a core** |
| stock 0.36.0, XWayland (same binary) | ~15% of a core |
| **0.36.0 + this patch, Wayland** | **15–16% of a core** |

The patch restores the 0.34 baseline and matches the XWayland figure for
the same binary — ~6.5× less CPU — with frame delivery unchanged at
60fps.

Two details worth noting: the same 0.36 binary is already fine on
XWayland, so this isn't application repaint behaviour; and the per-frame
*closure* cost rises slightly (1.55 to 2.15 ms) because those frames now
run on a CPU that isn't being held at max clocks by the spin loop.

* [X] I have followed the instructions in the PR template
2026-08-11 11:52:12 +00:00
Lucas Meurer
46ba6405bf Release 0.36.1 - Fix Sense::drag bug (#8397) 2026-08-07 15:08:38 +02:00
Emil Ernerfeldt
6aea7eff94 Enable the clippy::std_instead_of_core lint (#8394)
Prefer `core::` over `std::` where either work

* Part of https://github.com/emilk/egui/issues/5735
2026-08-06 13:19:13 +02:00
Lucas Meurer
2397194d5e Release 0.36.0 - Improved mobile keyboard support (#8390) 2026-08-05 10:55:25 +00:00
Emil Ernerfeldt
e37d44ad8a Never run an egui pass when nothing will be shown (#8387)
* Closes <https://github.com/emilk/egui/issues/8266>
* Alternative to #8385

Not the most simple or beautiful code, but it works, and makes sense.

What makes it complex: `app.logic` should still see some input (e.g.
what viewports are visible) and emit some output (e.g. "open this link",
or "focus and repaint").

## TODO
* [x] test multiple viewports

## Clanker says
Instead of teaching egui to skip book-keeping during a pass where no ui
is shown, we simply run no pass at all. Then there is nothing to
special-case: all ui state is left untouched, and the app finds
everything where it left it when the window is shown again.

* New `Context::run_logic(&raw_input, f)`: ticks app logic without a
pass, returning the `LogicOutput` (platform output + viewport commands)
that a pass would otherwise have carried, so e.g.
`ViewportCommand::Focus` still reaches the integration.
* All three eframe backends (glow, wgpu, web) call `run_logic` instead
of `run_ui` when the viewport is minimized/occluded (and has no visible
descendant viewport) or, on web, when the tab is hidden.
* `App::logic` is still called from inside the pass when the window is
visible, so it sees the current frame's input.

While hidden, `run_logic` fills in only the window state
(`RawInput::viewports` / `focused`), so the app can tell that it is
hidden. The ui input (events, time, …) is not interpreted, and is
instead given to the next real pass.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 09:14:35 +02: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
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
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
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
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
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
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
Lucas Meurer
68b74530b7 Release 0.35.0 - Inspection, egui_mcp, classes and improved IME (#8268) 2026-06-25 18:48:18 +00: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
Lucas Meurer
3c11c7b1b1 Fix eframe inspection for glow (#8254)
Removed wrong `cfg` attribute that broke inspection with the glow
backend.
2026-06-23 17:17:49 +02:00
Emil Ernerfeldt
de04af8fb7 Fix "drunk text" bug (#8250)
## Symptom

Fix this long-standing, occasional bug, that can cause text to look
compressed and "drunk":

<img width="552" height="226" alt="Screenshot 2026-06-22 at 13 12 56"
src="https://github.com/user-attachments/assets/9b1abad4-5ef6-4771-8168-f201afc341ab"
/>


## Root cause

`epaint::TextureAtlas::take_delta` is fire-and-forget: it resets the
dirty region as soon as it hands out a delta, assuming the delta will be
uploaded. Atlas growth always emits a **full** `ImageDelta` (`pos:
None`) which recreates the GPU texture at the new size — *as long as it
is applied*. But both native integrations applied `textures_delta`
inside skippable code paths:

- **wgpu** (`egui-wgpu/src/winit.rs`): textures were uploaded only
*after* surface-dependent early-returns (`render_state` /
`surfaces.get_mut(viewport_id)` missing). Texture uploads are
device-level and don't need a surface.
- **glow** (`eframe/src/native/glow_integration.rs`): textures were
uploaded only inside `if is_visible { … }` (and after a viewport-missing
early-return), while `integration.update` still ran and grew the atlas.
The root window even starts hidden on purpose (`with_visible(false)`, to
avoid a startup white flash), so the very first frames hit this.

When the delta was dropped, the GPU font texture stayed smaller than the
CPU-side atlas; every glyph UV (normalized by the CPU atlas size) then
sampled the wrong rows until the next full atlas recreation. wgpu/Metal
can't detect this — the read is in-bounds, just the wrong row.

## Fixes

- **wgpu**: apply `textures_delta.set` right after `render_state` is
obtained, **before** any surface-dependent early-return. `free` still
runs after submit (unchanged).
- **glow**: apply `textures_delta.set` (and `free`) regardless of
`is_visible`, making the GL context current when there's anything to
upload; only tessellation/paint/swap stay gated on visibility.
- **debug assert** in `egui-wgpu`'s `Renderer::update_texture`: a full
delta must (re)create the GPU texture at exactly the delta size —
catches any future CPU/GPU size desync at the source.

## wgpu ruled out

Confirmed the desync is **not** inside wgpu: Metal `create_texture` uses
the exact descriptor size, and `queue.write_texture` validates against
the texture's own live `desc` — a single texture can't have CPU/GPU
sizes disagree. The mismatch is born at the egui boundary (atlas size
for UVs vs. last-applied upload), which wgpu cannot see.

## Testing note

A headless regression test of `paint_and_update_textures` isn't
practical (it needs a real winit window; `render_state` is private with
no surface-less setter). I verified the failure *mechanism* separately
on macOS/Metal (texture lagging the atlas → silent wrong-row sampling,
no wgpu error), but that demo did not exercise the fixed code path, so
it's not included. The fixes rest on the reasoning above.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 17:14:16 +02:00
Matthieu Casanova
467c5b84f0 Fix crash when parent viewport is hidden (#8226)
Hey,
this is my fist PR to fix the bug I just published #8225 
I hope it helps. 
I tested the fix with wgpu but not glow.
I must say that I am not very happy to have very similar function
is_viewport_or_descendant_visible in both case.
Let me know if you would prefer that I try to factorize it.

* Closes <https://github.com/emilk/egui/issues/8225>
* [X] I have followed the instructions in the PR template

---------

Co-authored-by: Matthieu Casanova <public@kpouer.com>
2026-06-22 03:39:53 +02:00
Lucas Meurer
86fcffb229 Add egui_inspection protocol and plugin (#8234)
Introduces live inspection for running egui apps over a small TCP
request/response protocol, plus the `egui::Plugin` that serves it.

This is the minimal surface to get the egui mcp in, we may want to
extend this in the future to add support for the inspection gui.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 09:26:18 +00:00
rustbasic
f624e30786 **fix(web): prevent entire page from scrolling out of view in Chrome (WASM)** (#7888)
**fix(web): prevent entire page from scrolling out of view in Chrome
(WASM)**

* Closes #7887

**Problem**
When using `egui` on the web, the browser (especially Chrome)
occasionally triggers an unwanted page-level scroll. This happens
because the hidden input element (text agent) used for IME/text input is
sometimes positioned outside the visible bounds of the canvas, causing
the browser to "scroll it into view."

**Solution**
I modified the `move_to` function in the web's `text_agent` to ensure
the input element's position stays within the canvas height. By clamping
the `top` property between `0.0` and `canvas_height`, we prevent the
browser from incorrectly scrolling the entire page when the text agent
moves.

- **Specific change:** Applied `clamp(0.0, canvas_height)` to the
`clamped_y` value before setting the CSS `top` property.
2026-06-11 11:53:57 +02:00
Lucas Meurer
654a2a974d Bump version to 0.34.3 and update changelogs (#8207) 2026-05-27 11:27:25 +02:00
Emil Ernerfeldt
fd57895559 Add winit window access to eframe::Frame and CreationContext (#8205)
* Related: https://github.com/emilk/egui/issues/7798
2026-05-26 15:46:42 +02:00
Emil Ernerfeldt
27559ef3fd Rename Panel methods (#8192)
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.
2026-05-24 12:22:32 +02:00
all3f0r1
dcefb2e3b8 Add Context::set_cursor_image for OS-level custom cursors (#8155)
## What

Adds a way for apps to push an RGBA bitmap as the OS cursor — the
missing companion to `Context::set_cursor_icon`. The integration
translates it into a real `winit::CustomCursor`, so the cursor is drawn
by the compositor and can extend past the egui window edge like any
native cursor.

## Why

Apps with custom-shaped windows (Winamp-style skins, themed launchers,
kiosk apps) currently have no clean way to display a custom cursor:
- `CursorIcon` is limited to the standard system enum.
- Painting the cursor sprite via `egui::Painter` works inside the canvas
but gets clipped at the window edge — the bottom/right of the cursor
disappears the moment the pointer is near the boundary, and there's no
way to render onto the desktop area exposed by a
transparent/region-shaped window.

`winit` 0.30+ already supports `CustomCursor::from_rgba` +
`ActiveEventLoop::create_custom_cursor`, but `egui-winit` doesn't
surface it. This PR exposes it through egui.

### Visual demonstration

Driving use case: a Winamp WSZ skin player
([all3f0r1/oneamp](https://github.com/all3f0r1/oneamp)) with a
transparent + region-shaped window where the skin ships its own `.cur`
files. The bottom-right corner of the playlist exposes the resize cursor
— notice how it gets clipped at the window edge in the painter-based
approach.

| Before (cursor painted via `egui::Painter`) | After (cursor pushed via
`set_cursor_image`) |
| --- | --- |
| ![cursor clipped at the bottom-right of the playlist
window](https://raw.githubusercontent.com/all3f0r1/egui/pr-assets/cursor-clipping-before.png)
| ![cursor extends cleanly past the window edge onto the
desktop](https://raw.githubusercontent.com/all3f0r1/egui/pr-assets/cursor-clipping-after.png)
|

## API

```rust
// new in egui::data::output
pub struct CustomCursorImage {
    pub rgba: std::sync::Arc<[u8]>,
    pub size: [u16; 2],     // matches winit's u16 to avoid lossy casts
    pub hotspot: [u16; 2],
}

// new field on PlatformOutput (skipped from serde — ephemeral)
pub cursor_image: Option<CustomCursorImage>,

// new method on Context
ctx.set_cursor_image(Some(image)); // overrides cursor_icon for this frame
ctx.set_cursor_image(None);        // revert to cursor_icon
```

`Arc<[u8]>` is intentional: the integration dedupes by `Arc::as_ptr`, so
reusing the same Arc across frames means the bitmap is only uploaded to
the OS once per skin, not once per frame.

## Integration changes

- `egui_winit::State::handle_platform_output_with_event_loop(window,
Option<&ActiveEventLoop>, ...)` is a new method that threads the active
event loop so it can call `event_loop.create_custom_cursor(...)`.
- The legacy `handle_platform_output(window, ...)` delegates with `None`
and silently drops `cursor_image`. **No existing callers break.**
- The icon and bitmap paths are unified in a private `apply_cursor`. The
no-flicker dedupe of the old `set_cursor_icon` is preserved on both
paths.
- If `CustomCursor::from_rgba` rejects the bitmap (bad dimensions,
hotspot OOB, etc.), we log a warning and fall back to the icon path.
- eframe's wgpu + glow integrations thread `&ActiveEventLoop` through
`run_ui_and_paint` (glow already had it; wgpu needed one extra
parameter) and call the new method.
- Immediate viewports keep the old path because they're invoked from a
`Context` callback that doesn't have an event loop reference. Custom
cursors are a no-op in immediate viewports — acceptable since they're a
niche path.

## Fallback semantics

| backend / context              | what happens                  |
|--------------------------------|-------------------------------|
| eframe wgpu/glow main viewport | bitmap displayed via OS       |
| eframe immediate viewport      | falls back to `cursor_icon`   |
| eframe web                     | falls back to `cursor_icon`   |
| custom integrations not opted in | falls back to `cursor_icon` |
| `from_rgba` returns `BadImage` | warning + falls back to icon  |

## Verification

- `cargo fmt --all -- --check` 
- `cargo clippy -p egui -p egui-winit -p eframe --all-targets
--all-features -- -D warnings` 
- `cargo doc --lib --no-deps -p egui -p egui-winit -p eframe
--all-features` 
- `cargo check -p egui --no-default-features --features serde` 
(validates the `serde(skip)` on `cursor_image`)
- Interactive validation on Linux/Wayland with the OneAmp WSZ skin
player — see screenshots above.

I haven't run the full snapshot test suite (`scripts/check.sh`) because
we're on Linux and the snapshots are macOS-rendered — happy to run it if
you'd like.

## Notes

Drafted per the contributing guide ("open a draft PR, you may get
helpful feedback early"). Open to design feedback on:
1. Whether `CustomCursorImage` should live in `egui::viewport` rather
than `egui::data::output`.
2. Whether the legacy `handle_platform_output` should grow `event_loop`
directly (breaking) instead of getting a sibling method (non-breaking,
what I did).
3. Whether to also wire it through eframe-web (probably not —
`wasm-bindgen-cursor` would need its own path).

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 15:31:52 +02:00
Emil Ernerfeldt
27373b06d0 Style: forbid .zip and .chain (#8188)
The `zip(a, b)` variant produces clearer code imho.

Downside: added dependency on `itertools`
2026-05-22 12:25:34 +02:00
Jochen Görtler
a5ba0d23ce Default app_id to app_name on native (#8172)
### Related

* Closes #7872.

### What

On native (this is not limited to Wayland) we set the `app_id` to the
`app_name` if it is `None`.
2026-05-19 15:51:25 +02:00
Jochen Görtler
7dba2e99fa Fix random hangs by improving wgpu::Surface lifecycle handling (#8171)
### Related

* Closes #8134.
* Related to #5136.

Possibly fixes:

* #8123
* #5145 

### What

We did not properly handle the variants of
[`CurrentSurfaceTexture`](https://docs.rs/wgpu/latest/wgpu/enum.CurrentSurfaceTexture.html)
and always returned `SkipFrame`.

Because of this `egui` could end up in a state where frames are always
skipped after observing `Outdated`, without the chance to recover
(unless an event arrives from the outside).

> [!NOTE]
> This is not Wayland-specific, but could happen on all platforms. It
just happens frequently for Wayland compositors that directly resize a
window after creation (such as tiling/scrolling compositors like
`hyprland` and `niri`).

This PR improves this by separating the code paths for `Outdated` and
`Lost`, to help recover from those events.
2026-05-19 11:06:09 +02:00
Lucas Meurer
e3d7a01a6a Bump version to 0.34.2 and update changelogs (#8147)
Brings the 0.34.2 release commit back to `main` so it tracks the latest
published version and has updated changelogs.
2026-05-05 13:13:14 +02:00
Emil Ernerfeldt
56aabda7b3 Add Harness::spawn_eframe_app (#8120)
This lets you start up the test app from within the test itself, which
can be very useful when you have a specific test scenario set up that
you need to debug.

### Related
* Previous attempt: https://github.com/emilk/egui/pull/5418

### macOS
On macOS, you may only run UIs on the main loop, so you need a few
additional steps. Not ideal, but works!


```diff
diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml
index f9a153268..4e0cc14ee 100644
--- a/crates/egui_demo_app/Cargo.toml
+++ b/crates/egui_demo_app/Cargo.toml
@@ -84,3 +84,7 @@ web-sys.workspace = true
 
 [dev-dependencies]
 egui_kittest = { workspace = true, features = ["eframe", "snapshot", "wgpu"] }
+
+[[test]]
+name = "test_demo_app"
+harness = false
diff --git a/crates/egui_demo_app/tests/test_demo_app.rs b/crates/egui_demo_app/tests/test_demo_app.rs
index e083c8455..7ad9ed516 100644
--- a/crates/egui_demo_app/tests/test_demo_app.rs
+++ b/crates/egui_demo_app/tests/test_demo_app.rs
@@ -4,7 +4,10 @@ use egui_demo_app::{Anchor, WrapApp};
 use egui_kittest::SnapshotResults;
 use egui_kittest::kittest::Queryable as _;
 
-#[test]
+fn main() {
+    test_demo_app();
+}
+
 fn test_demo_app() {
     let mut harness = egui_kittest::Harness::builder()
         .with_size(Vec2::new(900.0, 600.0))
@@ -73,5 +76,8 @@ fn test_demo_app() {
         harness.run_steps(4);
 
         results.add(harness.try_snapshot(anchor.to_string()));
+
+        harness.spawn_eframe_app();
+        break;
     }
 }
```
2026-04-20 14:07:45 +02:00
Tap
d7e55b8381 Group glow config in a struct (#8108)
This is a breaking public API change, but is otherwise trivial due to it
not changing any actual runtime behaviour.

This renames eframe's NativeOptions `vsync` option to `glow_vsync` to
make clear without even looking at docs fully that this is specific to
the `glow` backend.

While I think a better option would actually be to change the wgpu
creation options to match the vsync option if not specified (either to
`AutoVsync` or `AutoNoVsync` depending on setting) this would require
this be made an `Option<PresentMode>`, which would be confusing - and
the `WgpuConfiguration` should probably take priority over other options
here, as there's more than 2 present modes that are relevant. So I think
this is a suitable way to go.

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

* This does not close an issue - this was a trivial amount of code to
change, so I might as well just make it a PR on the spot.
* [x] I have followed the instructions in the PR template

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-04-20 11:39:31 +02:00
Emil Ernerfeldt
f342ab8847 wgpu: Allow configuring VSync and frame latency at runtime (#8114)
Let apps change present_mode and desired_maximum_frame_latency at
runtime instead of only at startup.
API changes (egui-wgpu):
- New SurfaceConfig { present_mode, desired_maximum_frame_latency }.
- WgpuConfiguration now nests these as pub surface: SurfaceConfig (was
two top-level fields).
- RenderState gains pub surface_config: SurfaceConfig — the
currently-requested value.
API additions (eframe):
- Frame::wgpu_surface_config() / Frame::set_wgpu_surface_config(...) for
get/set.
- SurfaceConfig re-exported as eframe::SurfaceConfig.
                                                       
How it works:
The wgpu painter compares render_state.surface_config to its
currently-applied values each paint. If they differ it updates its
config and flips
needs_reconfigure on every surface, piggybacking on the existing
deferred-reconfigure pathway.
                                                       
Demo:
The backend panel (egui_demo_app) gets dropdowns for present mode and
desired max frame latency, wired through the new Frame accessors.

<img width="282" height="172" alt="image"
src="https://github.com/user-attachments/assets/0b1274b2-7e4e-4413-969b-0a014c415f79"
/>
2026-04-17 11:39:47 +02:00
Emil Ernerfeldt
1cd89b5edc Remove everything that was marked #[deprecated] (#8105)
Simplify. Streamline. Spring cleaning.
2026-04-14 20:19:36 +02:00
Emil Ernerfeldt
5c96f4f080 Document glow-only fields in NativeOptions (#8104) 2026-04-14 13:00:35 +02:00
Dimitris Papaioannou
41b64fc6f3 Call pre_present_notify before presenting (#8089) 2026-04-12 16:06:12 +02:00
Umaĵo
86a7f47738 Delegate handling of IME interruptions to integrations to fix virtual keyboard flickering on web (#8078)
* Closes N/A
* Partially replaces #7983
* Related: #8045
* [x] I have followed the instructions in the PR template

## Details

In #7983, I modified `Memory::request_focus` to interrupt any ongoing
IME composition. This fixed a bug where clicking inside an already
focused `TextEdit` failed to cancel the active composition, resulting in
duplicated text:

https://github.com/emilk/egui/pull/8045#issuecomment-4193310616

To avoid introducing API changes in that PR, I ensured the IME state was
reset by forcing `PlatformOutput::ime` to `None` for at least one frame.
While this works well on desktop platforms, it causes virtual keyboard
flickering on the web:

https://github.com/emilk/egui/pull/8045#issuecomment-4193035008

In this PR, I delegate the responsibility for handling IME composition
interruptions to integrations, allowing each integration to decide how
to interrupt compositions in a flexible manner.

### The new field `should_interrupt_composition` on `IMEOutput`.

Instead of introducing a new `OutputCommand` variant, this PR adds a new
field `should_interrupt_composition` to `IMEOutput`.

Interrupting an active composition is only meaningful when IME remains
allowed. If IME should be disabled altogether, `PlatformOutput::ime` can
simply be set to `None`.
Given this, IMO, it is more appropriate to attach the interrupt signal
to `IMEOutput` (i.e., the type of `PlatformOutput::ime`).
2026-04-08 10:04:15 +02:00
rustbasic
188ffacf41 Log localStorage write failures in local_storage_set (#8062)
Log `localStorage` write failures in `local_storage_set`

**Description**  
This PR improves `local_storage_set()` logging in
`eframe/src/web/storage.rs`.

It logs:
- write failures with key and browser error
- unavailable local storage

This helps diagnose web persistence issues such as `QuotaExceededError`
when `localStorage.setItem()` fails.

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-04-07 10:38:57 +02:00
Umaĵo
33e89e33be Improve IME handling, add public method owns_ime_events on Memory (#7983)
<!--
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!
-->

* Depends on #7967
* Closes #7485
* Should fix #7906 (This issue doesn't seem to have been resolved, but
the author closed it; I personally don't have the environment to verify
whether it is fixed.)
* Replaces #4137, #4896, and partially #7810
* [x] I have followed the instructions in the PR template

This PR started as a fix for #7485, but has since evolved into a broader
rewrite of IME-related logic.

## Overview

This PR primarily introduces a new public method, `owns_ime_events`, on
[`Memory`], and refactors parts of [`TextEdit`] to integrate with it.

Previously, each [`TextEdit`] widget independently determined whether to
handle IME events and stored its own IME-related state. This approach
made ownership-handling fragmented and was therefore error-prone.

With this PR:
- IME event ownership is centralized, ensuring that at most a single
widget owns IME events per frame.
- [`PlatformOutput`]'s `ime` field can be set to `None` for at least one
frame when IME composition is interrupted, allowing the IME to be
properly dismissed.

## Details

Two new public methods are introduced on [`Memory`]:

- `fn owns_ime_events(&self, id: Id) -> bool`: check IME event ownership
for the current frame for the widget with the given `id`.
- `fn interrupt_ime(&mut self)`: interrupt the current IME composition,
if any.

Since the newly added methods on [`Memory`] are public, other widgets
can also participate in IME handling without risking ownership conflicts
of IME events.

I also added an internal (`pub(crate)`) field on [`TextEditState`],
called `cursor_purpose`, to distinguish the role of the [`TextEdit`]
cursor.

Additionally, `egui::ImeEvent::Enabled` and `egui::ImeEvent::Disabled`
have been removed, as they are no longer used anywhere.

## Demonstrations

### Windows: The Korean IME text duplication bug fixed in #4137 does not
reappear.

<table>
<thead>
<tr>
<th></th>
<th>With this PR</th>
<th>Without this PR</th>
</tr>
</thead>
<tbody>
<tr>
<th>Behavior</th>
<td>Correct (no regression)</td>
<td>Correct</td>
</tr>
<tr>
<th>Screencast</th>
<td>


![win-kor-after](https://github.com/user-attachments/assets/1b080c8f-2031-406f-8781-aacafd5c879a)
</td>
<td>


![win-kor-before](https://github.com/user-attachments/assets/20258841-72fe-4652-b9a9-9b40e338ccf2)
</td>
</tr>
</tbody>
</table>

### Windows: Chinese and Japanese IMEs now behave more consistently with
the Korean IME in similar scenarios.

This change does not matter much, as composition is rarely interrupted
mid-process with these IMEs in typical usage.

<table>
<thead>
<tr>
<th></th>
<th>With this PR</th>
<th>Without this PR</th>
</tr>
</thead>
<tbody>
<tr>
<th>Behavior</th>
<td>Composition can be interrupted by clicking (like Korean IMEs)</td>
<td>Composition can not interrupted by clicking</td>
</tr>
<tr>
<th>Screencast (Builtin Chinese IME)</th>
<td>


![win-cmn-after](https://github.com/user-attachments/assets/2c76b0a9-da6d-48e1-84e0-47d9631f1196)
</td>
<td>


![win-cmn-before](https://github.com/user-attachments/assets/ea125fb8-c325-48d5-abaf-17d495b8f075)
</td>
</tr>
<tr>
<th>Screencast (Builtin Japanese IME)</th>
<td>


![win-jpn-after](https://github.com/user-attachments/assets/c69e5f48-65b1-4c0f-af4a-522d2f47b75d)
</td>
<td>


![win-jpn-before](https://github.com/user-attachments/assets/a0f1fdad-4f6c-40c2-af57-029f42acf6d5)
</td>
</tr>
</tbody>
</table>

### macOS: was buggy, still buggy

Likely due to this upstream bug in `winit`:
https://github.com/rust-windowing/winit/issues/4432
Once `winit` is updated to a version that includes the fix, the behavior
should become correct with this PR.

<table>
<thead>
<tr>
<th></th>
<th>With this PR</th>
<th>Without this PR</th>
</tr>
</thead>
<tbody>
<tr>
<th>Behavior</th>
<td>Buggy as before</td>
<td>Buggy: Characters are duplicated</td>
</tr>
<tr>
<th>Screencast</th>
<td>


![mac-kor-after](https://github.com/user-attachments/assets/c2bd90e8-e473-49c8-9537-c970c92889bf)
</td>
<td>


![mac-kor-before](https://github.com/user-attachments/assets/63b6cd8a-8903-4743-98bf-ee15296354ba)
</td>
</tr>
</tbody>
</table>

### Wayland + iBus: Korean IME duplication bug fixed

<table>
<thead>
<tr>
<th></th>
<th>With this PR</th>
<th>Without this PR</th>
</tr>
</thead>
<tbody>
<tr>
<th>Behavior</th>
<td>Correct</td>
<td>Buggy: Characters are duplicated</td>
</tr>
<tr>
<th>Screencast</th>
<td>


![wayland-kor-after-2](https://github.com/user-attachments/assets/b154add5-a1ce-4e3a-b243-e72480820c1b)
</td>
<td>


![wayland-kor-before-2](https://github.com/user-attachments/assets/43b28374-f273-4b6f-9845-3efd96ec9a37)
</td>
</tr>
</tbody>
</table>

### Wayland + iBus: #7485 is fixed

<table>
<thead>
<tr>
<th></th>
<th>With this PR</th>
<th>Without this PR</th>
</tr>
</thead>
<tbody>
<tr>
<th>Behavior</th>
<td>Correct</td>
<td>Buggy: Only a single ASCII character can be typed after
<code>TextEdit</code> is focused</td>
</tr>
<tr>
<th>Screencast</th>
<td>


![wayland-7485-after](https://github.com/user-attachments/assets/ec33a54d-1d4e-40f9-8c82-202104bd2d85)
</td>
<td>


![wayland-7485-before](https://github.com/user-attachments/assets/20d2d395-03fd-4966-a376-87249a41aab3)
</td>
</tr>
</tbody>
</table>

### Wayland + iBus: selection is also not broken

This PR does not reintroduce the selection bug fixed in #7973.

<table>
<thead>
<tr>
<th></th>
<th>With this PR</th>
</tr>
</thead>
<tbody>
<tr>
<th>Behavior</th>
<td>Correct</td>
</tr>
<tr>
<th>Screencast</th>
<td>


![wayland-focus-after](https://github.com/user-attachments/assets/daa29197-f7f7-4a7b-b454-c28ee9afa9c1)
</td>
</tr>
</tbody>
</table>

### X11 + Fcitx5: IME composition can be interrupted

But due to #7975, the experience is still subpar. (Uncommitted text is
lost after interruption.)

<table>
<thead>
<tr>
<th></th>
<th>With this PR</th>
<th>Without this PR</th>
</tr>
</thead>
<tbody>
<tr>
<th>Screencast</th>
<td>


![x11-after](https://github.com/user-attachments/assets/e626d9ed-89a2-4825-9cde-3a67723bcb82)
</td>
<td>


![x11-before](https://github.com/user-attachments/assets/da93b351-9488-4da9-aa56-b64190e84ec3)
</td>
</tr>
</tbody>
</table>


[`Memory`]: https://docs.rs/egui/latest/egui/struct.Memory.html
[`TextEdit`]:
https://docs.rs/egui/latest/egui/widgets/text_edit/struct.TextEdit.html
[`PlatformOutput`]:
https://docs.rs/egui/latest/egui/struct.PlatformOutput.html
[`TextEditState`]:
https://docs.rs/egui/latest/egui/widgets/text_edit/struct.TextEditState.html
2026-04-06 10:24:50 +02:00