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

236 Commits

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
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
1cd89b5edc Remove everything that was marked #[deprecated] (#8105)
Simplify. Streamline. Spring cleaning.
2026-04-14 20:19:36 +02:00
Dimitris Papaioannou
41b64fc6f3 Call pre_present_notify before presenting (#8089) 2026-04-12 16:06:12 +02:00
Emil Ernerfeldt
4a09782fce Enable a few more clippy lints (#8064)
Enable these new clippy lints and fix all warnings:

* `format_push_string` — use `write!` instead of `s += &format!(…)` to
avoid extra allocations
* `ignored_unit_patterns` — use `()` instead of `_` when matching unit
* `missing_fields_in_debug` — ensure manual `Debug` impls account for
all fields
* `needless_raw_string_hashes` — remove unnecessary `r#` on string
literals
* `ref_option` — prefer `Option<&T>` over `&Option<T>` in function
signatures
2026-04-04 16:20:29 +02:00
Emil Ernerfeldt
0887b54c93 Add eframe::WindowChromeMetrics (macOS only) (#8015)
When using `egui::ViewportBuilder::with_fullsize_content_view` one must
be careful not to paint anything where the "traffic light" buttons are:

<img width="87" height="47" alt="image"
src="https://github.com/user-attachments/assets/0e878c8e-7141-4fed-bbc8-4d542ddb5251"
/>

`eframe::WindowChromeMetrics` helps you with that!
2026-03-25 10:54:17 +01:00
Gautier Cailly
cd3c38cf2a Improve behavior of invisible windows (#7905)
## Summary
* Closes #5229
* Closes #7776

On Windows, once a window is hidden with
`ViewportCommand::Visible(false)`, two problems occur:

1. **Window can never be shown again** — Windows stops sending
`RedrawRequested` events to invisible windows, and viewport commands are
only processed during `run_ui_and_paint`, which is triggered by
`RedrawRequested`. This creates a deadlock:
```
Visible(false) → window hidden → no RedrawRequested → run_ui_and_paint never called → Visible(true) stuck in queue → window stays hidden forever
```

2. **High CPU usage** — The event loop spins at full speed with
`ControlFlow::Poll` even for invisible windows, and repaint requests are
scheduled immediately, causing a tight loop that burns CPU.

## Fix

**For #5229:** In `check_redraw_requests`, after calling
`window.request_redraw()`, detect invisible windows via
`window.is_visible() == Some(false)` and call `run_ui_and_paint`
directly for them. This ensures pending viewport commands (including
`Visible(true)`) are still processed even when the OS doesn't send
redraw events.

**For #7776:** Three layers of throttling for invisible windows:
- **Heartbeat scheduling:** After painting an invisible window, schedule
the next repaint 100ms in the future (instead of immediately). This
keeps viewport commands flowing while limiting to ~10 repaints/sec.
- **Event throttling:** In `user_event`, throttle `RequestRepaint`
events for invisible windows to at least 100ms delay, preventing egui's
repaint callback from bypassing the heartbeat.
- **ControlFlow fix:** Only set `ControlFlow::Poll` for visible windows.
Invisible windows use `WaitUntil` instead of spinning.
- **Backend sleep:** Add `is_visible() == Some(false)` alongside the
existing `is_minimized()` sleep check in both wgpu and glow backends
(defense in depth).

The fix is platform-agnostic: `is_visible()` returns `Some(false)` only
when the platform can confirm invisibility, so it won't trigger on
platforms where invisible windows still receive `RedrawRequested`.

## Test plan
- [x] `cargo fmt` passes
- [x] `cargo clippy -p eframe --all-features` passes with no warnings
- [x] Manual test on Windows: window reappears after `Visible(true)`
when hidden
- [x] Manual test on Windows: CPU stays near 0% while window is
invisible (was ~16% before fix)

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-03-24 16:20:57 +01:00
Emil Ernerfeldt
96cae39fb8 Update a couple of dependencies (#8009)
* Closes https://github.com/emilk/egui/pull/7992
2026-03-24 15:59:53 +01:00
Connor Fitzgerald
a59e803f25 Update to wgpu 29 (#7990)
* [x] I have followed the instructions in the PR template

This updates wgpu to v29 across the egui crate stack.

There a a few API changes due to the requirement to provide a display
handle up front to properly support GLES on linux. I have done my best
to make the api changes as reasonable as possible, but I don't have all
the greater project context, so lmk if things should be done a bit
differently.

I've also updated glow to 0.17 to make cargo deny happy, there are no
source changes. I'm not sure how you want to land these.

---------

Co-authored-by: lucasmerlin <hi@lucasmerlin.me>
2026-03-23 18:21:25 +01:00
Emil Ernerfeldt
1b8a9fe95e Only run App::ui if the application is visible (#7950)
* Closes https://github.com/emilk/egui/issues/5113
* Part of https://github.com/emilk/egui/issues/5112
* Part of https://github.com/emilk/egui/issues/5136

If the application is invisible (occluded or minimized), and the user
calls `.request_repaint`, then we should call `App::logic`, but NOT
`App::ui`.

There are still some situations where `App::logic` is not called when it
should be, but at least now we can skip running the UI code when the app
is invisible.
2026-03-02 19:30:24 +01:00
Emil Ernerfeldt
2be6e225bf Add ViewportInfo::occluded and visible (#7948)
* Part of https://github.com/emilk/egui/issues/5112
* Part of https://github.com/emilk/egui/issues/5113
* Part of https://github.com/emilk/egui/issues/5136

Once we support calling `App::logic` when an app is occluded or
minimized, it is useful to know that it is, in fact, occluded or
minimized.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-02 18:36:04 +01:00
Matthew Runo
9276778181 Avoid repaints on device mouse motion outside window (#7866)
## Summary
- Ignore raw device mouse motion unless the window is focused and the
pointer is inside it
- Also handles pointers starting down and then moving into or out of the
window (drag & drop)
- Prevents global mouse motion from triggering continuous repaint loops
- Applies to both glow and wgpu backends

## Testing
- I ran the check script, nothing seemed to fail

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-03-02 13:00:18 +01:00
Jiayi Zhuang
e505d98215 Fix: update get_proc_address to use Arc for better ownership management (#7922)
<!--
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

`get_proc_address` was introduced in #4145, but its lifetime was
designed to be tied to the lifetime `'s` of `CreationContext`. This
means that using `get_proc_address` outside the lifetime of
`CreationContext` is undefined behavior. This contradicts the original
intent behind introducing `get_proc_address`, as this API is intended
for integration with external libraries that cannot easily guarantee
alignment with egui's lifetimes. This PR changes the type of
`get_proc_address` from a reference to an `Arc`, decoupling its lifetime
from `CreationContext` to achieve safer memory management.
2026-03-02 08:59:29 +01:00
Emil Ernerfeldt
8d98763fe1 Replace #[allow attributes with expect (#7796)
We do have `clippy::allow_attributes` turned on, but it doesn't seem to
work properly
2025-12-19 20:55:50 +01:00
Emil Ernerfeldt
7fe58bbfd4 Forbid uses of unwrap() in the code (#7795) 2025-12-19 20:34:18 +01:00
Emil Ernerfeldt
986c2c0ffb Use explicit Arc::clone to clarify when clones are cheap (#7784) 2025-12-17 17:19:18 +01:00
Emil Ernerfeldt
2f6fe9c572 eframe: Replace Frame::update with fn logic and fn ui (#7775)
* Part of https://github.com/emilk/egui/issues/5113
* Part of https://github.com/emilk/egui/issues/3524

## What
This deprecates `eframe::App::update` and replaces it with two new
functions:

```rs
pub trait App {
	/// Called just before `ui`, and in the future this will
    /// also be called for background apps when needed.
	fn logic(&mut self, ctx: &egui::Context, frame: &mut Frame) { }
	
    /// Show your user interface to the user.
	fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame);

	…
}
```

Similarly, `Context::run` is deprecated in favor of `Context::run_ui`.

`Plugin`s are now handed a `Ui` instead of just a `Context` in
`on_begin/end_frame`.

## TODO
…either in this PR or a later one
* [x] Deprecate `App::update`
* [x] Deprecate `Context::run`
* [x] Change plugins to get a `Ui`
* [x] Update kittest
* [x] Change viewports to get UI:s (`show_viewport_immediate` etc)
  - https://github.com/emilk/egui/pull/7779

## Later PRs
* [ ] Deprecate `Panel::show`
* [ ] Deprecate `CentralPanel::show`
* [ ] Deprecate `CentralPanel` ?
2025-12-16 17:05:50 +01:00
Emil Ernerfeldt
bc48599f65 Rename Context::style to global_style; avoid confusion w/ Ui::style (#7772)
This is important after
* https://github.com/emilk/egui/pull/7770
2025-12-14 16:41:13 +01:00
Jochen Görtler
2dbfe3a083 Enable or_fun_call lint to avoid unnecessary allocations (#7754)
### What

From the [lint
description](https://rust-lang.github.io/rust-clippy/master/index.html?search=or_fu#or_fun_call):

> The function will always be called. This is only bad if it allocates
or does some non-trivial amount of work.

But also:

> If the function has side-effects, not calling it will change the
semantic of the program, but you shouldn’t rely on that.
> 
> The lint also cannot figure out whether the function you call is
actually expensive to call or not.

Still worth it to keep our happy paths clean, imo.
2025-12-05 10:46:34 +01:00
Emil Ernerfeldt
df6f35d568 eframe: add feature wgpu_no_default_features (#7700)
* Part of https://github.com/emilk/egui/issues/5889
* Closes https://github.com/emilk/egui/issues/7106

This changes the `eframe/wgpu` feature to also enable all the `default`
features of `wgpu` and `egui-wgpu`. This makes switching `eframe`
backend from `glow` to `wgpu` a lot easier.

To get the old behavior (depend on `wgpu` but you must opt-in to all its
features), use the new `wgpu_no_default_features` feature.
2025-11-12 10:51:38 +01:00
Emil Ernerfeldt
31d313572b Make sure native_pixels_per_point is set during app creation (#7683)
Useful for things like analytics
2025-11-05 10:35:57 +01:00
ASPCartman
c5347f28e4 Fix jittering during window resize on MacOS for WGPU/Metal (#7641)
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2025-11-01 12:55:56 +01:00
Lucas Meurer
cfca7ebd6d Fix black flash on start in glow eframe backend (#7616)
Before:


https://github.com/user-attachments/assets/b31b7a4f-a5f7-45af-bfe0-4c8e174e209f

After:


https://github.com/user-attachments/assets/47544987-ca81-4efb-b778-5ca59b7fc0ac

I think to make things even nicer we could also try calling
request_discard when a window is shown
2025-10-09 11:44:10 +02:00
Emil Ernerfeldt
47a437403f Use software texture filtering in kittest (#7602) 2025-10-08 16:24:02 +02:00
Emil Ernerfeldt
9cb4e6a54e Create egui_wgpu::RendererOptions (#7601) 2025-10-07 17:06:23 +02:00
Emil Ernerfeldt
86dc9ea64e Inline log format args (#7600) 2025-10-07 16:14:43 +02:00
Emil Ernerfeldt
ab461f4115 Fix eframe window not being focused on mac on startup (#7593)
* Workaround for https://github.com/rust-windowing/winit/issues/4371
* Closes https://github.com/emilk/egui/issues/7588

Tested manually
2025-10-07 14:21:10 +02:00
Emil Ernerfeldt
096ed1c0cb Replace cargo check with cargo clippy on ci (#7581)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-02 20:19:23 +02:00
Emil Ernerfeldt
bd45406fad Use a lot more let-else (#7582) 2025-10-02 19:47:00 +02:00
Tye
a450b1c989 Properly end winit event loop (#7565) 2025-09-30 15:48:54 +02:00
Sola
adf463b0a9 Replace winapi with windows-sys crate (#7416)
This the second take of <https://github.com/emilk/egui/pull/5038>,
adjusted for newer version of `windows-sys`, and with merge conflicts
resolved. I added the original author to `Co-authored-by:`.

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

Tested on Windows 10:
<img width="322" height="272" alt="image"
src="https://github.com/user-attachments/assets/15ddf0bf-1f30-452f-8764-07ccfce2b00c"
/>
<img width="802" height="632" alt="image"
src="https://github.com/user-attachments/assets/db6f4586-cfa5-4f7f-996c-06f7e2489df6"
/>

Action run result:
https://github.com/sola-contrib/egui/actions/runs/16748810761

---------

Co-authored-by: Casper Meijn <casper@meijn.net>
2025-08-12 12:28:51 +02:00
Emil Ernerfeldt
3024c39eaf Enable and fix some more clippy lints (#7426)
One can never have too many lints
2025-08-08 09:57:53 +02:00
Emil Ernerfeldt
36a4981f29 Enable clippy::iter_over_hash_type lint (#7421)
This helped discover a few things that _might_ have been buggy.
2025-08-06 13:55:53 +02:00
Emil Ernerfeldt
9d1dce51eb Extend .typos.toml to enforce american english (#7284)
More or less the same list we use at Rerun
2025-07-01 15:54:00 +02:00
Emil Ernerfeldt
b2995dcb83 Use Rust edition 2024 (#7280) 2025-06-30 14:01:57 +02:00
Max “Goldstein” Siling
853feea464 Fix incorrect window sizes for non-resizable windows on Wayland (#7103)
Using physical window sizes leads to all kinds of fun stuff: winit
always uses scale factor 1.0 on start to convert it back to logical
pixels and uses these logical pixels to set min/max size for
non-resizeable windows. You're supposed to adjust size after getting a
scale change event if you're using physical sizes, but adjusting min/max
sizes doesn't seem to work on sway, so the window is stuck with an
incorrect size.

The scale factor we guessed might also be wrong even if there's only a
single display since it doesn't take fractional scale into account.

TL;DR: winit actually wants logical sizes in these methods (since
Wayland in general operates mostly on logical sizes) and converting them
back and forth is lossy.

<!--
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/7095
* [x] I have followed the instructions in the PR template

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2025-06-24 13:44:56 +02:00