This is similar to `ScrollArea`, but:
* Supports zooming
* Has no scroll bars
* Has no limits on the scrolling
## TODO
* [x] Automatic sizing of `Scene`s outer bounds
* [x] Fix text selection in scenes
* [x] Implement `fit_rect`
* [x] Document / improve API
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/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!
-->
* Remove references to `glium` backend, because it is deprecated since
egui v0.18.0
* [x] I have followed the instructions in the PR template
Previously, `Harness::run` just called `Harness::step` 3 times. If that
wasn't enough, tests would often call run multiple times so all
animations would finish properly.
Also, I introduced `HarnessBuilder::with_step_dt` to customize with how
big of a dt each frame is called. I set the default to 1.0 / 6.0 (~6fps)
so we don't waste cpu in tests waiting on animations.
`HarnessBuilder::max_steps` allows us to control how many steps
`Harness::run` should run before panicing.
The default is 6, so we run for up to 1.0 logical seconds (six frames at
6 fps), which should be enough to finish most animations.
Turns out a lot of snapshots where rendered before fully shown and had a
light opacity, those are now fixed.
* [x] I have followed the instructions in the PR template
* Part of https://github.com/emilk/egui/issues/4019
`Frame` now includes the width of the stroke as part of its size. From
the new docs:
### `Frame` docs
The total (outer) size of a frame is `content_size + inner_margin +
2*stroke.width + outer_margin`.
Everything within the stroke is filled with the fill color (if any).
```text
+-----------------^-------------------------------------- -+
| | outer_margin |
| +------------v----^------------------------------+ |
| | | stroke width | |
| | +------------v---^---------------------+ | |
| | | | inner_margin | | |
| | | +-----------v----------------+ | | |
| | | | ^ | | | |
| | | | | | | | |
| | | |<------ content_size ------>| | | |
| | | | | | | | |
| | | | v | | | |
| | | +------- content_rect -------+ | | |
| | | | | |
| | +-------------fill_rect ---------------+ | |
| | | |
| +----------------- widget_rect ------------------+ |
| |
+---------------------- outer_rect ------------------------+
```
The four rectangles, from inside to outside, are:
* `content_rect`: the rectangle that is made available to the inner
[`Ui`] or widget.
* `fill_rect`: the rectangle that is filled with the fill color (inside
the stroke, if any).
* `widget_rect`: is the interactive part of the widget (what sense
clicks etc).
* `outer_rect`: what is allocated in the outer [`Ui`], and is what is
returned by [`Response::rect`].
### Notes
This required rewriting a lot of the layout code for `egui::Window`,
which was a massive pain. But now the window margin and stroke width is
properly accounted for everywhere.
Adds `Marginf` to fill the previous niche.
This is all in a pursuit to shrink the sizes of often-used structs, to
improve performance (less cache misses, less memcpy:s, etc).
* On the path towards https://github.com/emilk/egui/issues/4019
* Part of https://github.com/emilk/egui/issues/4019
As part of the work on adding a custom `Border` to everything, I want to
make sure that the size of `RectShape`, `Frame` and the future `Border`
is kept small (for performance reasons).
This PR changes the storage of the corner radius of rectangles from four
`f32` (one for each corner) into four `u8`. This mean the corner radius
can only be an integer in the range 0-255 (in ui points). This should be
enough for most people.
If you want to manipulate rounding using `f32`, there is a new
`Roundingf` to fill that niche.
* Merge this first: https://github.com/emilk/egui/pull/5517
This aligns all rectangles and (horizontal or vertical) line segments to
the physical pixel grid in the `epaint::Tessellator`, making these
shapes appear crisp everywhere.
* Closes https://github.com/emilk/egui/issues/5164
* Closes https://github.com/emilk/egui/issues/3667
This undoes a lot of the explicit, egui-side aligning added in:
* https://github.com/emilk/egui/pull/4943
The new approach has several benefits over the old one:
* It is done automatically by epaint, so it is applied to everything (no
longer opt-in)
* It is applied after any layer transforms (so it always works)
* It makes line segments crisper on high-DPI screens
* All filled rectangles now has sides that end on pixel boundaries
This implements web support for taking screenshots in an eframe app (and
adds a nice demo).
It also updates the native screenshot implementation to work with the
wgpu gl backend.
The wgpu implementation is quite different than the native one because
we can't block to wait for the screenshot result, so instead I use a
channel to pass the result to a future frame asynchronously.
* Closes <https://github.com/emilk/egui/issues/5425>
* [x] I have followed the instructions in the PR template
https://github.com/user-attachments/assets/67cad40b-0384-431d-96a3-075cc3cb98fb
* Closes https://github.com/emilk/egui/issues/5423
New output is actionable
```
failures:
---- demo::demo_app_windows::tests::demos_should_match_snapshot stdout ----
thread 'demo::demo_app_windows::tests::demos_should_match_snapshot' panicked at crates/egui_demo_lib/src/demo/demo_app_windows.rs:433:9:
Errors: [
"'demos/Code Example' Image size did not match snapshot. Expected: (402, 574), Actual: (415, 574).
Run `UPDATE_SNAPSHOTS=1 cargo test` to update the snapshots.",
]
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
demo::demo_app_windows::tests::demos_should_match_snapshot
```
* Closes#686
* Closes#839
* #5370 should be merged before this
* [x] I have followed the instructions in the PR template
This adds modals to egui.
This PR
- adds a new `Modal` struct
- adds `Memory::set_modal_layer` to limit focus to a layer and above
(used by the modal struct, but could also be used by custom modal
implementations)
- adds `Memory::allows_interaction` to check if a layer is behind a
modal layer, deprecating `Layer::allows_interaction`
Current problems:
- ~When a button is focused before the modal opens, it stays focused and
you also can't hit tab to focus the next widget. Seems like focus is
"stuck" on that widget until you hit escape. This might be related to
https://github.com/emilk/egui/issues/5359~ fixed!
Possible future improvements:
- The titlebar from `window` should be made into a separate widget and
added to the modal
- The state whether the modal is open should be stored in egui
(optionally), similar to popup and menu. Ideally before this we would
refactor popup state to unify popup and menu
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
The allows us to pass any state to the ui closure. While it is possible
to just store state in the closure itself, accessing that state after
the harness was created to e.g. read or modify it would require interior
mutability. With this change there are new `Harness::new_state`,
`Harness::run_state`, ... methods that allow passing state on each run.
This builds on top of #5301, which should be merged first
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This adds a `Harness::new_ui`, which accepts a Ui closure and shows the
ui in a central panel. One big benefit is that this allows us to add a
fit_contents method that can run the ui closure with a sizing pass and
resize the "screen" based on the content size.
I also used this to add a snapshot test for the rendering_test at
different scales.
This adds `egui_kittest::try_image_snapshot_options` and
`egui_kittest::image_snapshot_options`, as well as
`Harness::wgpu_snapshot_options` and
`Harness::try_wgpu_snapshot_options`
* [X] I have followed the instructions in the PR template
- closes#3491
- closes#3926
This adds a testing library to egui based on
[kittest](https://github.com/rerun-io/kittest). Kittest is a new
[AccessKit](https://github.com/AccessKit/accesskit/)-based testing
library. The api is inspired by the js
[testing-library](https://testing-library.com/) where the idea is also
to query the dom based on accessibility attributes.
We made kittest with egui in mind but it should work with any rust gui
framework with AccessKit support.
It currently has support for:
- running the egui app, frame by frame
- building the AccessKit tree
- ergonomic queries via kittest
- via e.g. get_by_name, get_by_role
- simulating events based on the accesskit node id
- creating arbitrary events based on Harness::input_mut
- rendering screenshots via wgpu
- snapshot tests with these screenshots
A simple test looks like this:
```rust
fn main() {
let mut checked = false;
let app = |ctx: &Context| {
CentralPanel::default().show(ctx, |ui| {
ui.checkbox(&mut checked, "Check me!");
});
};
let mut harness = Harness::builder().with_size(egui::Vec2::new(200.0, 100.0)).build(app);
let checkbox = harness.get_by_name("Check me!");
assert_eq!(checkbox.toggled(), Some(Toggled::False));
checkbox.click();
harness.run();
let checkbox = harness.get_by_name("Check me!");
assert_eq!(checkbox.toggled(), Some(Toggled::True));
// You can even render the ui and do image snapshot tests
#[cfg(all(feature = "wgpu", feature = "snapshot"))]
egui_kittest::image_snapshot(&egui_kittest::wgpu::TestRenderer::new().render(&harness), "readme_example");
}
```
~Since getting wgpu to run in ci is a hassle, I'm taking another shot at
creating a software renderer for egui (ideally without a huge dependency
like skia)~ (this didn't work as well as I hoped and it turns out in CI
you can just run tests on a mac runner which comes with a real GPU)
Here is a example of a failed snapshot test in ci, it will say which
snapshot failed and upload an artifact with the before / after and diff
images:
https://github.com/emilk/egui/actions/runs/11183049487/job/31090724606?pr=5166
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/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#5053
* [x] I have followed the instructions in the PR template
This fixes#5053 by adding a Sense parameter to UiBuilder, using that in
Context::create_widget, so the Widget is registered with the right Sense
/ focusable. Additionally, I've added a ignore_focus param to
create_widget, so the focus isn't surrendered / reregistered on
Ui::interact_bg.
The example from #5053 now works correctly:
https://github.com/user-attachments/assets/a8a04b5e-7635-4e05-9ed8-e17d64910a35
<details><summary>Updated example code</summary>
<p>
```rust
ui.button("I can focus");
ui.scope_builder(
UiBuilder::new()
.sense(Sense::click())
.id_source("focus_test"),
|ui| {
ui.label("I can focus for a single frame");
let response = ui.interact_bg();
let t = if response.has_focus() {
"has focus"
} else {
"doesn't have focus"
};
ui.label(t);
},
);
ui.button("I can't focus :(");
```
</p>
</details>
---
Also, I've added `Ui::interact_scope` to make it easier to read a Ui's
response in advance, without having to know about the internals of how
the Ui Ids get created.
This makes it really easy to created interactive container elements or
custom buttons, without having to use Galleys or
Painter::add(Shape::Noop) to style based on the interaction.
<details><summary>
Example usage to create a simple button
</summary>
<p>
```rust
use eframe::egui;
use eframe::egui::{Frame, InnerResponse, Label, RichText, UiBuilder, Widget};
use eframe::NativeOptions;
use egui::{CentralPanel, Sense, WidgetInfo};
pub fn main() -> eframe::Result {
eframe::run_simple_native("focus test", NativeOptions::default(), |ctx, _frame| {
CentralPanel::default().show(ctx, |ui| {
ui.button("Regular egui Button");
custom_button(ui, |ui| {
ui.label("Custom Button");
});
if custom_button(ui, |ui| {
ui.label("You can even have buttons inside buttons:");
if ui.button("button inside button").clicked() {
println!("Button inside button clicked!");
}
})
.response
.clicked()
{
println!("Custom button clicked!");
}
});
})
}
fn custom_button<R>(
ui: &mut egui::Ui,
content: impl FnOnce(&mut egui::Ui) -> R,
) -> InnerResponse<R> {
let auto_id = ui.next_auto_id();
ui.skip_ahead_auto_ids(1);
let response = ui.interact_scope(
Sense::click(),
UiBuilder::new().id_source(auto_id),
|ui, response| {
ui.style_mut().interaction.selectable_labels = false;
let visuals = response
.map(|r| ui.style().interact(&r))
.unwrap_or(&ui.visuals().noninteractive());
let text_color = visuals.text_color();
Frame::none()
.fill(visuals.bg_fill)
.stroke(visuals.bg_stroke)
.rounding(visuals.rounding)
.inner_margin(ui.spacing().button_padding)
.show(ui, |ui| {
ui.visuals_mut().override_text_color = Some(text_color);
content(ui)
})
.inner
},
);
response
.response
.widget_info(|| WidgetInfo::new(egui::WidgetType::Button));
response
}
```
</p>
</details>
https://github.com/user-attachments/assets/281bd65f-f616-4621-9764-18fd0d07698b
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
* Closes https://github.com/emilk/egui/issues/4976
* Part of #4378
* Implements parts of #843
### Background
Some widgets (like `Grid` and `Table`) needs to know the width of future
elements in order to properly size themselves. For instance, the width
of the first column of a grid may not be known until all rows of the
grid has been added, at which point it is too late. Therefore these
widgets store sizes from the previous frame. This leads to "first-frame
jitter", were the content is placed in the wrong place for one frame,
before being accurately laid out in subsequent frames.
### What
This PR adds the function `ctx.request_discard` which discards the
visual output and does another _pass_, i.e. calls the whole app UI code
once again (in eframe this means calling `App::update` again). This will
thus discard the shapes produced by the wrongly placed widgets, and
replace it with new shapes. Note that only the visual output is
discarded - all other output events are accumulated.
Calling `ctx.request_discard` should only be done in very rare
circumstances, e.g. when a `Grid` is first shown. Calling it every frame
will mean the UI code will become unnecessarily slow.
Two safe-guards are in place:
* `Options::max_passes` is by default 2, meaning egui will never do more
than 2 passes even if `request_discard` is called on every pass
* If multiple passes is done for multiple frames in a row, a warning
will be printed on the screen in debug builds:

### Breaking changes
A bunch of things that had "frame" in the name now has "pass" in them
instead:
* Functions called `begin_frame` and `end_frame` are now called
`begin_pass` and `end_pass`
* `FrameState` is now `PassState`
* etc
### TODO
* [x] Figure out good names for everything (`ctx.request_discard`)
* [x] Add API to query if we're gonna repeat this frame (to early-out
from expensive rendering)
* [x] Clear up naming confusion (pass vs frame) e.g. for `FrameState`
* [x] Figure out when to call this
* [x] Show warning on screen when there are several frames in a row with
multiple passes
* [x] Document
* [x] Default on or off?
* [x] Change `Context::frame_nr` name/docs
* [x] Rename `Context::begin_frame/end_frame` and deprecate the old ones
* [x] Test with Rerun
* [x] Document breaking changes
* Closes <https://github.com/emilk/egui/issues/4490>
* [x] I have followed the instructions in the PR template
---
Unfortunately, this PR contains a bunch of breaking changes because
`Context` no longer has one style, but two. I could try to add some of
the methods back if that's desired.
The most subtle change is probably that `style_mut` mutates both the
dark and the light style (which from the usage in egui itself felt like
the right choice but might be surprising to users).
I decided to deviate a bit from the data structure suggested in the
linked issue.
Instead of this:
```rust
pub theme: Theme, // Dark or Light
pub follow_system_theme: bool, // Change [`Self::theme`] based on `RawInput::system_theme`?
```
I decided to add a `ThemePreference` enum and track the current system
theme separately.
This has a couple of benefits:
* The user's theme choice is not magically overwritten on the next
frame.
* A widget for changing the theme preference only needs to know the
`ThemePreference` and not two values.
* Persisting the `theme_preference` is fine (as opposed to persisting
the `theme` field which may actually be the system theme).
The `small_toggle_button` currently only toggles between dark and light
(so you can never get back to following the system). I think it's easy
to improve on this in a follow-up PR :)
I made the function `pub(crate)` for now because it should eventually be
a method on `ThemePreference`, not `Theme`.
To showcase the new capabilities I added a new example that uses
different "accent" colors in dark and light mode:
<img
src="https://github.com/user-attachments/assets/0bf728c6-2720-47b0-a908-18bd250d15a6"
width="250" alt="A screenshot of egui's widget gallery demo in dark mode
using a purple accent color instead of the default blue accent">
<img
src="https://github.com/user-attachments/assets/e816b380-3e59-4f11-b841-8c20285988d6"
width="250" alt="A screenshot of egui's widget gallery demo in light
mode using a green accent color instead of the default blue accent">
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/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/3549
* [X] I have followed the instructions in the PR template
The syntax highlighting font size was always hardcoded to 12 or 10
depending on what case it was hitting (so not consistent). This is
particularly noticeable when you increase the font size to something
larger for the rest of the ui.
With this the default monospace font size is used by default.
Since the issue is closely related to #3549 I decided to implement the
ability to use override_font_id too.
## Visualized
Default monospace is set to 15 in all the pictures
Before/After without syntect:

Before/after _with_ syntect:

Font override after without/with syntect (monospace = 20):

### Breaking changes
- `CodeTheme::dark` and `CodeTheme::light` takes in the font size
- `CodeTheme::from_memory` takes in `Style`
- `highlight` function takes in `Style`
You can see this feature in action
[here](https://docs.rs/sysinfo/latest/src/sysinfo/common/system.rs.html#46)
or on any of dtolnay's crates and many others. I found myself going
through your project code recently on docs.rs and I was a bit sad I
couldn't have this feature enabled. This should fix it at next release.
:)
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/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/4776>
* [x] I have followed the instructions in the PR template
I've been meaning to look into this for a while but finally bit the
bullet this week. Contrary to what I initially thought, the problem of
blurry lines is unrelated to feathering because it also happens with
feathering disabled.
The root cause is that lines tend to land on pixel boundaries, and
because of that, frequently used strokes (e.g. 1pt), end up partially
covering pixels. This is especially noticeable on 1ppp displays.
There were a couple of things to fix, namely: individual lines like
separators and indents but also shape strokes (e.g. Frame).
Lines were easy, I just made sure we round them to the nearest pixel
_center_, instead of the nearest pixel boundary.
Strokes were a little more complicated. To illustrate why, here’s an
example: if we're rendering a 5x5 rect (black fill, red stroke), we
would expect to see something like this:

The fill and the stroke to cover entire pixels. Instead, egui was
painting the stroke partially inside and partially outside, centered
around the shape’s path (blue line):

Both methods are valid for different use-cases but the first one is what
we’d typically want for UIs to feel crisp and pixel perfect. It's also
how CSS borders work (related to #4019 and #3284).
Luckily, we can use the normal computed for each `PathPoint` to adjust
the location of the stroke to be outside, inside, or in the middle.
These also are the 3 types of strokes available in tools like Photoshop.
This PR introduces an enum `StrokeKind` which determines if a
`PathStroke` should be tessellated outside, inside, or _on_ the path
itself. Where "outside" is defined by the directions normals point to.
Tessellator will now use `StrokeKind::Outside` for closed shapes like
rect, ellipse, etc. And `StrokeKind::Middle` for the rest since there's
no meaningful "outside" concept for open paths. This PR doesn't expose
`StrokeKind` to user-land, but we can implement that later so that users
can render shapes and decide where to place the stroke.
### Strokes test
(blue lines represent the size of the rect being rendered)
`Stroke::Middle` (current behavior, 1px and 3px are blurry)

`Stroke::Outside` (proposed default behavior for closed paths)

`Stroke::Inside` (for completeness but unused at the moment)

### Demo App
The best way to review this PR is to run the demo on a 1ppp display,
especially to test hover effects. Everything should look crisper. Also
run it in a higher dpi screen to test that nothing broke 🙏.
Before:

After (notice the sharper lines):

<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/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!
-->
I removed (I hope so) all wildcard imports I found.
For me on my pc this improved the build time:
- for egui -5s
- for eframe -12s
* [x] I have followed the instructions in the PR template
* Closes https://github.com/emilk/egui/issues/1713
I almost went to implement my own undo/redo system, and then found the
egui undoer.
Went to make a small demo to test for myself how it worked, and then
found the linked issue.
So here is a tweaked version of that :)
Co-authored-by: Wybe Westra <w.westra@kwantcontrols.nl>