## What
Two new builder methods on `DatePickerButton`:
- **`reverse_years(bool)`** — lists years in descending order (newest
first). Useful when users are more likely to pick recent years.
- **`year_scroll_to(i32)`** — scrolls the year dropdown to a specific
year when it first opens, centred in the list. Defaults to the currently
selected year, so the picker no longer opens at the top of a 110-item
list.
## Why
The year `ComboBox` currently always renders in ascending order and
opens scrolled to the top. For a range spanning e.g. 1925–2035, the
current year is buried near the bottom. Users have to scroll past ~100
entries every time they open the picker.
## Notes
- Both options are purely additive builder methods — no breaking
changes.
- `year_scroll_needed` is a persisted state flag that is set on popup
open and cleared after the first scroll, so the user can freely scroll
the list after that.
- Existing behaviour is unchanged when neither method is called.
Co-authored-by: Simon Deurell <simon@deurell.se>
This is a breaking change.
- Enables using `FrameCache` in cases where the cached value cannot be
cloned.
- Improves use cases where only a reference to the cached value is
needed.
- If the user needs an owned value, they can clone it themselves.
Adding a `get_ref` method instead of changing `get` would avoid the
breaking change, but I didn't want to do so because it is kind of
expected for `get` to return `&V` when querying a collection.
Following this MR https://github.com/emilk/egui/pull/7375
Without Syntect, the urrent theme selector is
`global_theme_preference_buttons`. It should be the dark_theme variable
of the local CodeTheme - same as syntect
Tested with
```sh
cargo run
# and
cargo run --features syntect
```
* [x] I have followed the instructions in the PR template
thanks for this amazing library!
<!--
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!
-->
I am having an issue loading this image
"https://stacks.stanford.edu/image/iiif/wy534zh7137/SULAIR_rosette/full/400,/0/default.jpg".
It has a mime type of "image/jpeg; charset=utf-8", which is not common.
The code doesn't parse the full string that includes the optional
parameter "charset=utf-8" to create the jpeg image format.
A line is added to take only the mime type in the media type before the
";" and ignore the optional parameters.
* Closes <https://github.com/emilk/egui/issues/7738>
* [x] I have followed the instructions in the PR template
Co-authored-by: leungkkf <leungkkf@gmail.com>
### What
From the [lint
description](https://rust-lang.github.io/rust-clippy/master/index.html?search=or_fu#or_fun_call):
> The function will always be called. This is only bad if it allocates
or does some non-trivial amount of work.
But also:
> If the function has side-effects, not calling it will change the
semantic of the program, but you shouldn’t rely on that.
>
> The lint also cannot figure out whether the function you call is
actually expensive to call or not.
Still worth it to keep our happy paths clean, imo.
## Short bluesky announcement:
We just released egui 0.33.0!
Highlights:
- `egui::Plugin` a improved way to create and access egui plugins
- [kitdiff](https://github.com/rerun-io/kitdiff), a viewer for
egui_kittest image snapshots (and a general image diff tool)
- better kerning (check the diff on
[kitdiff](https://rerun-io.github.io/kitdiff/?url=https://github.com/emilk/egui/pull/7431))
https://github.com/user-attachments/assets/971f0493-6dae-42e5-8019-58b74cf5d203
## Relaese Changelog:
egui is an easy-to-use immediate mode GUI for Rust that runs on both web
and native.
Try it now: <https://www.egui.rs/>
egui development is sponsored by [Rerun](https://www.rerun.io/), a
startup building an SDK for visualizing streams of multimodal data.
# egui 0.33.0 changelog
Highlights from this release:
- `egui::Plugin` a improved way to create and access egui plugins
- [kitdiff](https://github.com/rerun-io/kitdiff), a viewer for
egui_kittest image snapshots (and a general image diff tool)
- better kerning
### Improved kerning
As a step towards using [parley](https://github.com/linebender/parley)
for font rendering, @valadaptive has refactored the font loading and
rendering code. A result of this (next to the font rendering code being
much nicer now) is improved kerning.
Notice how the c moved away from the k:

### `egui::Plugin` trait
We've added a new trait-based plugin api, meant to replace
`Context::on_begin_pass` and `Context::on_end_pass`.
This makes it a lot easier to handle state in your plugins. Instead of
having to write to egui memory it can live right on your plugin struct.
The trait based api also makes easier to add new hooks that plugins can
use. In addition to `on_begin_pass` and `on_end_pass`, the `Plugin`
trait now has a `input_hook` and `output_hook` which you can use to
inspect / modify the `RawInput` / `FullOutput`.
### kitdiff, a image diff viewer
At rerun we have a ton of snapshots. Some PRs will change most of them
(e.g. [the](https://github.com/rerun-io/rerun/pull/11253/files)
[one](https://rerun-io.github.io/kitdiff/?url=https://github.com/rerun-io/rerun/pull/11253/files)
that updated egui and introduced the kerning improvements, ~500
snapshots changed!).
If you really want to look at every changed snapshot it better be as
efficient as possible, and the experience on github, fiddeling with the
sliders, is kind of frustrating.
In order to fix this, we've made
[kitdiff](https://rerun-io.github.io/kitdiff/).
You can use it locally via
- `kitdiff files .` will search for .new.png and .diff.png files
- `kitdiff git` will compare the current files to the default branch
(main/master)
Or in the browser via
- going to https://rerun-io.github.io/kitdiff/ and pasting a PR or
github artifact url
- linking to kitdiff via e.g. a github workflow
`https://rerun-io.github.io/kitdiff/?url=<link_to_pr_or_artefact>`
To install kitdiff run `cargo install --git
https://github.com/rerun-io/kitdiff`
Here is a video showing the kerning changes in kitdiff ([try it
yourself](https://rerun-io.github.io/kitdiff/?url=https://github.com/rerun-io/rerun/pull/11253/files)):
https://github.com/user-attachments/assets/74640af1-09ba-435a-9d0c-2cbeee140c8f
### Migration guide
- `egui::Mutex` now has a timeout as a simple deadlock detection
- If you use a `egui::Mutex` in some place where it's held for longer
than a single frame, you should switch to the std mutex or parking_lot
instead (egui mutexes are wrappers around parking lot)
- `screen_rect` is deprecated
- In order to support safe areas, egui now has `viewport_rect` and
`content_rect`.
- Update all usages of `screen_rect` to `content_rect`, unless you are
sure that you want to draw outside the `safe area` (which would mean
your Ui may be covered by notches, system ui, etc.)
This was initially a PR to add kitdiff, but this now lives in it's own
crate: https://github.com/rerun-io/kitdiff
I needed to make the image loaders public, this way it's possible to
compose image loaders together (which allowed me to create a image diff
loader that uses two other image loaders). But you can't use the
`ctx.try_load_image` since that would deadlock, so you have to store a
reference to the other loader in the wrapping loader.
Currently, DatePickerButton will close without saving whenever a user
clicks a dropdown from year/month/date. The issue is caused because the
system mistakenly interprets the user as clicking off of the calendar.
This is unexpected and creates an unpleasant experience for the user.
This change now allows the user to use the dropdowns as expected; it
will close on save or cancel. The calendar still closes when user clicks
off of it, as before. The changes here are made in:
crates/egui_extras/src/datepicker/button.rs
I will admit that I am not an experienced Rust developer. The changes
were made with the help of ChatGPT 4.0.
I have tested the changes locally, as I am using the date picker in my
project.
* Closes <https://github.com/emilk/egui/issues/THE_RELEVANT_ISSUE>
* [x] I have followed the instructions in the PR template
---------
Co-authored-by: Lucas Meurer <hi@lucasmerlin.me>
* Recently CI runs started to hang randomly:
https://github.com/emilk/egui/actions/runs/17427449210/job/49477714447?pr=7359
This fixes the deadlock and adds the basic deadlock detection we also
added to Mutexes in #7468.
Also, interestingly, the more sophisticated deadlock detection (behind
the deadlock_detection feature) didn't catch this for some reason. I
wonder why it exists in the first place, when parking_lot also has built
in deadlock detection? It also seems to make tests slower, widget_tests
usually needs ~30s, with the deadlock detection removed its only ~12s.
This is the same issue that was fixed for the http bytes loader in
239ade9a59
* [x] I have followed the instructions in the PR template
----------------
Funnily, [this
comment](https://github.com/emilk/egui/issues/3747#issuecomment-1872192997)
describes exactly how I encountered this issue:
> That assert is wrong if something calls forget between the start of
the request and the end of it.
I'm displaying lots of images in a scrolling grid (20 or so visible at a
time). It seems like image textures are never freed up automatically so
it stacks up a lot meaning I have to unload the image textures manually
with `egui::Context::forget_image()` in each `eframe::App::update()`
call for the images that are no longer shown when scrolling.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This pull request modifies the `BytesLoader` implementation for
`FileLoader` in `crates/egui_extras/src/loaders/file_loader.rs` to
improve thread safety and handle unexpected states more gracefully.
### Changes to thread safety and state handling:
* Updated the cache logic to check if the `uri` exists in the cache
before inserting the result. If the `uri` is not found, a log message is
added to indicate the loading was canceled. This change prevents
overwriting cache entries unexpectedly.
* Closes <https://github.com/emilk/egui/issues/6755>
* [x] I have followed the instructions in the PR template
<!--
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/3964>
* [X] I have followed the instructions in the PR template
This PR adds support for syntax highlighting with custom
`syntect::parsing::SyntaxSet` and `syntect::highlighting::ThemeSet`. It
adds a new `egui_extras::highlight_with` function (enabled with `feature
= "syntect"`), which takes a new public`struct SyntectSettings`
containing the syntax and theme sets.
```rust
let mut builder = SyntaxSetBuilder::new();
builder.add_from_folder("syntax", true).unwrap();
let ps = builder.build();
let ts = syntect::highlighting::ThemeSet::load_defaults();
let syntax =
egui_extras::syntax_highlighting::SyntectSettings { ps, ts };
// ...elsewhere
egui_extras::syntax_highlighting::highlight_with(
ui.ctx(),
ui.style(),
&theme,
buf,
"rhai",
&syntax,
);
```
There's a little bit of architectural complexity, but it all emerges
naturally from the problem's constraints.
Previously, the `Highlighter` both contained the `syntect` settings
_and_ implemented `egui::cache::ComputerMut` to highlight a string; the
settings would never change. After this change, the `syntect` settings
have become part of the cache key, so we should redo highlighting if
they change. The `Highlighter` becomes an empty `struct` which just
serves to implement `ComputerMut`.
`SyntaxSet` and `ThemeSet` are not hasheable themselves, so can't be
used as cache keys direction. Instead, we can use the *address* of the
`&SyntectSettings` as the key. This requires an object with a custom
`Hash` implementation, so I added a new `HighlightSettings(&'a
SyntectSettings)`, implementing `Hash` using `std::ptr::hash` on the
reference. I think using the address is reasonable – it would be _weird_
for a user to be constantly moving around their `SyntectSettings`, and
there's a warning in the docstring to this effect.
To work _without_ custom settings, `SyntectSettings::default` preserves
the same behavior as before, using `SyntaxSet::load_defaults_newlines`
and `ThemeSet::load_defaults`. If the user doesn't provide custom
settings, then we instantiate a singleton `SyntectSettings` in `data`
and use it; this will only be constructed once.
Finally, in cases where the `syntect` feature is disabled,
`SyntectSettings` are replaced with a unit `()`. This adds a _tiny_
amount of overhead – one singleton `Arc<()>` allocation and a lookup in
`data` per `highlight` – but I think that's better than dramatically
different implementations. If this is an issue, I can refactor to make
it zero-cost when the feature is disabled.
Add the ability to set the `DatePickerButton`'s start and end years via
new `start_year` and `end_year` methods.
Continue to use the existing today - 100 years and today + 10 years
behavior if a year is not specified.
* This more fully closes <https://github.com/emilk/egui/issues/3597> and
expands on <https://github.com/emilk/egui/pull/3599>.
* [x] I have followed the instructions in the PR template
Today each widget does its own custom layout, which has some drawbacks:
- not very flexible
- you can add an `Image` to `Button` but it will always be shown on the
left side
- you can't add a `Image` to a e.g. a `SelectableLabel`
- a lot of duplicated code
This PR introduces `Atoms` and `AtomLayout` which abstracts over "widget
content" and layout within widgets, so it'd be possible to add images /
text / custom rendering (for e.g. the checkbox) to any widget.
A simple custom button implementation is now as easy as this:
```rs
pub struct ALButton<'a> {
al: AtomicLayout<'a>,
}
impl<'a> ALButton<'a> {
pub fn new(content: impl IntoAtomics) -> Self {
Self { al: content.into_atomics() }
}
}
impl<'a> Widget for ALButton<'a> {
fn ui(mut self, ui: &mut Ui) -> Response {
let response = ui.ctx().read_response(ui.next_auto_id());
let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
ui.style().interact(&response)
});
self.al.frame = self
.al
.frame
.inner_margin(ui.style().spacing.button_padding)
.fill(visuals.bg_fill)
.stroke(visuals.bg_stroke)
.corner_radius(visuals.corner_radius);
self.al.show(ui)
}
}
```
The initial implementation only does very basic layout, just enough to
be able to implement most current egui widgets, so:
- only horizontal layout
- everything is centered
- a single item may grow/shrink based on the available space
- everything can be contained in a Frame
There is a trait `IntoAtoms` that conveniently allows you to construct
`Atoms` from a tuple
```
ui.button((Image::new("image.png"), "Click me!"))
```
to get a button with image and text.
This PR reimplements three egui widgets based on the new AtomLayout:
- Button
- matches the old button pixel-by-pixel
- Button with image is now [properly
aligned](https://github.com/emilk/egui/pull/5830/files#diff-962ce2c68ab50724b01c6b64c683c4067edd9b79fcdcb39a6071021e33ebe772)
in justified layouts
- selected button style now matches SelecatbleLabel look
- For some reason the DragValue text seems shifted by a pixel almost
everywhere, but I think it's more centered now, yay?
- Checkbox
- basically pixel-perfect but apparently the check mesh is very slightly
different so I had to update the snapshot
- somehow needs a bit more space in some snapshot tests?
- RadioButton
- pixel-perfect
- somehow needs a bit more space in some snapshot tests?
I plan on updating TextEdit based on AtomLayout in a separate PR (so
you could use it to add a icon within the textedit frame).
This fixes bugs related to how an `Image` follows the size of an SVG.
We track the "source size" of each image, i.e. the original width/height
of the SVG, which can be different from whatever it was rasterized as.
* Closes https://github.com/emilk/egui/issues/3501
The problem occurs when you want to render the same SVG at different
scales, either at the same time in different parts of your UI, or at two
different times (e.g. the DPI changes).
The solution is to use the `SizeHint` as part of the key.
However, when you have an SVG in a resizable container, that can lead to
hundreds of versions of the same SVG. So new eviction code is added to
handle this case.
With kittest it was difficult to wait for images to be loaded before
taking a snapshot test.
This PR adds `Harness::with_wait_for_pending_images` (true by default)
which will cause `Harness::run` to sleep until all images are loaded (or
`HarnessBuilder::with_max_steps` is exceeded).
It also adds a new ImageLoader::has_pending and
BytesLoader::has_pending, which should be implemented if things are
loaded / decoded asynchronously.
It reverts https://github.com/emilk/egui/pull/6901 which was my previous
attempt to fix this (but this didn't work since only the tested crate is
compiled with cfg(test) and not it's dependencies)
https://github.com/emilk/egui/pull/5394 made it so images would load on
a background thread, which is great. But this makes snapshot tests that
have images via include_image!() flakey since they might or might not
load by the time the snapshot is rendered.
This is no perfect solution, since the underlying problem of "waiting
for something async to happen" still exists and we should add some more
general solution for that.
* Closes <https://github.com/emilk/egui/issues/5406>
* [x] I have followed the instructions in the PR template
The changes follow what is described in the issue with a couple changes:
- Scroll bars are not hidden when dragging is disabled, for that
`ScrollArea::scroll_bar_visibility()` has to be used, this is as not to
limit the user configurability by imposing a specific function. The user
might want to retain the scrollbars visibility to show the current
position.
- The input for mouse wheel scrolling is unchanged. When I inspected the
code initially I made a mistake in recognizing the source of scrolling.
Current implementation is in fact using
`InputState::smooth_scroll_delta` and not `PassState::scroll_delta`,
therefore it is possible to prevent scrolling by setting the
`InputState::smooth_scroll_delta` to zero before painting the
`ScrollArea`.
A simple demo is available at
https://github.com/MStarha/egui_scroll_area_test