* Part of https://github.com/emilk/egui/issues/8180
Drag-to-scroll is a must-have on touch-screens, since there is no other
way to scroll.
However, when you are not on a touch screens, it is more surprising than
useful.
Enable these new clippy lints and fix all warnings:
* `format_push_string` — use `write!` instead of `s += &format!(…)` to
avoid extra allocations
* `ignored_unit_patterns` — use `()` instead of `_` when matching unit
* `missing_fields_in_debug` — ensure manual `Debug` impls account for
all fields
* `needless_raw_string_hashes` — remove unnecessary `r#` on string
literals
* `ref_option` — prefer `Option<&T>` over `&Option<T>` in function
signatures
This PR fundamentally solves the same problem as
https://github.com/emilk/egui/pull/5827 just implemented with a lot less
ambition and api surface on my end. It contains the bare minimum amount
of changes that I need in order to be able to solve my problem.
My Problem:
I am still suffering from the problem that the TypeIdMap blows up over a
very long time when using my application. (The user generally never
turns off the application, it is intended to be just kept running
forever, some users also never restart their computers) My application
generates a lot of content dynamically so it may for some time display
widgets with a certain set of TypeId's + Id's later hiding them. Some of
the elements that were hidden may turn visible again once an external
event occurs, some may forever be discarded.
I do know myself when which sections of the UI have to be purged because
they will never become visible again, so this PR contains the minimum
amount of necessary functions that allow me to implement this
housekeeping logic on my end.
The existing facilities are insufficient to handle this as the type T
which the TypeId and the hash is derived from are sometimes pub(crate)
privates of widget subcrates or even pub(crate) of egui internals
itself, so its impossible to manually remove those from the TypeIdMap
the only build-in method to remove them is to call "clear" on the
TypeIdMap, however that gets rid of everything, even the elements that
are still shown and should still be in the TypeIdMap.
If the changes in this PR are agreeable to you and you want me to write
unit test for the 4 functions that I have added then tell me and I will
write the tests for you.
If you need anything else changed please tell me.
I ran cargo clippy and cargo fmt, but your check.sh does not work on my
computer.
---------
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
## 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.