1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00
Commit Graph

4374 Commits

Author SHA1 Message Date
lucasmerlin
bc50ef7ae2 Fix accidental change 2026-06-25 20:30:35 +02:00
lucasmerlin
9758f7b6e6 Fix video 2026-06-25 20:27:23 +02:00
lucasmerlin
74c0a0cb9b Add changelog 2026-06-25 20:25:16 +02:00
Andrew Farkas
a08630c996 Improve docs on some methods to clarify what counts as a "click" (#8251)
* [x] I have followed the instructions in the PR template

Just a small docs change, since I saw this trip someone up. The ones on
`Response` may be redundant.
2026-06-25 19:01:51 +02:00
Umaĵo
5bf62ca4b3 Implement proper visuals for IME composition (#8083)
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

* Closes N/A
* [x] I have followed the instructions in the PR template

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

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

## Details

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

in IME composition.

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

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

## Demonstrations

### Chinese IMEs (Shuangpin)

We can see where the cursor is now.

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

### Japanese IMEs

We can see where the conversion segment is now.

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

### Korean IMEs

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

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

---------

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

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

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

The fix has three parts:

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

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

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

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

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

## Why change the existing feature?

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

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

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

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

## Validation

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

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

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

Recordings:

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

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


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

### Before 2: transaction presentation with low latency

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


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

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

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


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

---------

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:59:45 +02:00
mike
26ead4af21 feat: add remove_string() to storage trait (#8264)
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

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

This PR adds a `remove_string()` API to the Storage trait and also
implements it in the `FileStorage` and `LocalStorage` stucts.
2026-06-25 12:34:12 +02:00
Nashvill375
3fdcef15ab Add atoms() helpers to get Atoms from widgets (#8128)
A get atoms trait for Button, checkbox etc. I made it because I had time
to kill after I tried sorting buttons in a Vec by the image in their
atoms, but couldn't get to it because it was private.

---------

Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:16:56 +02:00
kay-lambdadelta
ffbd7dc641 Remove 64 bit atomics in egui_extras (#8263)
This is a bit like my previous pr
(<https://github.com/emilk/egui/pull/8037>), but addresses the final bit
of 64 bit atomic dependent code, the svg loader in the `egui_extras`
crate.

As with the previous pr, this improves egui's compatibility on platforms
without 64 bit atomics.

* Closes <https://github.com/emilk/egui/issues/7692>

* [x] I have followed the instructions in the PR template
2026-06-25 11:56:19 +02:00
Emil Ernerfeldt
3e19bd1404 Make font hinting target configurable via FontTweak (#8262)
Fixes #8079, where font hinting only sharpens the vertical axis
(vertical stems stay blurry), and none of the skrifa knobs were
reachable — only switching to `Target::Mono` helped, but that wasn't
exposed.

This makes the hinting target configurable instead of hardcoding
`Target::Smooth { symmetric_rendering: true, preserve_linear_metrics:
true }`.

### API
- New `epaint::text::HintingTarget` mirroring `skrifa::outline::Target`:
  - `Mono`
- `Smooth(SmoothHinting)` where `SmoothHinting { light,
symmetric_rendering, preserve_linear_metrics }`
- New field `FontTweak::hinting_target: HintingTarget`.
- Each variant/field is documented with what it does and the
egui-specific caveats (e.g. `symmetric_rendering` only affects
interpreter-hinted fonts; egui positions glyphs from shaper advances so
`preserve_linear_metrics` mostly affects sharpness, not layout).

### Render
- `font.rs` converts `HintingTarget` → `skrifa::outline::Target` and
threads it into the per-glyph `reconfigure` call; the hinting instance
is also reconfigured when the target changes.

### UI
- The font-tweak settings panel gets a `hinting_target` row: Smooth/Mono
radios, `light` / `symmetric_rendering` / `preserve_linear_metrics`
checkboxes, and a `Reset` button — all with tooltips.

### Behavior
- `HintingTarget::default()` matches egui's previous hardcoded target,
so **rendering is unchanged** unless you opt in. To fix the horizontal
blur from #8079, uncheck `preserve_linear_metrics` (or pick `Mono`).
Whether to flip the *default* is left as a follow-up.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 03:18:21 +02:00
Emil Ernerfeldt
e8d96525f4 Make Slider::new take impl Into<RangeInclusive (#8260)
That makes it more ergonomic in some cases, e.g. allows you to use
`Rangef`
2026-06-24 19:03:17 +00:00
Emil Ernerfeldt
7c600b3c76 Pre-populate font variation axes in the FontTweak UI (#8258)
The `FontTweak` settings UI previously let you edit variable-font
variation coordinates only via free-form tag + value entry — you had to
*know* that e.g. `wght` exists and what range is valid.

This PR queries the font's actual variation axes and pre-populates the
UI.

### Changes
- **`epaint`**: new `FontData::variation_axes() ->
Vec<FontVariationAxis>` (skrifa-backed). Each `FontVariationAxis`
exposes the axis `tag`, human-readable `name`, `min`/`default`/`max`,
and `hidden`. Empty for static (non-variable) fonts.
- **`egui`**: extracted the `FontTweak` body into a public
`style::font_tweak_ui(ui, tweak, axes)`. When `axes` is non-empty, each
axis is shown as a named **slider** pre-filled with the font's default
and clamped to its valid range, with a ⟲ button to drop the override.
`impl Widget for &mut FontTweak` still exists and delegates with no axes
(free-form fallback, also used for unknown/manual tags).
- The font settings panel (`Context::fonts_tweak_ui`) now passes
`data.variation_axes()`.
- UI label renamed `coords` → **Axes** (matching Google Fonts'
terminology); the underlying `FontTweak.coords` field keeps the OpenType
"design coordinates" name.

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


Example (Weight and Width):
<img width="340" height="239" alt="Screenshot 2026-06-24 at 11 51 33"
src="https://github.com/user-attachments/assets/f898289a-e329-453a-ba86-c60858901466"
/>

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 20:55:57 +02:00
Lucas Meurer
600286d056 Handle ViewportCommand::Screenshot in kittest (#8256)
Makes it possible to use egui_mcp to drive apps headlessly via kittest
2026-06-24 19:58:30 +02:00
Antoine Cellerier
5a49d895bf Announce pressed state of selectable buttons to screen readers (#8130)
A `Button` used as a toggle (via `Button::selected(true)`,
`Button::selectable(...)`, or `Ui::selectable_label`) now announces its
pressed / not-pressed state to screen readers. Plain buttons that never
call `.selected(...)` stay un-toggled, so their announcement doesn't
change. `Checkbox` and the pre-existing selectable-label code path
already did this; `Button` was the odd one out.

No public API change: the field is private, `Button::selected(bool)`
keeps its signature, and visuals are identical. Internally the field
becomes `Option<bool>` so we can distinguish "plain button" from "toggle
button currently off".

Regression test added in `regression_tests.rs`, do let me know if some
other file would be a better location.

### Note for manual testing

`egui_demo_app` pulls in `eframe` with `default-features = false` and
doesn't re-enable `accesskit`, so `cargo run -p egui_demo_app` publishes
no AccessKit tree at all. To verify manually:
`cargo run -p egui_demo_app --features accessibility_inspector`
Happy to send a small follow-up PR enabling `accesskit` in the demo
app's defaults if that's desirable, since that makes a11y work much
easier to smoke-test locally.

### Use of AI

This PR was drafted with Claude Code. I understand different projects
have different policies regarding AI generated code. Do let me know if
this is not acceptable here. Also happy to take any other feedback.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-24 13:58:30 +02:00
Lucas Meurer
080ce81c8b Add AtomLayout::direction (#8221)
* To be merged after #8219 

Adds support for vertical atom layout. Together with #8219 this
basically creates a minimal flex layout engine
2026-06-24 13:56:54 +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
Lucas Meurer
5ca09dc0b5 Allow downscaling image in GetScreenshot inspection request (#8248)
When an agent screenshots the app using the mcp and then interacts with
the app by clicking at coords, they see the coords in the native image
coords. Since the mcp does everything else in logical coordinates, it
helps if the image they see is also in logical resoltution, so we always
downscale it to 1.0.

I've added this here to avioid having to decode and re-encode the image
in the mcp.
Unfortunately it only does downscaling for now, since adding some way to
upscale the image just for the screenshot would add a lot of complexity,
and might be invasive from a plugin.

I've also changed the submit call to take a closure, to make it easier
to use other transport channel (makes the implementation for reruns mcp
nicer).

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Lucas Meurer <lucas@rerun.io>
2026-06-23 10:51:42 +02:00
Emil Ernerfeldt
7be2e9a2ae Document why the font and SVG crates can't be updated yet (#8253)
The font-shaping and SVG crate families can't be bumped right now
without splitting a transitive crate into two versions in `Cargo.lock`.
Every blocker is an upstream crate that hasn't caught up, so this PR
just records the reasons inline in `Cargo.toml` instead of forcing
duplicates.

## Blockers

| Crate | Wanted | Blocked by |
|---|---|---|
| `skrifa` 0.42 → 0.43 | | `vello_cpu` → `glifo` 0.1.1 pins `skrifa
^0.42` (no newer `glifo`) |
| `font-types` 0.11 → 0.12 | | same `glifo` chain pins `font-types` 0.11
|
| `harfrust` 0.7 → 0.10 | | needs `read-fonts` 0.40 / `font-types` 0.12;
`glifo` pins 0.39 / 0.11 |
| `resvg` 0.45 → 0.47 | | `winit` 0.30's `sctk-adwaita` stuck on
`tiny-skia ^0.11`; resvg 0.47 needs 0.12 |
| `image` 0.25.6 → 0.25.10 | | needs `png` 0.18, which only matches
resvg once resvg is on `tiny-skia` 0.12 |

On `main`, epaint and `glifo` *share* `skrifa` 0.42 / `font-types` 0.11,
so there is a single copy of each. Any epaint-side bump splits them.
resvg 0.46 avoids the tiny-skia split but its `fontconfig-parser`
duplicates `roxmltree`.

Revisit once `glifo` (for the font stack) and `sctk-adwaita` (for
tiny-skia) update.

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

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 03:42:22 +00:00
Matt Fellenz
b409bf3f72 Respect image's orientation (#8240)
Closes https://github.com/emilk/egui/issues/8239
2026-06-23 05:28:55 +02:00
Emil Ernerfeldt
0fce065f85 Update a lot of dependencies (#8252)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 05:20:06 +02:00
Lucas Meurer
f209b9aea0 Add AtomKind::Layout, for nesting AtomLayout (#8219)
This allows you to put an `AtomLayout` inside another `AtomLayout`.
Right now this has limited use, but once we add wrapping, vertical
`AtomLayout` and `AtomUi`, this will be a really powerful new layouting
primitive for egui.

Added a test for this in https://github.com/emilk/egui/pull/8221
2026-06-22 19:09:43 +02:00
Emil Ernerfeldt
428e027ec7 Export ByteRange and CharRange (#8247) 2026-06-22 03:45:48 +00: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
Vitaly Kravchenko
2c8c27c5df Fix label selection in deferred viewports (#8242)
Label text selection before the fix in a deferred viewport:

<img width="484" height="172" alt="before_the_fix"
src="https://github.com/user-attachments/assets/2214a7d9-9585-497d-9920-dd336a7df7ea"
/>

After the fix:

<img width="484" height="172" alt="after_the_fix"
src="https://github.com/user-attachments/assets/0999ed8e-22d4-4109-a5b5-f468f99e692d"
/>

## What changed

- Keep label text-selection state separate for each viewport.
- Route pass lifecycle and label painting through the current
`ViewportId`.
- Drop inactive per-viewport state after its pass.
- Add a regression test that verifies an unrelated viewport pass cannot
clear a child viewport's
selection, while the owning viewport still clears selections whose
labels disappear.

## Why

Issue #4758 identified that deferred viewports need independent
label-selection state. PR #4760
fixed it by keying the temporary state by viewport. The plugin refactor
in PR #7385 moved that
state into one context-wide `LabelSelectionState`, which accidentally
removed the viewport
isolation. A pass in another viewport then fails to encounter the
selected widgets and clears the
selection.

This restores the behavior of #4760 within the current plugin
architecture. Applications do not
need any special handling.
2026-06-22 03:37:32 +02:00
Emil Ernerfeldt
13d6b5afcf Use strongly typed CharIndex and ByteIndex + bug fixes (#8245)
Less risk of confusing the two.

Found and fix a couple real bugs in the process!

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 02:24:00 +02:00
Emil Ernerfeldt
eac51da9ca Add LayoutJob::format_at_byte (#8244)
This PR adds two small additions to `LayoutJob`:

- `LayoutJob::format_at_byte` to query the `TextFormat` of the section
covering a given byte index.
- An optimization to `LayoutJob::append` that merges newly appended text
into the previous section when the format matches and there is no
leading space.

It also documents the `LayoutJob::sections` invariant (sections are
ordered and together cover the whole text with no gaps or overlaps) and
adds `LayoutJob::debug_sanity_check`, which verifies this in debug
builds. It is called from `format_at_byte` and from the text layouter.

## Why the `easymarkeditor` snapshot changed

The `append` optimization changes how many sections a `LayoutJob` ends
up with: consecutive runs of identically-formatted text now collapse
into a single section instead of one section per `append` call. The
easymark editor produces many such adjacent same-format sections, so it
is affected.

This matters because text is **shaped per section**: `layout_section`
runs the shaper once per section, so each section is an independent
shaping run. Merging two adjacent sections into one means the text
across the old boundary is now shaped together as a single run, which
enables cross-boundary kerning (and, in principle, ligatures) that
previously did not happen. Additionally, `extra_letter_spacing` is
skipped before the first glyph of a section, so merging removes a "first
glyph" boundary and lets the spacing apply there.

The net effect is sub-pixel glyph position shifts at the former section
boundaries, which is why `easymarkeditor.png` was regenerated. The new
output is the more correct one — the text is now shaped as the author
wrote it, rather than being artificially split at `append` boundaries.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 22:07:18 +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
冷凍アカギ
172fb54f7f Add AtomLayout::selectable for opt-in text selection (#8224)
* Closes https://github.com/emilk/egui/issues/8217
* [x] I have followed the instructions in the PR template

## What

`AllocatedAtomLayout::paint` painted text via
`ui.painter().galley(...)`, so text in atom-based widgets could never be
selected. As discussed in #8217, routing it through
`LabelSelectionState::label_text_selection` unconditionally would break
`Button` / `Checkbox` / `RadioButton` / `TextEdit` (whose labels should
not be selectable, and whose click/drag handling would fight the
selection drag).

So this adds an **opt-in** `AtomLayout::selectable(bool)` (default
`false`). When enabled, the layout also senses clicks and drags
(mirroring `Label::layout_in_ui`) and paints its text through the
label-selection machinery. The underline is `Stroke::NONE`, so when
nothing is selected the painted output is identical to the existing path
— the default (`selectable == false`) behaviour, and existing snapshots,
are unchanged.

## Tests

Two tests in `tests/egui_tests/tests/test_atoms.rs`:

- `test_atom_selectable_senses_click_and_drag` — a `selectable(true)`
layout senses click+drag; the default stays inert.
- `test_atom_selectable_text_can_be_copied` — selecting (drag) the text
of a selectable layout and copying yields the text via
`OutputCommand::CopyText`, while a non-selectable layout yields nothing.

## Notes

- Verified locally: `cargo fmt --all -- --check`, `cargo clippy -p egui`
(clean), and the two new tests pass. I haven't run the full
`./scripts/check.sh` (wasm/typos) locally, hence opening as a draft.
- This only adds the opt-in API; no existing widget is made selectable.
Happy to wire it up on a specific widget and/or add an `egui_demo_lib`
demo if you'd like — just let me know.
2026-06-13 20:25:00 +02:00
Alexander Kireyev
27a61934b1 docs: add AccessKit accessibility guide (#8233)
Summary:
- add docs/accessibility.md with labels, custom widgets, and testing
guidance
- link the guide from the README accessibility FAQ

Closes #4396

Checks:
- cargo fmt --all -- --check
- git diff --check
2026-06-13 20:23:59 +02: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
Emil Ernerfeldt
b46b9e225f Formatting fixes: fix red main (#8232)
* I accidentally merged https://github.com/emilk/egui/pull/8127 without
all of its checks having been run.

I've now marked a few CI items as required
2026-06-10 19:45:13 +00:00
Emil Ernerfeldt
e392fbadd4 Refactor: break up input.rs into many files (#8230) 2026-06-10 10:12:20 +02:00
Germán Navarro
333442008c Column::remainder().clip(true) now shrinks when available width decreases (#8048)
### Problem
`Column::remainder().clip(true)` grows correctly when the panel is made
wider, but does not shrink when the panel is made narrower.

### Root cause
There are two places in `table.rs` where the `max_used_widths` floor is
applied to column widths. One correctly checks `column.clip`, the other
doesn't.

**Post-render** path (already correct, line ~827):
```rust
if !column.clip {
    *column_width = column_width.at_least(max_used_widths[i]);
}
```
`TableState::load` **pre-render path** (the bug, line 647):
```rust
.at_least(column.width_range.min.max(*max_used))  // clip flag ignored
```

In `TableState::load`, `.at_least(max_used)` is applied to every column
regardless of `clip`. For a `Column::remainder()`, `max_used`
accumulates the historically widest rendered content. When the panel
shrinks, this floor prevents the column from computing a smaller width,
creating a cycle where it can never shrink.

### Fix
Apply the same `clip` guard that already exists in the post-render path:
```rust
.at_least(if column.clip {
    column.width_range.min
} else {
    column.width_range.min.max(*max_used)
})
```

When `clip = true`, the floor is just `width_range.min` (0.0 by
default), allowing the remainder column to shrink freely to the actual
remaining space.

### Minimal reproduction
<details>
  <summary>Show code</summary>

```rust
use eframe::egui;
use egui_extras::{Column, TableBuilder};

fn main() -> eframe::Result {
    eframe::run_ui_native(
        "Column::remainder().clip(true) shrink test",
        Default::default(),
        |ui, _frame| {
            egui::Frame::central_panel(ui.style()).show(ui, |ui| {
                let mut text = String::from("some content");
                egui::ScrollArea::horizontal().show(ui, |ui| {
                    TableBuilder::new(ui)
                        .column(Column::auto())
                        .column(Column::remainder().clip(true))
                        .header(20.0, |mut header| {
                            header.col(|ui| { ui.strong("Auto"); });
                            header.col(|ui| { ui.strong("Remainder + clip"); });
                        })
                        .body(|mut body| {
                            body.row(18.0, |mut row| {
                                row.col(|ui| { ui.label("label"); });
                                row.col(|ui| {
                                    ui.add(
                                        egui::TextEdit::singleline(&mut text)
                                            .desired_width(f32::INFINITY),
                                    );
                                });
                            });
                        });
                });
            });
        },
    )
}
```
</details>

**Without the fix:** make the window wider (column grows ✓), then
narrower — column does not shrink and a horizontal scrollbar appears ✗


https://github.com/user-attachments/assets/2b586588-9f72-4a15-80f4-afddadb69441


**With the fix:** the column shrinks correctly and no scrollbar appears
✓



https://github.com/user-attachments/assets/f1655641-e135-489c-9f59-2af3faa887ab
2026-06-10 10:01:11 +02:00
Francesco Cinà
b3e4cde85a Fix #2142 - lost_focus not firing after a mid-frame focus transfer (#8210)
### Problem
`Response::lost_focus()` could silently fail to fire when keyboard focus
moved from one widget to another *within the same frame* — for example,
clicking a `TextEdit` that was added to the UI *after* the
currently-focused one.

### Fix
This widens the detection window by one extra frame, which is exactly
enough for the deferred loss signal to reach the previously focused
widget on its next render.

### Notes
* The `test_demo_app` test fails, but it has nothing to do with this PR;
it fails on the current master branch, too.

* This PR replaces https://github.com/emilk/egui/pull/3247
* Closes <https://github.com/emilk/egui/issues/2142>
* [x] I have followed the instructions in the PR template
2026-06-10 10:00:55 +02:00
psyche314
704d86e4aa Expose interactive rects from the last pass (#8211)
## Summary

Adds `Context::interactive_rects_last_pass() -> Vec<Rect>`, an
integration-facing helper that returns the same widget interaction
rectangles egui uses for hit-testing in the last completed pass.

The method filters out disabled widgets, non-interactive widgets, and
layers that currently do not allow interaction. It also applies
per-layer transforms so the returned rectangles are in global viewport
coordinates.

## Motivation

Some egui integrations need to declare platform-level input regions
before pointer events can reach egui itself. A concrete example is a
transparent or click-through overlay: the platform/windowing layer must
know which parts of the overlay should receive input and which parts
should pass through to whatever is underneath.

Today egui keeps this information internally in `WidgetRects` and uses
it for its own hit-testing, but integrations cannot enumerate those
rectangles. Downstream integrations therefore need app-level side
channels where each app manually reports its clickable rectangles. That
is fragile because it duplicates data egui already has, is easy for
applications to forget, and tends to go stale when widgets move or
popups/menus appear.

This method exposes only the already-derived, integration-relevant
result instead of making `WidgetRects` itself part of the public API.

## Notes

- The method uses the last completed pass because that is the same data
egui uses for interaction at the start of the next pass.
- Rectangles are returned in layer order for deterministic output.
- Non-positive or non-finite rectangles are skipped.

## Verification

- `cargo check -p egui`
- `cargo test -p egui --lib`

Co-authored-by: psyche314 <psyche314@users.noreply.github.com>
2026-06-10 10:00:41 +02:00
n4n5
923ddcf30d Enhance documentation for Ui::disabled method (#8209)
* [x] I have followed the instructions in the PR template

Related to https://github.com/emilk/egui/discussions/4263
2026-06-10 09:58:19 +02:00
rustbasic
5012603e03 Fix: ScrollArea layout jitter with floating bars and zoom levels (#7944)
Fix: ScrollArea layout jitter with floating bars and zoom levels

* Closes #7937 
* Closes #7942 

This PR improves the stability of `ScrollArea` by addressing two layout
issues:

1. **Discrete Layout for Floating Bars:** Fixed content "shaking" in
`floating` mode when a non-zero `allocated_width` is used. By using
discrete visibility (`show_bars`) instead of the animated factor for
space allocation, we ensure the layout remains stable during scrollbar
animations.
2. **Zoom-level Stability:** Introduced a small epsilon (0.1) when
checking if content exceeds the viewport. This prevents scrollbars from
flickering on and off due to floating-point rounding errors at specific
zoom factors (e.g., 1.01 or 0.95).
2026-06-10 09:57:44 +02:00
Sylvain
0b920aae42 Add modifier keys to egui::Key (#8127)
## Problem

winit has always delivered distinct physical variants for every keyboard
key — \`KeyCode::ShiftLeft\` vs \`KeyCode::ShiftRight\`,
\`KeyCode::ControlLeft\`/\`ControlRight\`, \`AltLeft\`/\`AltRight\`,
\`SuperLeft\`/\`SuperRight\`, plus the ISO 102nd key
\`KeyCode::IntlBackslash\` (the one between LShift and Z, labelled
\`<>|\` on French AZERTY and \`\\|\` on UK QWERTY). Today none of these
reach egui:

- Pressing Shift / Ctrl / Alt alone produces *no* \`Event::Key\` at all.
\`key_from_key_code\` and \`key_from_named_key\` both return \`None\`
for modifiers, so the \`if let Some(active_key)\` branch in
\`on_keyboard_input\` is skipped. The collapsed \`Modifiers\` bools are
the only trace of the press, and they don't distinguish left vs right.
- \`KeyCode::IntlBackslash\` has no arm in \`key_from_key_code\`, so on
French / UK ISO keyboards the \`<>|\` key is completely invisible to
egui apps — neither \`key\` nor \`physical_key\` is ever set.

## Who hits this

- Games / kiosk frontends / pincab UIs that bind \`LeftFlipper =
LShift\` vs \`RightFlipper = RShift\` (or \`LeftMagna = LCtrl\` vs
\`RightMagna = RCtrl\`) — currently impossible inside egui without
shelling out to platform APIs (\`device_query\`, raw X11, etc.).
- Anyone on an ISO keyboard who wants to capture the 102nd key in an
input-binding UI.

Previously discussed: context in #2977 (closed by #3649 which added
\`physical_key\`, but only for keys already in \`egui::Key\`).

## Change

Two small additions, no behaviour change for existing code:

**\`crates/egui/src/data/key.rs\`** — new variants at the end of
\`Key\`:
- \`ShiftLeft\`, \`ShiftRight\`, \`ControlLeft\`, \`ControlRight\`,
\`AltLeft\`, \`AltRight\`, \`SuperLeft\`, \`SuperRight\`
- \`IntlBackslash\`

plus their entries in \`Key::ALL\`, \`Key::from_name\`, and
\`Key::name\` (the \`key_from_name\` roundtrip test at the bottom of the
file still passes).

**\`crates/egui-winit/src/lib.rs\`** — new arms in
\`key_from_key_code\`:
\`\`\`rust
KeyCode::ShiftLeft => Key::ShiftLeft,
KeyCode::ShiftRight => Key::ShiftRight,
// ...ControlLeft/Right, AltLeft/Right, SuperLeft/Right...
KeyCode::IntlBackslash => Key::IntlBackslash,
\`\`\`

The existing \`Modifiers\` struct is untouched — shortcut matching
(\"Ctrl+C\"), \`consume_shortcut\`, etc. still see the collapsed state.
The new variants are purely additive and only surface as physical
\`Event::Key\` presses when someone is specifically looking for them.

## Test

- Existing \`test_key_from_name\` test still passes (updated the
sentinel to \`Key::IntlBackslash as usize + 1\`).
- Manual smoke test: pressing left vs right Shift, Ctrl, Alt each
produces an \`Event::Key { key: Key::ShiftLeft/Right/..., physical_key:
Some(Key::ShiftLeft/Right/...), ... }\`; pressing the AZERTY 102nd key
yields \`Key::IntlBackslash\`. Character-key behaviour and \`Modifiers\`
bools are unchanged.

## Not included

- **Web backend** (\`eframe_web\`): \`PhysicalKey\` isn't fully exposed
there yet per the existing \`physical_key\` docs, so these new variants
are only emitted on native. Happy to extend to web in a follow-up if
wanted.
- \`ModifiersSymmetric\` / per-side sticky state: would be a bigger API
change in \`Modifiers\`. This PR stays at the minimum: forward what
winit already gives us for the event path.

Closes no issue directly but addresses the underlying gap noted in the
thread of #2977 (scancode forwarding) for the modifier / Intl-key
subset.
2026-06-10 09:53:48 +02:00
Emil Ernerfeldt
858c8fd99f Improve Debug-formatting of Id in debug-builds (#8190)
The Debug-formatting of `Id` in debug-builds now contain the full
lineage:

```rs
#[test]
fn with_chain() {
    let id = Id::new("a").with("b").with("c").with(7_i32);
    assert_eq!(
        format!("{id:?}"),
        r#"Id::new("a").with("b").with("c").with(7)"#
    );
}
```


## Related
* https://github.com/emilk/egui/pull/5851
* https://github.com/emilk/egui/pull/7988
2026-06-10 09:39:33 +02:00
Gábor Gyebnár
71c4ff3c33 Fixes color picker hue drift at low alpha values (#8208)
<!--
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

At low apha values, premultiplied colors lose precision.
This PR makes the color picker use unmultiplied colors internally.

Before:


https://github.com/user-attachments/assets/4617a355-daa9-4911-86e6-518ac6867014

After:


https://github.com/user-attachments/assets/d9681b01-50d8-418e-b5a5-79b4bd1bbddf
2026-06-05 15:58:36 +02:00
rustbasic
db7559368d Refactor scroll area fade painting logic (#8214)
Reserve the scroll area before painting fades and update the fade
painting logic.

* Closes #8213
2026-06-02 16:32:40 +02:00
Lucas Meurer
654a2a974d Bump version to 0.34.3 and update changelogs (#8207) 2026-05-27 11:27:25 +02:00
Sylvain
99a8d7b3ff Add ViewportBuilder::with_monitor + ViewportCommand::SetMonitor (#8140)
## Summary

Adds two paired API entry-points that let an integration target a
specific monitor at viewport creation time, or move an existing viewport
to a different monitor at runtime, in a way that works portably on
Wayland.

```rust
// At creation
ViewportBuilder::default()
    .with_inner_size([1920.0, 1080.0])
    .with_monitor(playfield_idx)         // ← new
    .with_decorations(false)

// Or at runtime
ctx.send_viewport_cmd(egui::ViewportCommand::SetMonitor(idx));
```

Both route through winit's
`Fullscreen::Borderless(Some(MonitorHandle))`, which is the only
portable mechanism that:
- targets a specific output on **Wayland** (where there is no global
`OuterPosition`)
- avoids the **Mutter race** where `OuterPosition` is dropped before the
window is mapped (X11/Wayland-Mutter)
- works the same way on Windows and macOS

`with_position` and `with_outer_position` continue to work for cases
where the integration *does* know the absolute pixel coordinates of each
monitor and is on a platform where they are honored. `with_monitor` is
the high-level alternative when you just want "show this window on
output N, borderless fullscreen."

## Why this matters

Multi-monitor borderless setups (kiosks, pinball cabinets, museum
installs, embedded panels) need each window to land on a specific
physical display. Without `with_monitor`:

- On Wayland, you can't move a window to a chosen output at all — the
compositor decides. There's no `OuterPosition` API.
- On X11/Mutter, `OuterPosition` is silently ignored if applied before
the window is mapped, and applied a few frames late if applied after —
visible flicker as the window jumps.
- Polling `monitor.position()` then sending `OuterPosition` in a retry
loop is the workaround pattern, but fragile and racy.

Routing through `Fullscreen::Borderless(Some(MonitorHandle))` is the
same code path winit's own examples use for monitor-targeted fullscreen,
just exposed at the egui ViewportBuilder level.

## Implementation

- `crates/egui/src/viewport.rs` — adds `monitor: Option<usize>` to
`ViewportBuilder`, the `with_monitor(usize)` builder method, and the
`ViewportCommand::SetMonitor(usize)` variant.
- `crates/egui-winit/src/lib.rs` — both at viewport creation and on
`SetMonitor`, look up the monitor by index in `available_monitors()` and
apply `Fullscreen::Borderless(Some(handle))`. Index out of range is a
no-op (with a `log::warn!`), matching how unknown values are handled
elsewhere in the file.

73 lines added, 1 modified. No public API removed or changed.

## Test plan

- [x] `cargo build -p egui -p egui-winit` clean
- [x] `cargo clippy -p egui -p egui-winit --all-features -- -D warnings`
clean
- [x] `cargo fmt -p egui -p egui-winit --check` clean
- [ ] Manual: tested on Linux X11 (Mutter), Linux Wayland (Mutter &
KWin), Windows 11. Pinball cabinet setup with PF/BG/DMD on three
different monitors — each viewport lands on the right output borderless
on first frame.
- [ ] Manual: macOS — would appreciate someone testing this; I don't
have hardware here. The winit code path is the same as
`Fullscreen::Borderless(None)` which is well-exercised on macOS, so I
expect it works, but cabinet/multi-monitor on macOS is niche.

## Background

This is the third of three small upstream-able pieces extracted from the
closed [PR #8113](https://github.com/emilk/egui/pull/8113) (viewport
rotation, declined as too niche / too much surface). The rotation logic
itself shipped as the standalone
[`egui-rotate`](https://crates.io/crates/egui-rotate) crate. The
remaining two integration touch-points needed for kiosk/cabinet setups
are:

- [PR #8138](https://github.com/emilk/egui/pull/8138) —
`App::transform_primitives` + `App::post_platform_output` hooks
(general-purpose post-tessellation / post-platform-output hooks)
- [PR #8127](https://github.com/emilk/egui/pull/8127) —
`Key::ShiftLeft/Right` + `IntlBackslash` physical key variants
- **This PR** — `with_monitor` / `SetMonitor`

Each is independently useful. None depend on the others.

🤖 Drafted with [Claude Code](https://claude.com/claude-code)
2026-05-26 21:45:48 +02:00
Onè
c57e3c4b0c Fix typo in interaction_snapshot documentation (#8158)
<!--
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!
-->

Fixes a small typo
* [x] I have followed the instructions in the PR template
2026-05-26 16:40:42 +02:00
Ammar Abou Zor
a66f0dbd4f Fix ScrollArea::scroll_to_* calls when stick_to_bottom is Active (#8033)
<!--
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 #8032
* [x] I have followed the instructions in the PR template


It includes:
* Fix for `ScrollArea` when `scroll_to_*` could be ignored when
`stick_to_bottom(true)` was active and the viewport was already stuck to
the bottom.
* The fix is by making explicit per-axis scroll movement take priority
over sticky-end snapping for that frame, and avoid immediately
re-marking animated scrolls as still stuck.
* I've also added a regression test for this issue to ensure it will be
caught on further code changes.

The code snippets form the original issue can be used for testing here
as well

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-05-26 16:01:55 +02:00
Josie Elliston
37a7ee448a Improve UiBuilder docs (#8132)
Previously, the doc for `Ui::scope_builder` read

> Create a child, add content to it, and then allocate only what was
used in the parent `Ui`.

which I understood as meaning that "only what was used in the parent
`UI`" would be allocated (in the child or parent), which makes no sense
(in either case).

I rewrote it and some related docs.

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-05-26 15:53:55 +02:00
Lucas Meurer
8d5a7b4557 Configure wgpu to be low-latency by default (#8203)
* initially done in https://github.com/emilk/egui/pull/8103, reverted in
#8167 due to resize hangs
* Related: https://github.com/emilk/egui/issues/8043 (maybe closes??)

Turns out the resize hangs were caused by the present_with_transaction
call. Disabling that when `desired_maximum_frame_latency == 1` causes
the window to resize smoothly.

Thanks @krisdigital for noticing that connection:
https://github.com/emilk/egui/issues/8043#issuecomment-4154440382

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2026-05-26 15:49:51 +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