Fix ScrollArea failure by handling horizontal and vertical scrolling
separately in the missing place
Everywhere in `ScrollArea`, horizontal and vertical scrolling are
handled separately.
However, because there is a single place where they are not handled
separately, when trying to process horizontal and vertical scrolls
independently, one of the dimensions fails to scroll.
This Pull Request ensures that horizontal and vertical scrolling are
handled separately in this area, just like in the rest of the codebase.
* Closes#5289
* Closes#5307
* Closes#8274
Add `extra_text_line_spacing` to control vertical spacing between text
lines
**Description**
This PR adds a new `Spacing::extra_text_line_spacing` field to control
additional vertical spacing between lines of text.
The spacing is applied to text layout by adjusting
`TextFormat::line_height` based on the font row height plus the
configured extra spacing.
This improves text readability and allows consistent line spacing
customization for widgets such as `TextEdit` and `Label`.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* Closes#4654
* Related #4667
* [x] I have followed the instructions in the PR template
This PR avoids materializing the contents of a file that was dragged
into an egui application on the web. It does so by storing the
`web_sys::File` handle directly on WASM.
This breaks the existing API of `DroppedFile` on the web, because there
is no way to retrieve the bytes synchronously form a `DroppedFile`
anymore, forcing handling call sites to become asynchronous.
The native API remains the same.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Change the feature name from syntax_highlighting to syntect.
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* [x] I have followed the instructions in the PR template
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
Hi, I may or may not have used your crate but I'd like to say a quick
thank you for it anyway!
I'm going down the list of reverse dependencies on `memoffset`.
This PR aims to remove the `memoffset` crate from your dependencies.
[`core::mem::offset_of`](https://doc.rust-lang.org/core/mem/macro.offset_of.html)
was stabilised in rustc 1.77 which I believe is at or below your MSRV.
The `memoffset` crate 0.9.1 says that
> If you're using a rustc version greater or equal to 1.77,
> this crate's offset_of!() macro simply forwards to
core::mem::offset_of!().
I consider it very unlikely (see
[here](https://github.com/rust-lang/rust/issues/111839)) for any usage
of the `offset_of!` macro to break but please check anyway.
I hope we can all enjoy the benefits of one less dependency :)
---
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* [x] I have followed the instructions in the PR template *except for
`./scripts/check.sh` which doesn't run in my environment* (I'm unwilling
to chase it down because I'm firing off a whole bunch of these PRs to
various repositories, sorry.)
`cargo clippy` gives 1 unrelated warning.
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
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>
* [X] I have followed the instructions in the PR template
Add a way to set the frame for the content and for the title of the
window
- `self.frame` will be used for the margins of the body
- `self.title_frame` will be used for the margins of the header (title)
Two fixes to the separator line of `Panel` (`resolve_frame` was added in
#8367):
* **Reserve room only when the line is always drawn.** Before,
`show_separator_line || resizable` reserved the line's thickness, so a
resizable panel that opted out still got a permanently visible gap along
its inner edge — space held for a line only drawn transiently, while
hovering or dragging the resize handle.
* **Paint the line outside the frame's outline**, in room reserved in
`Frame::outer_margin` rather than `inner_margin`, so going outwards from
the panel contents you get:
`contents | inner_margin | stroke | separator line | outer_margin`
Previously the line landed on top of the frame's outline (or outside its
outer margin). Default panels — no stroke, no outer margin — are
unchanged pixel-wise.
Found in the Rerun viewer: the time panel is
`.resizable(true).show_separator_line(false)` and draws its own top
line, so the extra 1pt landed above the top bar's buttons, making them
look 1pt too low.
Tests in `tests/egui_tests/tests/test_panel_separator_line.rs`, both
spanning `show_separator_line` on/off × resize handle hovered/not:
snapshots of a top panel with a garish outline, plus a pixel probe
across the inner edge of a panel on each of the four sides. Both fail on
`main`.
* [x] I have followed the instructions in the PR template
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary
- Closes#8309
- [x] I have followed the instructions in the PR template
`TextEdit` hint text was always aligned to `Align2::LEFT_TOP`, ignoring
the alignment set via `TextEdit::horizontal_align` / `vertical_align`.
This caused the hint text, the cursor, and the typed text to disagree on
alignment: e.g. a centered `TextEdit` showed a left-aligned hint with a
centered cursor.
The hint text atoms now use the widget's `align`, so the hint matches
the input text alignment. The default `align` is still `LEFT_TOP`, so
multi line text edits (and the default styling) are unchanged.
### Root cause
In `crates/egui/src/widgets/text_edit/builder.rs`, the hint-text branch
hardcoded:
```rust
atoms.push_right(atom.atom_align(Align2::LEFT_TOP));
```
while the input-text branch used `.atom_align(self.align)`. The hint
path now uses `align` as well.
### Drive-by: silence `clippy::unnecessary_wraps` in
`egui_kittest::app_kind`
`AppKind::run` returns `Option<egui::Response>`. The `Option` wrap is
required when the `eframe` feature is enabled (the `Eframe` branch
returns `None`), but `clippy::unnecessary_wraps` fires when
`egui_kittest` is built standalone without the `eframe` feature (e.g.
`cargo clippy -p egui_kittest`). The workspace CI run doesn't hit it
because feature unification via `egui_demo_app` enables `eframe`, but
it's a real annoyance for anyone linting the crate on its own. Added a
scoped `#[cfg_attr(not(feature = "eframe"),
expect(clippy::unnecessary_wraps))]` with an explanatory comment.
## Test plan
- [x] Added `textedit_hint_text_should_follow_text_alignment` kittest
regression in `crates/egui_kittest/tests/regression_tests.rs`. It fails
before the fix (`hint_center_x=24.25` vs `edit_center_x=100`) and passes
after.
- [x] `cargo test -p egui`
- [x] `cargo test -p egui_kittest --all-features --test
regression_tests`
- [x] `cargo clippy -p egui_kittest --all-features --test
regression_tests -- -D warnings`
- [x] `RUSTFLAGS="-D warnings" cargo clippy -p egui_kittest --lib`
(pre-existing `unnecessary_wraps` now silenced)
- [x] `cargo clippy -p egui -- -D warnings`
- [x] `cargo fmt --check`
---------
Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
When an image snapshot fails, you get the number of pixels differing by
more than the `threshold` you happened to configure — which doesn't tell
you what threshold *would* have passed. So picking
`SnapshotOptions::threshold` / `failed_pixel_count_threshold` is trial
and error, one CI round-trip per guess.
This measures the failing pixel count at a sweep of thresholds (new
public `THRESHOLD_SWEEP`) and includes it in `SnapshotError::Diff`:
```
'sweep_demo' Image did not match snapshot. Diff: 293, …/sweep_demo.diff.png.
Failing pixels by threshold: 0.0: 1522, 0.1: 1522, 0.2: 293, 0.4: 293, 0.6: 293, 1.0: 293, …
Run `UPDATE_SNAPSHOTS=1 cargo test --all-features` to update the snapshots.
```
The sweep only runs for snapshots that already failed, so passing tests
are unaffected.
Breaking: `SnapshotError::Diff` gained a `failing_pixels_by_threshold`
field.
* [x] I have followed the instructions in the PR template
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Closes#8371
* [x] I have followed the instructions in the PR template
Adds a `link` feature to eframe to allow disabling links on egui-winit.
The feature is enabled by default so nothing changes for existing users
of eframe.
## Summary
- Add `LayoutJob::clear` to reuse layout settings while rebuilding text.
- Cover preservation of every layout setting.
## Test
- `cargo clippy -p epaint --all-features --all-targets`
- `cargo test -p epaint --all-features`
* [x] I have followed the instructions in the PR template
## Summary
- Add `WidgetText::size` for sizing plain, rich, and layout-job text
uniformly.
- Preserve already-laid-out galleys.
## Test
- `cargo clippy -p egui --all-features --all-targets`
- `cargo test -p egui --all-features`
* [x] I have followed the instructions in the PR template
* Closes https://github.com/emilk/egui/issues/5605
It has been zero by default for a few months now, and I do not wish to
support it. It was always an ugly hack, and it is no longer needed.
Fixes two bugs around tooltips:
- During sizing pass tooltips would render at a different size, at which
point they might overlap the interacted widget causing the hover state
to change and the tooltip to never be shown
- Fix: During sizing pass make the area `interactable: false` so events
pass through
- When hovering close to the boarder of a widget (in the area where
`interact_radius` takes effect), there could be a feedback loop where
the tooltip is shown one frame and hidden the next.
- Fix: Always make tooltips `interactable: false` when they don't have
interactive contents
This PR changes the behavior of `Area::interactable` to it's original
behavior: Now interactions will pass through the area background _and_
it's containing widgets. It worked this way initially but got changed in
#4026
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Fix [the Samsung Keyboard Korean Cheonjiin layout
bug](https://github.com/emilk/egui/pull/7967#issuecomment-4098503570)
* Partially fix (does not close) #8046
* Supersedes #7914
* Supersedes #8047
* Related: #8068
* Related: #7983
* Related: #8078
* [x] I have followed the instructions in the PR template
This PR reworks the text input handling logic in eframe's web
integration, primarily in `text_agent.rs`.
It also adds a new `ImeEvent::DeleteSurrounding` variant, along with the
corresponding handling logic in `egui` to support the changes.
## Fix: Samsung Keyboard Cheonjiin issue
This PR fixes a bug reported by @rustbasic when using Samsung Keyboard's
Cheonjiin Korean layout.
Since Samsung Keyboard is only available on Samsung devices, I wasn't
able to verify it myself. The fix is based on @rustbasic's testing and
confirmation.
The root cause is that the layout relies on the preceding text to
correctly handle batchim composition. Previously, the text agent eagerly
cleared the text input after every IME composition, removing the context
too early. This PR makes that cleanup more conservative, preserving the
text when it may still be needed.
My understanding is that this is a quirk specific to Samsung Keyboard:
The IME reports that composition has finished even though it is
effectively still active and the composed text may continue to change.
## Fix: Keystrokes resetting keyboard layout (numpad/symbols/etc.)
Partially fixes#8046.
Previously, keystrokes would cause the on-screen keyboard to switch back
to its primary layout.
One remaining issue is that tapping within the active `TextEdit` to
reposition the cursor still resets the keyboard to its primary layout.
## About text suggestions
I originally planned to include text suggestion support in this PR
because #8068 implemented it.
However, adding text suggestion support would broaden the scope of this
PR, so I think it is better addressed in a separate PR.
For reference, [the reverted
implementation](8a4f70859c)
works fine on Android (Gboard), but not on iOS (iPadOS 17 + SwiftKey).
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>
* Follow-up to #8296, part of
<https://github.com/emilk/egui/issues/8295>
* [x] I have followed the instructions in the PR template
Stacked on #8296 (base branch), kept as a separate PR so it can be
reverted independently.
The hidden text-agent `<input>` is now inserted as a *sibling of the
canvas* (instead of appended to `document.body`) and positioned with
`offsetLeft`/`offsetTop` instead of `getBoundingClientRect`. Since the
input and the canvas share the same containing block, the input stays
anchored to the canvas top-left corner no matter how the page is
scrolled or how the canvas is embedded.
Consequences:
* Fixes the IME popup position when the host page is scrolled —
`move_to` previously wrote *viewport* coordinates from
`getBoundingClientRect` into document-absolute `left`/`top`.
* Subsumes the mobile Safari virtual-keyboard workaround (it replaced
the flapping `getBoundingClientRect` y with `offsetTop`, which is now
used everywhere), so `is_mobile_safari()` is removed.
* Removes the special-casing of document vs shadow DOM roots — sibling
insertion works uniformly in both.
* The input is `position: absolute`, so it does not participate in
flex/grid layout of the canvas' parent and causes no layout shift.
Caveat: host CSS selectors like `div > canvas:only-child` would no
longer match.
Verified with `cargo clippy -p eframe --target wasm32-unknown-unknown
--all-features` and `cargo fmt --all`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The `textures_delta` in `AppRunner::logic()` comes from
`TextureManager::take_delta()` via `end_pass()`. Previously, if
`is_visible` was `false`, these deltas would have been dropped and never
applied, so the `TextureManager` state gets out of sync with the
`Renderer`.
That causes https://github.com/emilk/egui/issues/8228: When the app is
loaded in a background tab and the first frame occurs via the
`setTimeout` path prior to the tab becoming visible, it loses the
`ImageDelta` representing the initial creation of the font atlas
texture. When the user then switches to the tab, it panics at
crates/egui-wgpu/src/renderer.rs:669:18 with `Tried to update a texture
that has not been allocated yet` when attempting to update the texture
that the wgpu renderer never saw when it was created.
Testing: To make it load in a background tab, put `data:text/html,<a
href=http://127.0.0.1:8765/>Click</a>` in the address bar and then
middle click the resulting link. On Chrome you also have to hover the
tab preview before switching to the tab to reproduce the issue.
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!
* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.
Please be patient! I will review your PR, but my time is limited!
-->
* Closes <https://github.com/emilk/egui/issues/8228> and its duplicate
https://github.com/emilk/egui/issues/8278
* [x] I have followed the instructions in the PR template
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
After upgrading to wgpu v30, it requires to drop the `BufferView` return
by `get_mapped_range` before unmap the buffer. Fix it in this pr, making
screenshot event crash no more
- needs #8335
Previously it was impossible to correctly set modifiers via egui
inspection, since that only passes egui::Event and egui::Event had no
way to generally express modifiers.
Now modifiers are passed to egui as a Event and not as a field of
`RawInput`.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
## Summary
- [x] I have followed the instructions in the PR template
Fixes comment-style violations of the `CONTRIBUTING.md` code style rule:
> `// Comment like this.` and not `//like this`
### Files changed
- `crates/epaint/src/shapes/bezier_shape.rs`: `//temporary solution` →
`// temporary solution`, and 22× `//add the start point` → `// add the
start point`
- `crates/egui/src/animation_manager.rs`: `//start new animation…` → `//
start new animation…`
- `crates/eframe/src/web/web_painter_wgpu.rs`:
`//create_new.instance_descriptor…` → `//
create_new.instance_descriptor…`
No code logic changes — only adding the missing space after `//` in
inline and standalone comments.
## Test plan
- [x] `cargo fmt --check`
- [x] `cargo clippy -p epaint -p egui`
- [x] `cargo test -p epaint --lib`
* Closes <https://github.com/emilk/egui/issues/8115>
* [x] I have followed the instructions in the PR template
## Summary
- Recalculate a menu popup's cached `Area` size when it reopens.
- Preserve cached sizing for continuously open menus and leave tooltips
and general popups unchanged.
- Add a headless regression test covering a wider item added while the
menu is closed.
## Root cause
`Area` keeps its cached size after a menu closes. When that menu
reopened with wider content, the
cached width constrained the new item and caused it to wrap instead of
allowing the popup to grow.
The fix requests the same invisible sizing pass used for a first-open
`Area` whenever a menu was not
open during the previous frame.
## User impact
Menus now expand to fit newly added wider items after reopening.
Existing wrapping, explicit-width,
alignment, screen-constraining, and continuously open menu behavior
remain unchanged.
## Validation
- `cargo test -p egui_kittest --test menu`
- `cargo check -p egui`
- `cargo fmt --all -- --check`
* Closes <https://github.com/emilk/egui/issues/8295>
* [x] I have followed the instructions in the PR template
When an eframe app is embedded in a scrollable host page, the host page
jumped to the top whenever the app booted or grabbed keyboard focus.
Cause: the hidden text-agent `<input>` sat at (0,0) of `<body>` with the
`autofocus` attribute, and all focus calls (text agent and canvas) used
plain `focus()`, so the browser scrolled the focused element into view —
i.e. to the top of the page.
Changes:
* All focus calls in `eframe` web (text agent, canvas, Gboard
blur/refocus workaround) now go through a new `focus_without_scroll()`
helper that uses `focus_with_options` with `preventScroll: true`
(supported by all evergreen browsers).
* The `autofocus` attribute is replaced with an explicit focus call
after DOM insertion — the browser-internal autofocus path always scrolls
the element into view and cannot be prevented by any focus option.
Boot-focus behavior is preserved.
* The text-agent input is initially parked at the canvas origin instead
of (0,0) of the page, so any residual scroll-into-view would target the
canvas rather than the top of the host page. (`move_to` keeps
repositioning it for IME as before.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* [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.
## 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>
<!--
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.
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>
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
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>
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>