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
Less risk of confusing the two.
Found and fix a couple real bugs in the process!
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
### Problem
`Column::remainder().clip(true)` grows correctly when the panel is made
wider, but does not shrink when the panel is made narrower.
### Root cause
There are two places in `table.rs` where the `max_used_widths` floor is
applied to column widths. One correctly checks `column.clip`, the other
doesn't.
**Post-render** path (already correct, line ~827):
```rust
if !column.clip {
*column_width = column_width.at_least(max_used_widths[i]);
}
```
`TableState::load` **pre-render path** (the bug, line 647):
```rust
.at_least(column.width_range.min.max(*max_used)) // clip flag ignored
```
In `TableState::load`, `.at_least(max_used)` is applied to every column
regardless of `clip`. For a `Column::remainder()`, `max_used`
accumulates the historically widest rendered content. When the panel
shrinks, this floor prevents the column from computing a smaller width,
creating a cycle where it can never shrink.
### Fix
Apply the same `clip` guard that already exists in the post-render path:
```rust
.at_least(if column.clip {
column.width_range.min
} else {
column.width_range.min.max(*max_used)
})
```
When `clip = true`, the floor is just `width_range.min` (0.0 by
default), allowing the remainder column to shrink freely to the actual
remaining space.
### Minimal reproduction
<details>
<summary>Show code</summary>
```rust
use eframe::egui;
use egui_extras::{Column, TableBuilder};
fn main() -> eframe::Result {
eframe::run_ui_native(
"Column::remainder().clip(true) shrink test",
Default::default(),
|ui, _frame| {
egui::Frame::central_panel(ui.style()).show(ui, |ui| {
let mut text = String::from("some content");
egui::ScrollArea::horizontal().show(ui, |ui| {
TableBuilder::new(ui)
.column(Column::auto())
.column(Column::remainder().clip(true))
.header(20.0, |mut header| {
header.col(|ui| { ui.strong("Auto"); });
header.col(|ui| { ui.strong("Remainder + clip"); });
})
.body(|mut body| {
body.row(18.0, |mut row| {
row.col(|ui| { ui.label("label"); });
row.col(|ui| {
ui.add(
egui::TextEdit::singleline(&mut text)
.desired_width(f32::INFINITY),
);
});
});
});
});
});
},
)
}
```
</details>
**Without the fix:** make the window wider (column grows ✓), then
narrower — column does not shrink and a horizontal scrollbar appears ✗
https://github.com/user-attachments/assets/2b586588-9f72-4a15-80f4-afddadb69441
**With the fix:** the column shrinks correctly and no scrollbar appears
✓
https://github.com/user-attachments/assets/f1655641-e135-489c-9f59-2af3faa887ab
* Closes <https://github.com/emilk/egui/issues/4491>
* [x] I have followed the instructions in the PR template
Lets you create an `EhttpLoader` with arbitrary headers like so:
```rust
cc.egui_ctx.add_bytes_loader(std::sync::Arc::new(
egui_extras::loaders::http_loader::EhttpLoader::default().with_headers(&[
("User-Agent", "foo"),
])
));
```
I'm not sure if there are any problems with installing a second
`EhttpLoader` (in addition to the one installed by default when using
`egui_extras::install_image_loaders(&cc.egui_ctx)`. But I wasn't
sure how else to pass in configuration options to
`install_image_loaders`.
Current behavior fails when translating file uris that contain windows
UNC paths. This commit attempts to fix that behavior by looking at the
hostname attribute of the uri and changing behavior if the hostname is
present.
* Closes <https://github.com/emilk/egui/issues/8161>
* [x] I have followed the instructions in the PR template
## Related
* https://github.com/emilk/egui/pull/5851
* https://github.com/emilk/egui/pull/7988
## What
We want to make it easier to understand the lineage of a `Id` (which is
the parent `Id`, and the parent of that, etc?)
As a first step of that, we want to clarify the different between a
globally unique `Id`, and an `IdSalt`
I also introduced the `AsId` and `AsIdSalt` traits, which are
implemented of anything that implements `Hash` and `Debug`. The `Debug`
half of that is unused here, but will later be used in `Debug` builds to
produce a proper tree.
* 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.
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)