mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Merge branch 'main' into lucas/malmal/main
This commit is contained in:
126
CHANGELOG.md
126
CHANGELOG.md
@@ -14,6 +14,132 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
|
||||
### Highlights from this release
|
||||
- Sharper text unlocked by switching font rendering crate to [`skrifa`](https://crates.io/crates/skrifa)
|
||||
- Fade out edges of `ScrollArea`s
|
||||
- Use `Ui` as the main entrypoint
|
||||
|
||||
### Skrifa and font hinting
|
||||
The font rendering backend was switched from `ab_glyph` to `skrifa` + `vello_cpu`. This enabled us support
|
||||
font hinting and variations. It also paves the way for more font improvements in the future, like support for color
|
||||
emojis and adding helpers for variations like `RichText::bold`.
|
||||
|
||||
Font hinting makes text more clear (look at the =):
|
||||
|
||||
https://github.com/user-attachments/assets/ea9151ec-869f-4c05-ab59-836114683417
|
||||
|
||||
We now support setting variable font parameters:
|
||||
|
||||
https://github.com/user-attachments/assets/0febde1c-ebf6-4d85-8f96-86ec0f934ecf
|
||||
|
||||
(Unfortunately there is currently a bug with variations, meaning changing them live like this won't work in practise.
|
||||
There is a [draft PR](https://github.com/emilk/egui/pull/8029) to fix it, but it didn't make the release)
|
||||
|
||||
* Replace ab_glyph with Skrifa + vello_cpu; enable font hinting [#7694](https://github.com/emilk/egui/pull/7694) by [@valadaptive](https://github.com/valadaptive)
|
||||
* Add font variations API [#7859](https://github.com/emilk/egui/pull/7859) by [@valadaptive](https://github.com/valadaptive)
|
||||
|
||||
### More `Ui`, less `Context`
|
||||
egui has long had a confusing overlap in responsibilities between `Context` and `Ui`.
|
||||
In particular, you could add panels to either one (or both!).
|
||||
In this release, we switch from having `Context` be the main entrypoint, and instead provide whole-app `Ui`.
|
||||
In egui we've replaced `Context::run` with `Context::run_ui`, and changed viewports to be given a `&mut Ui` instead of `Context`.
|
||||
In `eframe` we've deprecated `App::update` replaced it with `App::ui` (which provides a `&mut Ui` instead of a `&Context`).
|
||||
|
||||
In addition to this, `Ui` now derefs to `Context`, so all code like `ui.ctx().input(…)` can now be written `ui.input(…)`.
|
||||
This means you are much less likely to have to use naked `Context`s.
|
||||
`Context` can still be useful though, since they implement `Clone` and can be sent to other threads so you can call `.request_repaint` on them.
|
||||
|
||||
* Add `Context::run_ui` [#7736](https://github.com/emilk/egui/pull/7736) by [@emilk](https://github.com/emilk)
|
||||
* Add `Deref<Target = Context>` for `Ui` [#7770](https://github.com/emilk/egui/pull/7770) by [@emilk](https://github.com/emilk)
|
||||
* Replace `App::update` with `fn logic` and `fn ui` [#7775](https://github.com/emilk/egui/pull/7775) by [@emilk](https://github.com/emilk)
|
||||
* Rename `Context::style` to `global_style`; avoid confusion w/ `Ui::style` [#7772](https://github.com/emilk/egui/pull/7772) by [@emilk](https://github.com/emilk)
|
||||
* Rename functions in `Context` to avoid confusion [#7773](https://github.com/emilk/egui/pull/7773) by [@emilk](https://github.com/emilk)
|
||||
* Viewports: give the caller a `Ui` instead of `Context` [#7779](https://github.com/emilk/egui/pull/7779) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### Changed panel API
|
||||
As part of the above work, we have unified the panel API.
|
||||
`SidePanel` and `TopBottomPanel` are deprecated, replaced by a single `Panel`.
|
||||
Furthermore, it is now deprecated to use panels directly on `Context`. Use the `show_inside` functions instead, acting on `Ui`s.
|
||||
|
||||
This unification and simplification will make it easier to maintain and improve panels going forward.
|
||||
|
||||
* Add `Panel` to replace `SidePanel` and `TopBottomPanel` [#5659](https://github.com/emilk/egui/pull/5659) by [@sharky98](https://github.com/sharky98)
|
||||
* Deprecate using `Panel` directly on a `Context` [#7781](https://github.com/emilk/egui/pull/7781) by [@emilk](https://github.com/emilk)
|
||||
* Deprecate `CentralPanel::show` [#7783](https://github.com/emilk/egui/pull/7783) by [@emilk](https://github.com/emilk)
|
||||
* Deprecate `Context::used_size` and `Context::available_rect` [#7788](https://github.com/emilk/egui/pull/7788) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### ⭐ Added
|
||||
* Add `is_scrolling`/`is_smooth_scrolling` util, checking for active scroll action [#7669](https://github.com/emilk/egui/pull/7669) by [@IsseW](https://github.com/IsseW)
|
||||
* Allow multiple atoms in `Button::shortcut_text` and `right_text` [#7696](https://github.com/emilk/egui/pull/7696) by [@emilk](https://github.com/emilk)
|
||||
* Add `ScrollArea::content_margin` [#7722](https://github.com/emilk/egui/pull/7722) by [@emilk](https://github.com/emilk)
|
||||
* Per-widget style [#7667](https://github.com/emilk/egui/pull/7667) by [@AdrienZianne](https://github.com/AdrienZianne)
|
||||
* Plugin: export `TypedPluginGuard` and `TypedPluginHandle` [#7780](https://github.com/emilk/egui/pull/7780) by [@apekros](https://github.com/apekros)
|
||||
* Add `ViewportInfo::occluded` and `visible` [#7948](https://github.com/emilk/egui/pull/7948) by [@emilk](https://github.com/emilk)
|
||||
* Add `Atom` prefix/suffix support to `DragValue` [#7949](https://github.com/emilk/egui/pull/7949) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* ⚠️ Atom improvements: `Atom::id`, `align`, `closure`, `max_size` [#7958](https://github.com/emilk/egui/pull/7958) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add `DebugOptions::warn_if_rect_changes_id` [#7984](https://github.com/emilk/egui/pull/7984) by [@emilk](https://github.com/emilk)
|
||||
* `TextEdit` `Atom` prefix/suffix [#7587](https://github.com/emilk/egui/pull/7587) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add `Button::left_text` [#7955](https://github.com/emilk/egui/pull/7955) by [@rustbasic](https://github.com/rustbasic)
|
||||
* Add `Response::parent_id` [#8010](https://github.com/emilk/egui/pull/8010) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add `Context::text_edit_focused` [#8014](https://github.com/emilk/egui/pull/8014) by [@emilk](https://github.com/emilk)
|
||||
* Add `Context::time` [#8017](https://github.com/emilk/egui/pull/8017) by [@emilk](https://github.com/emilk)
|
||||
* Add `Ui::is_tooltip` [#8016](https://github.com/emilk/egui/pull/8016) by [@emilk](https://github.com/emilk)
|
||||
* Add `UiStack::bg_color` [#8020](https://github.com/emilk/egui/pull/8020) by [@emilk](https://github.com/emilk)
|
||||
* Make `egui::IdSet` public [#8019](https://github.com/emilk/egui/pull/8019) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Add raw key methods to TypeIdMap [#8007](https://github.com/emilk/egui/pull/8007) by [@AlexanderSchuetz97](https://github.com/AlexanderSchuetz97)
|
||||
|
||||
### 🔧 Changed
|
||||
* Remove `accesskit` feature and always depend on `accesskit` [#7701](https://github.com/emilk/egui/pull/7701) by [@emilk](https://github.com/emilk)
|
||||
* Update MSRV from 1.88 to 1.92 [#7793](https://github.com/emilk/egui/pull/7793) by [@JasperBRiedel](https://github.com/JasperBRiedel)
|
||||
* Improve modifier handling when scrolling [#7678](https://github.com/emilk/egui/pull/7678) by [@emilk](https://github.com/emilk)
|
||||
* Apply preferred font weight when loading variable fonts [#7790](https://github.com/emilk/egui/pull/7790) by [@pmnxis](https://github.com/pmnxis)
|
||||
* Make scroll bars and resize splitters visible to accesskit [#7804](https://github.com/emilk/egui/pull/7804) by [@emilk](https://github.com/emilk)
|
||||
* Allow moving existing widgets to the top of interaction stack [#7805](https://github.com/emilk/egui/pull/7805) by [@emilk](https://github.com/emilk)
|
||||
* Slightly change interact behavior around thin splitters [#7806](https://github.com/emilk/egui/pull/7806) by [@emilk](https://github.com/emilk)
|
||||
* Move window resize interaction to be over contents [#7807](https://github.com/emilk/egui/pull/7807) by [@emilk](https://github.com/emilk)
|
||||
* Don't expand widgets on hover [#7808](https://github.com/emilk/egui/pull/7808) by [@emilk](https://github.com/emilk)
|
||||
* Make `FrameCache::get` return a reference instead of cloning the cached value [#7834](https://github.com/emilk/egui/pull/7834) by [@KonaeAkira](https://github.com/KonaeAkira)
|
||||
* Update selected dependencies [#7920](https://github.com/emilk/egui/pull/7920) by [@oscargus](https://github.com/oscargus)
|
||||
* Make `Galley::pos_from_layout_cursor` `pub` [#7864](https://github.com/emilk/egui/pull/7864) by [@dionb](https://github.com/dionb)
|
||||
* Update accesskit to 0.24.0 (and related deps) [#7850](https://github.com/emilk/egui/pull/7850) by [@delan](https://github.com/delan)
|
||||
* Quit on Ctrl-Q [#7985](https://github.com/emilk/egui/pull/7985) by [@emilk](https://github.com/emilk)
|
||||
* Fade out the edges of `ScrollAreas` [#8018](https://github.com/emilk/egui/pull/8018) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🔥 Removed
|
||||
* Remove `CacheTrait::as_any_mut` [#7833](https://github.com/emilk/egui/pull/7833) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🐛 Fixed
|
||||
* Fix: ensure `CentralPanel::show_inside` allocates space in parent [#7778](https://github.com/emilk/egui/pull/7778) by [@emilk](https://github.com/emilk)
|
||||
* Heed constrain rect when auto-positioning windows [#7786](https://github.com/emilk/egui/pull/7786) by [@emilk](https://github.com/emilk)
|
||||
* Fix jitter when hovering edge of scroll area close to resize splitter [#7803](https://github.com/emilk/egui/pull/7803) by [@emilk](https://github.com/emilk)
|
||||
* Don't focus Areas, Windows and ScrollAreas [#7827](https://github.com/emilk/egui/pull/7827) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Fix backspacing leaving last character in IME prediction not removed on macOS native and Safari [#7810](https://github.com/emilk/egui/pull/7810) by [@umajho](https://github.com/umajho)
|
||||
* Implemented distance threshold for double/triple clicks [#7817](https://github.com/emilk/egui/pull/7817) by [@bl4ze4447](https://github.com/bl4ze4447)
|
||||
* Fix `CentralPanel::show_inside_dyn` to round `panel_rect` [#7868](https://github.com/emilk/egui/pull/7868) by [@ripopov](https://github.com/ripopov)
|
||||
* Stop ctrl+arrow etc from moving focus [#7897](https://github.com/emilk/egui/pull/7897) by [@emilk](https://github.com/emilk)
|
||||
* Fix scroll area not consuming scroll events [#7904](https://github.com/emilk/egui/pull/7904) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Pass in an explicit id in `UiBuilder`, to avoid wrapping passed in ids with Id::new() [#7925](https://github.com/emilk/egui/pull/7925) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Fix crash when dragging a DragValue through small floats [#7939](https://github.com/emilk/egui/pull/7939) by [@Fyrecean](https://github.com/Fyrecean)
|
||||
* Fix emoji icon font [#7940](https://github.com/emilk/egui/pull/7940) by [@Jhynjhiruu](https://github.com/Jhynjhiruu)
|
||||
* Fixes the overly aggressive overflow elision in `truncate()` and similar for os scaling other than 100% [#7867](https://github.com/emilk/egui/pull/7867) by [@RndUsr123](https://github.com/RndUsr123)
|
||||
* Fix text color when selecting newline character [#7951](https://github.com/emilk/egui/pull/7951) by [@emilk](https://github.com/emilk)
|
||||
* Fix: repaint on drag-and-drop files [#7953](https://github.com/emilk/egui/pull/7953) by [@emilk](https://github.com/emilk)
|
||||
* Fix instable IDs following animated panels [#7994](https://github.com/emilk/egui/pull/7994) by [@emilk](https://github.com/emilk)
|
||||
* Enables every combination of `TextEdit` and `LayoutJob` alignments [#7831](https://github.com/emilk/egui/pull/7831) by [@RndUsr123](https://github.com/RndUsr123)
|
||||
* Fix `horizontal_wrapping` row height after using `text_edit_multiline` [#8000](https://github.com/emilk/egui/pull/8000) by [@optozorax](https://github.com/optozorax)
|
||||
* Fix menu keyboard toggle for open submenus [#7957](https://github.com/emilk/egui/pull/7957) by [@fjkorf](https://github.com/fjkorf)
|
||||
* Fix: `Visuals::interact_cursor` support in `Button` [#7986](https://github.com/emilk/egui/pull/7986) by [@mango766](https://github.com/mango766)
|
||||
|
||||
### 🚀 Performance
|
||||
* Shrink the byte-size of `Response` slightly [#8011](https://github.com/emilk/egui/pull/8011) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
* Treat `.` as a word-splitter in text navigation [#7741](https://github.com/emilk/egui/pull/7741) by [@emilk](https://github.com/emilk)
|
||||
* Change text color of selected text [#7691](https://github.com/emilk/egui/pull/7691) by [@emilk](https://github.com/emilk)
|
||||
|
||||
33
Cargo.lock
33
Cargo.lock
@@ -1193,7 +1193,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53"
|
||||
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cint",
|
||||
@@ -1205,7 +1205,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "eframe"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1244,7 +1244,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash",
|
||||
@@ -1264,7 +1264,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-wgpu"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1282,7 +1282,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"accesskit_winit",
|
||||
"arboard",
|
||||
@@ -1305,7 +1305,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_demo_app"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
@@ -1330,12 +1330,11 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_demo_lib"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"document-features",
|
||||
@@ -1352,7 +1351,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_extras"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"document-features",
|
||||
@@ -1371,7 +1370,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_glow"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
@@ -1390,7 +1389,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_kittest"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"dify",
|
||||
"document-features",
|
||||
@@ -1410,7 +1409,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui_tests"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"egui",
|
||||
"egui_extras",
|
||||
@@ -1440,7 +1439,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"document-features",
|
||||
@@ -1538,7 +1537,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1566,7 +1565,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "epaint_default_fonts"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
@@ -3434,7 +3433,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||
|
||||
[[package]]
|
||||
name = "popups"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"env_logger",
|
||||
@@ -5822,7 +5821,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
|
||||
26
Cargo.toml
26
Cargo.toml
@@ -24,7 +24,7 @@ members = [
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.92"
|
||||
version = "0.33.3"
|
||||
version = "0.34.1"
|
||||
|
||||
|
||||
[profile.release]
|
||||
@@ -55,18 +55,18 @@ opt-level = 2
|
||||
|
||||
|
||||
[workspace.dependencies]
|
||||
emath = { version = "0.33.3", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.33.3", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.33.3", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.33.3", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.33.3", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.33.3", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.33.3", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.33.3", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.33.3", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.33.3", path = "crates/eframe", default-features = false }
|
||||
emath = { version = "0.34.1", path = "crates/emath", default-features = false }
|
||||
ecolor = { version = "0.34.1", path = "crates/ecolor", default-features = false }
|
||||
epaint = { version = "0.34.1", path = "crates/epaint", default-features = false }
|
||||
epaint_default_fonts = { version = "0.34.1", path = "crates/epaint_default_fonts" }
|
||||
egui = { version = "0.34.1", path = "crates/egui", default-features = false }
|
||||
egui-winit = { version = "0.34.1", path = "crates/egui-winit", default-features = false }
|
||||
egui_extras = { version = "0.34.1", path = "crates/egui_extras", default-features = false }
|
||||
egui-wgpu = { version = "0.34.1", path = "crates/egui-wgpu", default-features = false }
|
||||
egui_demo_lib = { version = "0.34.1", path = "crates/egui_demo_lib", default-features = false }
|
||||
egui_glow = { version = "0.34.1", path = "crates/egui_glow", default-features = false }
|
||||
egui_kittest = { version = "0.34.1", path = "crates/egui_kittest", default-features = false }
|
||||
eframe = { version = "0.34.1", path = "crates/eframe", default-features = false }
|
||||
|
||||
accesskit = "0.24.0"
|
||||
accesskit_consumer = "0.35.0"
|
||||
|
||||
@@ -6,6 +6,14 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -7,6 +7,35 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
* `wgpu` backend: Enable WebGL fallback [#8038](https://github.com/emilk/egui/pull/8038) by [@emilk](https://github.com/emilk)
|
||||
* Only apply cursor style to the `<canvas>` [#8036](https://github.com/emilk/egui/pull/8036) by [@mkeeter](https://github.com/mkeeter)
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
### ⭐ Added
|
||||
* Add feature `wgpu_no_default_features` [#7700](https://github.com/emilk/egui/pull/7700) by [@emilk](https://github.com/emilk)
|
||||
* Add `ViewportInfo::occluded` and `visible` [#7948](https://github.com/emilk/egui/pull/7948) by [@emilk](https://github.com/emilk)
|
||||
* Add `eframe::WindowChromeMetrics` (macOS only) [#8015](https://github.com/emilk/egui/pull/8015) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🔧 Changed
|
||||
* Replace `App::update` with `fn logic` and `fn ui` [#7775](https://github.com/emilk/egui/pull/7775) by [@emilk](https://github.com/emilk)
|
||||
* Make `wgpu` the default renderer for `eframe` and egui.rs [#7615](https://github.com/emilk/egui/pull/7615) by [@emilk](https://github.com/emilk)
|
||||
* Roll out new egui icon and logo [#7995](https://github.com/emilk/egui/pull/7995) by [@emilk](https://github.com/emilk)
|
||||
* Update wasm-bindgen to 0.2.108, and ehttp to 0.7.1 [#7996](https://github.com/emilk/egui/pull/7996) by [@emilk](https://github.com/emilk)
|
||||
* Update to `wgpu` 29 [#7990](https://github.com/emilk/egui/pull/7990) by [@cwfitzgerald](https://github.com/cwfitzgerald)
|
||||
* Allow fallback from smithay to arboard when getting clipboard [#7976](https://github.com/emilk/egui/pull/7976) by [@wizzeh](https://github.com/wizzeh)
|
||||
|
||||
### 🐛 Fixed
|
||||
* Fix: update get_proc_address to use Arc for better ownership management [#7922](https://github.com/emilk/egui/pull/7922) by [@Wybxc](https://github.com/Wybxc)
|
||||
* Much improved IME [#7967](https://github.com/emilk/egui/pull/7967) by [@umajho](https://github.com/umajho)
|
||||
* Improve behavior of invisible windows [#7905](https://github.com/emilk/egui/pull/7905) by [@gcailly](https://github.com/gcailly)
|
||||
|
||||
### 🚀 Performance
|
||||
* Avoid repaints on device mouse motion outside window [#7866](https://github.com/emilk/egui/pull/7866) by [@inktomi](https://github.com/inktomi)
|
||||
* Only run `App::ui` if the application is visible [#7950](https://github.com/emilk/egui/pull/7950) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ web_screen_reader = ["web-sys/SpeechSynthesis", "web-sys/SpeechSynthesisUtteranc
|
||||
## See <https://github.com/emilk/egui/issues/5889> for more details.
|
||||
##
|
||||
## By default, eframe will prefer WebGPU over WebGL, but
|
||||
## you can configure this at run-time with [`NativeOptions::wgpu_options`].
|
||||
## you can configure this at run-time with `WebOptions::wgpu_options`.
|
||||
wgpu = ["wgpu_no_default_features", "egui-wgpu/default"]
|
||||
|
||||
## This is exactly like the `wgpu` feature, but does NOT enable the default features of `wgpu` and `egui-wgpu`.
|
||||
@@ -155,7 +155,6 @@ glutin-winit = { workspace = true, optional = true, default-features = false, fe
|
||||
"wgl",
|
||||
] }
|
||||
home = { workspace = true, optional = true }
|
||||
wgpu = { workspace = true, optional = true }
|
||||
|
||||
# mac:
|
||||
[target.'cfg(any(target_os = "macos"))'.dependencies]
|
||||
@@ -170,12 +169,16 @@ objc2-app-kit = { workspace = true, default-features = false, features = [
|
||||
"std",
|
||||
"NSApplication",
|
||||
"NSBitmapImageRep",
|
||||
"NSButton",
|
||||
"NSControl",
|
||||
"NSGraphics",
|
||||
"NSImage",
|
||||
"NSImageRep",
|
||||
"NSMenu",
|
||||
"NSMenuItem",
|
||||
"NSResponder",
|
||||
"NSView",
|
||||
"NSWindow",
|
||||
] }
|
||||
|
||||
# windows:
|
||||
|
||||
@@ -159,7 +159,7 @@ pub use {egui, egui::emath, egui::epaint};
|
||||
pub use {egui_glow, glow};
|
||||
|
||||
#[cfg(feature = "wgpu_no_default_features")]
|
||||
pub use {egui_wgpu, wgpu};
|
||||
pub use {egui_wgpu, egui_wgpu::wgpu};
|
||||
|
||||
mod epi;
|
||||
|
||||
@@ -190,6 +190,9 @@ pub use web::{WebLogger, WebRunner};
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
mod native;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use native::macos::WindowChromeMetrics;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
|
||||
pub use native::run::EframeWinitApplication;
|
||||
|
||||
76
crates/eframe/src/native/macos.rs
Normal file
76
crates/eframe/src/native/macos.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use egui::Vec2;
|
||||
use objc2_app_kit::{NSView, NSWindow, NSWindowButton};
|
||||
use raw_window_handle::{AppKitWindowHandle, RawWindowHandle};
|
||||
|
||||
/// Size of the "traffic lights" (red/yellow/green close/minimize/maximize buttons)
|
||||
/// on the native macOS window.
|
||||
///
|
||||
/// This is very useful together with [`egui::ViewportBuilder::with_fullsize_content_view`].
|
||||
#[derive(Debug)]
|
||||
pub struct WindowChromeMetrics {
|
||||
/// Size of the "traffic lights" (red/yellow/green close/minimize/maximize buttons),
|
||||
/// including margins.
|
||||
///
|
||||
/// The unit here is in "native scale", which means it needs to be divided by [`egui::Context::zoom_factor`]
|
||||
/// to get the size in egui points.
|
||||
pub traffic_lights_size: Vec2,
|
||||
}
|
||||
|
||||
impl WindowChromeMetrics {
|
||||
/// Get the window chrome metrics for a given window handle.
|
||||
pub fn from_window_handle(window_handle: &RawWindowHandle) -> Option<Self> {
|
||||
window_chrome_metrics(window_handle)
|
||||
}
|
||||
}
|
||||
|
||||
fn window_chrome_metrics(window_handle: &RawWindowHandle) -> Option<WindowChromeMetrics> {
|
||||
let RawWindowHandle::AppKit(appkit_handle) = window_handle else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let ns_view = ns_view_from_handle(appkit_handle)?;
|
||||
let ns_window = ns_view.window()?;
|
||||
|
||||
Some(WindowChromeMetrics {
|
||||
traffic_lights_size: traffic_lights_metrics(&ns_window)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn traffic_lights_metrics(ns_window: &NSWindow) -> Option<Vec2> {
|
||||
// Button order is CloseButton, MiniaturizeButton, ZoomButton:
|
||||
let close_button = ns_window
|
||||
.standardWindowButton(NSWindowButton::CloseButton)?
|
||||
.frame();
|
||||
let zoom_button = ns_window
|
||||
.standardWindowButton(NSWindowButton::ZoomButton)?
|
||||
.frame();
|
||||
|
||||
let left_margin = close_button.origin.x;
|
||||
let right_margin = left_margin; // for symmetry
|
||||
|
||||
let total_width = zoom_button.origin.x + zoom_button.size.width + right_margin;
|
||||
|
||||
let top_margin = close_button.origin.y;
|
||||
let bottom_margin = top_margin; // Usually symmetric
|
||||
let total_height = top_margin + close_button.size.height + bottom_margin;
|
||||
|
||||
Some(Vec2::new(total_width as f32, total_height as f32))
|
||||
}
|
||||
|
||||
fn ns_view_from_handle(handle: &AppKitWindowHandle) -> Option<&NSView> {
|
||||
let ns_view_ptr = handle.ns_view.as_ptr().cast::<NSView>();
|
||||
|
||||
// Validate the pointer is non-null
|
||||
if ns_view_ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
// SAFETY:
|
||||
// - We've verified the pointer is non-null
|
||||
// - The pointer comes from the windowing system, so it should be valid
|
||||
// - NSView pointers from AppKit are expected to remain valid for the window lifetime
|
||||
#[expect(unsafe_code)]
|
||||
unsafe {
|
||||
ns_view_ptr.as_ref()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ mod epi_integration;
|
||||
mod event_loop_context;
|
||||
pub mod run;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) mod macos;
|
||||
|
||||
/// File storage which can be used by native backends.
|
||||
#[cfg(feature = "persistence")]
|
||||
pub mod file_storage;
|
||||
|
||||
@@ -393,7 +393,7 @@ impl AppRunner {
|
||||
}
|
||||
}
|
||||
|
||||
super::set_cursor_icon(cursor_icon);
|
||||
super::set_cursor_icon(self.canvas(), cursor_icon);
|
||||
|
||||
if self.has_focus() {
|
||||
// The eframe app has focus.
|
||||
|
||||
@@ -178,10 +178,8 @@ fn canvas_size_in_points(canvas: &web_sys::HtmlCanvasElement, ctx: &egui::Contex
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Set the cursor icon.
|
||||
fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> {
|
||||
let document = web_sys::window()?.document()?;
|
||||
document
|
||||
.body()?
|
||||
fn set_cursor_icon(canvas: &web_sys::HtmlCanvasElement, cursor: egui::CursorIcon) -> Option<()> {
|
||||
canvas
|
||||
.style()
|
||||
.set_property("cursor", cursor_web_name(cursor))
|
||||
.ok()
|
||||
|
||||
@@ -6,6 +6,24 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
* `wgpu` backend: Enable WebGL fallback [#8038](https://github.com/emilk/egui/pull/8038) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
### ⭐ Added
|
||||
* Add error message when calling `.render()` without `.update_buffers()` [#8005](https://github.com/emilk/egui/pull/8005) by [@emilk](https://github.com/emilk)
|
||||
|
||||
### 🔧 Changed
|
||||
* Put the `capture` module behind a feature flag, make the `egui` dependency optional [#7698](https://github.com/emilk/egui/pull/7698) by [@StT191](https://github.com/StT191)
|
||||
* Attach stencil buffer [#7702](https://github.com/emilk/egui/pull/7702) by [@jgraef](https://github.com/jgraef)
|
||||
* Update wgpu to 28.0.0 [#7853](https://github.com/emilk/egui/pull/7853) by [@SuchAFuriousDeath](https://github.com/SuchAFuriousDeath)
|
||||
* Update to wgpu 29 [#7990](https://github.com/emilk/egui/pull/7990) by [@cwfitzgerald](https://github.com/cwfitzgerald)
|
||||
|
||||
### 🐛 Fixed
|
||||
* Fix wgpu memory leak leading to panic when window is minimized (#7434) [#7928](https://github.com/emilk/egui/pull/7928) by [@landaire](https://github.com/landaire)
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -25,7 +25,12 @@ all-features = true
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
|
||||
[features]
|
||||
default = ["fragile-send-sync-non-atomic-wasm", "macos-window-resize-jitter-fix", "wgpu/default"]
|
||||
default = [
|
||||
"fragile-send-sync-non-atomic-wasm",
|
||||
"macos-window-resize-jitter-fix",
|
||||
"wgpu/default",
|
||||
"wgpu/webgl", # A very important fallback for web support
|
||||
]
|
||||
|
||||
## Enables the `capture` module for capturing screenshots.
|
||||
capture = ["dep:egui"]
|
||||
|
||||
@@ -5,6 +5,17 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
* Add `is_scrolling`/`is_smooth_scrolling` util, checking for active scroll action [#7669](https://github.com/emilk/egui/pull/7669) by [@IsseW](https://github.com/IsseW)
|
||||
* Fix backspacing leaving last character in IME prediction not removed on macOS native and Safari [#7810](https://github.com/emilk/egui/pull/7810) by [@umajho](https://github.com/umajho)
|
||||
* Much improved IME [#7967](https://github.com/emilk/egui/pull/7967) by [@umajho](https://github.com/umajho)
|
||||
* Allow fallback from smithay to arboard when getting clipboard [#7976](https://github.com/emilk/egui/pull/7976) by [@wizzeh](https://github.com/wizzeh)
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
use std::ops::{Add, AddAssign, BitOr, BitOrAssign};
|
||||
|
||||
use emath::GuiRounding as _;
|
||||
use epaint::Margin;
|
||||
use epaint::{Color32, Direction, Margin, Shape};
|
||||
|
||||
use crate::{
|
||||
Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder,
|
||||
@@ -1019,13 +1019,17 @@ impl ScrollArea {
|
||||
.inner;
|
||||
|
||||
let (content_size, state) = prepared.end(ui);
|
||||
ScrollAreaOutput {
|
||||
let output = ScrollAreaOutput {
|
||||
inner,
|
||||
id,
|
||||
state,
|
||||
content_size,
|
||||
inner_rect,
|
||||
}
|
||||
};
|
||||
|
||||
paint_fade_areas(ui, &output);
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1504,3 +1508,88 @@ impl Prepared {
|
||||
(content_size, state)
|
||||
}
|
||||
}
|
||||
|
||||
/// Paint fade-out gradients at the top and/or bottom of a scroll area to
|
||||
/// indicate that more content is available beyond the visible region.
|
||||
fn paint_fade_areas<R>(ui: &Ui, scroll_output: &ScrollAreaOutput<R>) {
|
||||
let crate::style::ScrollFadeStyle {
|
||||
strength,
|
||||
size: fade_size,
|
||||
} = ui.spacing().scroll.fade;
|
||||
|
||||
if strength <= 0.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let bg = ui.stack().bg_color();
|
||||
|
||||
let offset = scroll_output.state.offset;
|
||||
let overflow = scroll_output.content_size - scroll_output.inner_rect.size();
|
||||
|
||||
let paint_rect = scroll_output
|
||||
.inner_rect
|
||||
.intersect(ui.min_rect())
|
||||
.expand(ui.visuals().clip_rect_margin);
|
||||
|
||||
// Top fade: animate opacity based on how far we've scrolled down.
|
||||
if 0.0 < offset.y {
|
||||
let t = (offset.y / fade_size).clamp(0.0, 1.0) * strength;
|
||||
let bg_faded = bg.gamma_multiply(t);
|
||||
let rect = Rect::from_min_max(
|
||||
paint_rect.left_top(),
|
||||
pos2(paint_rect.right(), paint_rect.top() + fade_size),
|
||||
);
|
||||
ui.painter().add(Shape::gradient_rect(
|
||||
rect,
|
||||
Direction::TopDown,
|
||||
[bg_faded, Color32::TRANSPARENT],
|
||||
));
|
||||
}
|
||||
|
||||
// Bottom fade: animate opacity based on distance from the bottom.
|
||||
let distance_from_bottom = overflow.y - offset.y;
|
||||
if 0.0 < distance_from_bottom {
|
||||
let t = (distance_from_bottom / fade_size).clamp(0.0, 1.0) * strength;
|
||||
let bg_faded = bg.gamma_multiply(t);
|
||||
let rect = Rect::from_min_max(
|
||||
pos2(paint_rect.left(), paint_rect.bottom() - fade_size),
|
||||
paint_rect.right_bottom(),
|
||||
);
|
||||
ui.painter().add(Shape::gradient_rect(
|
||||
rect,
|
||||
Direction::BottomUp,
|
||||
[bg_faded, Color32::TRANSPARENT],
|
||||
));
|
||||
}
|
||||
|
||||
// Left fade: animate opacity based on how far we've scrolled right.
|
||||
if 0.0 < offset.x {
|
||||
let t = (offset.x / fade_size).clamp(0.0, 1.0) * strength;
|
||||
let bg_faded = bg.gamma_multiply(t);
|
||||
let rect = Rect::from_min_max(
|
||||
paint_rect.left_top(),
|
||||
pos2(paint_rect.left() + fade_size, paint_rect.bottom()),
|
||||
);
|
||||
ui.painter().add(Shape::gradient_rect(
|
||||
rect,
|
||||
Direction::LeftToRight,
|
||||
[bg_faded, Color32::TRANSPARENT],
|
||||
));
|
||||
}
|
||||
|
||||
// Right fade: animate opacity based on distance from the right edge.
|
||||
let distance_from_right = overflow.x - offset.x;
|
||||
if 0.0 < distance_from_right {
|
||||
let t = (distance_from_right / fade_size).clamp(0.0, 1.0) * strength;
|
||||
let bg_faded = bg.gamma_multiply(t);
|
||||
let rect = Rect::from_min_max(
|
||||
pos2(paint_rect.right() - fade_size, paint_rect.top()),
|
||||
paint_rect.right_bottom(),
|
||||
);
|
||||
ui.painter().add(Shape::gradient_rect(
|
||||
rect,
|
||||
Direction::RightToLeft,
|
||||
[bg_faded, Color32::TRANSPARENT],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1545,6 +1545,11 @@ impl Context {
|
||||
crate::debug_text::print(self, text);
|
||||
}
|
||||
|
||||
/// Current time in seconds, relative to some unknown epoch.
|
||||
pub fn time(&self) -> f64 {
|
||||
self.input(|i| i.time)
|
||||
}
|
||||
|
||||
/// What operating system are we running on?
|
||||
///
|
||||
/// When compiling natively, this is
|
||||
@@ -1933,7 +1938,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Callbacks
|
||||
/// Plugins
|
||||
impl Context {
|
||||
/// Call the given callback at the start of each pass of each viewport.
|
||||
///
|
||||
@@ -2945,6 +2950,15 @@ impl Context {
|
||||
self.egui_wants_keyboard_input()
|
||||
}
|
||||
|
||||
/// Is the currently focused widget a text edit?
|
||||
pub fn text_edit_focused(&self) -> bool {
|
||||
if let Some(id) = self.memory(|mem| mem.focused()) {
|
||||
crate::text_edit::TextEditState::load(self, id).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Highlight this widget, to make it look like it is hovered, even if it isn't.
|
||||
///
|
||||
/// If you call this after the widget has been fully rendered,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use emath::GuiRounding as _;
|
||||
|
||||
use crate::{
|
||||
Align,
|
||||
Align, Direction,
|
||||
emath::{Align2, NumExt as _, Pos2, Rect, Vec2, pos2, vec2},
|
||||
};
|
||||
const INFINITY: f32 = f32::INFINITY;
|
||||
@@ -87,36 +87,6 @@ impl Region {
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Layout direction, one of [`LeftToRight`](Direction::LeftToRight), [`RightToLeft`](Direction::RightToLeft), [`TopDown`](Direction::TopDown), [`BottomUp`](Direction::BottomUp).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum Direction {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
TopDown,
|
||||
BottomUp,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
#[inline(always)]
|
||||
pub fn is_horizontal(self) -> bool {
|
||||
match self {
|
||||
Self::LeftToRight | Self::RightToLeft => true,
|
||||
Self::TopDown | Self::BottomUp => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_vertical(self) -> bool {
|
||||
match self {
|
||||
Self::LeftToRight | Self::RightToLeft => false,
|
||||
Self::TopDown | Self::BottomUp => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The layout of a [`Ui`][`crate::Ui`], e.g. "vertical & centered".
|
||||
///
|
||||
/// ```
|
||||
|
||||
@@ -449,7 +449,7 @@ pub use emath::{
|
||||
remap_clamp, vec2,
|
||||
};
|
||||
pub use epaint::{
|
||||
ClippedPrimitive, ColorImage, CornerRadius, ImageData, Margin, Mesh, PaintCallback,
|
||||
ClippedPrimitive, ColorImage, CornerRadius, Direction, ImageData, Margin, Mesh, PaintCallback,
|
||||
PaintCallbackInfo, Shadow, Shape, Stroke, StrokeKind, TextureHandle, TextureId, mutex,
|
||||
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
|
||||
textures::{TextureFilter, TextureOptions, TextureWrapMode, TexturesDelta},
|
||||
@@ -478,7 +478,7 @@ pub use self::{
|
||||
drag_and_drop::DragAndDrop,
|
||||
epaint::text::TextWrapMode,
|
||||
grid::Grid,
|
||||
id::{Id, IdMap},
|
||||
id::{Id, IdMap, IdSet},
|
||||
input_state::{InputOptions, InputState, MultiTouchInfo, PointerState, SurrenderFocusOn},
|
||||
layers::{LayerId, Order},
|
||||
layout::*,
|
||||
|
||||
@@ -586,6 +586,8 @@ pub struct ScrollStyle {
|
||||
/// This is only for floating scroll bars.
|
||||
/// Solid scroll bars are always opaque.
|
||||
pub interact_handle_opacity: f32,
|
||||
|
||||
pub fade: ScrollFadeStyle,
|
||||
}
|
||||
|
||||
impl Default for ScrollStyle {
|
||||
@@ -616,6 +618,8 @@ impl ScrollStyle {
|
||||
dormant_handle_opacity: 0.0,
|
||||
active_handle_opacity: 0.6,
|
||||
interact_handle_opacity: 1.0,
|
||||
|
||||
fade: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,6 +703,8 @@ impl ScrollStyle {
|
||||
dormant_handle_opacity,
|
||||
active_handle_opacity,
|
||||
interact_handle_opacity,
|
||||
|
||||
fade,
|
||||
} = self;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
@@ -772,6 +778,52 @@ impl ScrollStyle {
|
||||
ui.label("Inner margin");
|
||||
});
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
fade.ui(ui);
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls if and how to fade out the sides of a [`crate::ScrollArea`]
|
||||
/// to indicate there is more there if you scroll.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct ScrollFadeStyle {
|
||||
/// Opacity of the fade effect at the outer edge, in 0.0-1.0.
|
||||
///
|
||||
/// Set to 0.0 to disable the fade effect.
|
||||
pub strength: f32,
|
||||
|
||||
/// Size of the fade-area (height for vertical scrolling,
|
||||
/// width for horizontal scrolling).
|
||||
pub size: f32,
|
||||
}
|
||||
|
||||
impl Default for ScrollFadeStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
strength: 0.5,
|
||||
size: 20.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollFadeStyle {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
let Self { strength, size } = self;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(strength).speed(0.01).range(0.0..=1.0));
|
||||
ui.label("Fade strength");
|
||||
});
|
||||
|
||||
if 0.0 < *strength {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(DragValue::new(size).range(0.0..=64.0));
|
||||
ui.label("Fade size");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -487,6 +487,12 @@ impl Ui {
|
||||
&mut self.style_mut().visuals
|
||||
}
|
||||
|
||||
/// Is this [`Ui`] in a tooltip?
|
||||
#[inline]
|
||||
pub fn is_tooltip(&self) -> bool {
|
||||
self.layer_id().order == Order::Tooltip
|
||||
}
|
||||
|
||||
/// Get a reference to this [`Ui`]'s [`UiStack`].
|
||||
#[inline]
|
||||
pub fn stack(&self) -> &Arc<UiStack> {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, iter::FusedIterator};
|
||||
|
||||
use epaint::Color32;
|
||||
|
||||
use crate::{Direction, Frame, Id, Rect};
|
||||
|
||||
/// What kind is this [`crate::Ui`]?
|
||||
@@ -253,6 +255,25 @@ impl UiStack {
|
||||
pub fn has_visible_frame(&self) -> bool {
|
||||
!self.info.frame.stroke.is_empty()
|
||||
}
|
||||
|
||||
/// The background color of this [`crate::Ui`].
|
||||
///
|
||||
/// This blend together all [`Frame::fill`] colors
|
||||
/// up to the root.
|
||||
#[inline]
|
||||
pub fn bg_color(&self) -> Color32 {
|
||||
let mut total = Color32::TRANSPARENT;
|
||||
for node in self.iter() {
|
||||
let fill = node.frame().fill;
|
||||
if fill != Color32::TRANSPARENT {
|
||||
total = fill.blend(total);
|
||||
if total.is_opaque() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
|
||||
// these methods act on the entire stack
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// This will also allow users to pick their own serialization format per type.
|
||||
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
/// Like [`std::any::TypeId`], but can be serialized and deserialized.
|
||||
@@ -182,6 +181,18 @@ impl Element {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_temp(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(feature = "persistence")]
|
||||
Self::Value { serialize_fn, .. } => serialize_fn.is_none(),
|
||||
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
Self::Value { .. } => true,
|
||||
|
||||
Self::Serialized(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_temp<T: 'static>(&self) -> Option<&T> {
|
||||
match self {
|
||||
@@ -316,6 +327,41 @@ fn from_ron_str<T: serde::de::DeserializeOwned>(ron: &str) -> Option<T> {
|
||||
|
||||
use crate::Id;
|
||||
|
||||
/// The key used in [`IdTypeMap`], which is a combination of an [`Id`] and a [`TypeId`].
|
||||
///
|
||||
/// This key can be used to remove or access values in the [`IdTypeMap`] without
|
||||
/// knowledge of the `TypeId` `T` that is required for other accessors.
|
||||
///
|
||||
/// [`RawKey`]s make no guarantees about layout or their ability to be persisted.
|
||||
/// They only produce deterministic results if they are used with the map
|
||||
/// they were initially obtained from. Using them on other instances of [`IdTypeMap`]
|
||||
/// may produce unexpected behavior.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
#[repr(transparent)]
|
||||
pub struct RawKey(u64);
|
||||
|
||||
impl nohash_hasher::IsEnabled for RawKey {}
|
||||
|
||||
impl RawKey {
|
||||
/// Create a new key for the given type.
|
||||
///
|
||||
/// Note that two keys with the same id but different types
|
||||
/// will be different keys.
|
||||
///
|
||||
/// ```
|
||||
/// use egui::{Id, util::id_type_map::RawKey};
|
||||
/// assert_ne!(
|
||||
/// RawKey::new::<i32>(Id::NULL),
|
||||
/// RawKey::new::<String>(Id::NULL),
|
||||
/// );
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn new<T: 'static>(id: Id) -> Self {
|
||||
let type_id = TypeId::of::<T>();
|
||||
Self(type_id.value() ^ id.value())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(emilk): make IdTypeMap generic over the key (`Id`), and make a library of IdTypeMap.
|
||||
/// Stores values identified by an [`Id`] AND the [`std::any::TypeId`] of the value.
|
||||
///
|
||||
@@ -358,7 +404,7 @@ use crate::Id;
|
||||
#[derive(Clone, Debug)]
|
||||
// We use `id XOR typeid` as a key, so we don't need to hash again!
|
||||
pub struct IdTypeMap {
|
||||
map: nohash_hasher::IntMap<u64, Element>,
|
||||
map: nohash_hasher::IntMap<RawKey, Element>,
|
||||
|
||||
max_bytes_per_type: usize,
|
||||
}
|
||||
@@ -375,16 +421,23 @@ impl Default for IdTypeMap {
|
||||
impl IdTypeMap {
|
||||
/// Insert a value that will not be persisted.
|
||||
#[inline]
|
||||
pub fn insert_temp<T: 'static + Any + Clone + Send + Sync>(&mut self, id: Id, value: T) {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
self.map.insert(hash, Element::new_temp(value));
|
||||
pub fn insert_temp<T: 'static + Any + Clone + Send + Sync>(
|
||||
&mut self,
|
||||
id: Id,
|
||||
value: T,
|
||||
) -> RawKey {
|
||||
let key = RawKey::new::<T>(id);
|
||||
self.map.insert(key, Element::new_temp(value));
|
||||
key
|
||||
}
|
||||
|
||||
/// Insert a value that will be persisted next time you start the app.
|
||||
#[inline]
|
||||
pub fn insert_persisted<T: SerializableAny>(&mut self, id: Id, value: T) {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
self.map.insert(hash, Element::new_persisted(value));
|
||||
let key = RawKey::new::<T>(id);
|
||||
self.map.insert(key, Element::new_persisted(value));
|
||||
// We don't yet return the key here, because currently all our `raw`
|
||||
// methods are only for temporary values.
|
||||
}
|
||||
|
||||
/// Read a value without trying to deserialize a persisted value.
|
||||
@@ -392,8 +445,28 @@ impl IdTypeMap {
|
||||
/// The call clones the value (if found), so make sure it is cheap to clone!
|
||||
#[inline]
|
||||
pub fn get_temp<T: 'static + Clone>(&self, id: Id) -> Option<T> {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
self.map.get(&hash).and_then(|x| x.get_temp()).cloned()
|
||||
let key = RawKey::new::<T>(id);
|
||||
self.map.get(&key).and_then(|x| x.get_temp()).cloned()
|
||||
}
|
||||
|
||||
/// Gets a reference to a value for a given raw key.
|
||||
///
|
||||
/// Serialized values are ignored.
|
||||
pub fn get_temp_raw(&self, raw: RawKey) -> Option<&(dyn Any + Send + Sync)> {
|
||||
match self.map.get(&raw)? {
|
||||
Element::Value { value, .. } => Some(value.as_ref()),
|
||||
Element::Serialized(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a mutable reference to a value for a given raw key.
|
||||
///
|
||||
/// Serialized values are ignored.
|
||||
pub fn get_temp_raw_mut(&mut self, raw: RawKey) -> Option<&mut (dyn Any + Send + Sync)> {
|
||||
match self.map.get_mut(&raw)? {
|
||||
Element::Value { value, .. } => Some(value.as_mut()),
|
||||
Element::Serialized(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a value, optionally deserializing it if available.
|
||||
@@ -404,9 +477,9 @@ impl IdTypeMap {
|
||||
/// The call clones the value (if found), so make sure it is cheap to clone!
|
||||
#[inline]
|
||||
pub fn get_persisted<T: SerializableAny>(&mut self, id: Id) -> Option<T> {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
let key = RawKey::new::<T>(id);
|
||||
self.map
|
||||
.get_mut(&hash)
|
||||
.get_mut(&key)
|
||||
.and_then(|x| x.get_mut_persisted())
|
||||
.cloned()
|
||||
}
|
||||
@@ -443,9 +516,9 @@ impl IdTypeMap {
|
||||
id: Id,
|
||||
insert_with: impl FnOnce() -> T,
|
||||
) -> &mut T {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
let key = RawKey::new::<T>(id);
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.map.entry(hash) {
|
||||
match self.map.entry(key) {
|
||||
Entry::Vacant(vacant) => {
|
||||
// this unwrap will never panic, because we insert correct type right now
|
||||
#[expect(clippy::unwrap_used)]
|
||||
@@ -465,9 +538,9 @@ impl IdTypeMap {
|
||||
id: Id,
|
||||
insert_with: impl FnOnce() -> T,
|
||||
) -> &mut T {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
let key = RawKey::new::<T>(id);
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.map.entry(hash) {
|
||||
match self.map.entry(key) {
|
||||
Entry::Vacant(vacant) => {
|
||||
// this unwrap will never panic, because we insert correct type right now
|
||||
#[expect(clippy::unwrap_used)]
|
||||
@@ -486,7 +559,7 @@ impl IdTypeMap {
|
||||
#[cfg(feature = "persistence")]
|
||||
#[allow(clippy::allow_attributes, unused)]
|
||||
fn get_generation<T: SerializableAny>(&self, id: Id) -> Option<usize> {
|
||||
let element = self.map.get(&hash(TypeId::of::<T>(), id))?;
|
||||
let element = self.map.get(&RawKey::new::<T>(id))?;
|
||||
match element {
|
||||
Element::Value { .. } => Some(0),
|
||||
Element::Serialized(SerializedElement { generation, .. }) => Some(*generation),
|
||||
@@ -496,18 +569,33 @@ impl IdTypeMap {
|
||||
/// Remove the state of this type and id.
|
||||
#[inline]
|
||||
pub fn remove<T: 'static>(&mut self, id: Id) {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
self.map.remove(&hash);
|
||||
let key = RawKey::new::<T>(id);
|
||||
self.map.remove(&key);
|
||||
}
|
||||
|
||||
/// Remove and fetch the state of this type and id.
|
||||
#[inline]
|
||||
pub fn remove_temp<T: 'static + Default>(&mut self, id: Id) -> Option<T> {
|
||||
let hash = hash(TypeId::of::<T>(), id);
|
||||
let mut element = self.map.remove(&hash)?;
|
||||
let key = RawKey::new::<T>(id);
|
||||
let mut element = self.map.remove(&key)?;
|
||||
Some(std::mem::take(element.get_mut_temp()?))
|
||||
}
|
||||
|
||||
/// Remove a temporary value given a raw key.
|
||||
///
|
||||
/// Serialized values are ignored.
|
||||
pub fn remove_temp_raw(&mut self, raw: RawKey) -> Option<Box<dyn Any + Send + Sync>> {
|
||||
use std::collections::hash_map::Entry;
|
||||
if let Entry::Occupied(e) = self.map.entry(raw)
|
||||
&& e.get().is_temp()
|
||||
&& let Element::Value { value, .. } = e.remove()
|
||||
{
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Note all state of the given type.
|
||||
pub fn remove_by_type<T: 'static>(&mut self) {
|
||||
let key = TypeId::of::<T>();
|
||||
@@ -532,6 +620,18 @@ impl IdTypeMap {
|
||||
self.map.len()
|
||||
}
|
||||
|
||||
/// Returns all [`RawKey`]s to values in this map.
|
||||
///
|
||||
/// The returned keys can only be used with this map.
|
||||
///
|
||||
/// Serializable values will be ignored.
|
||||
pub fn temp_keys(&self) -> impl Iterator<Item = RawKey> {
|
||||
self.map
|
||||
.iter()
|
||||
.filter(|(_, v)| v.is_temp())
|
||||
.map(|(k, _)| *k)
|
||||
}
|
||||
|
||||
/// Count how many values are stored but not yet deserialized.
|
||||
#[inline]
|
||||
pub fn count_serialized(&self) -> usize {
|
||||
@@ -576,11 +676,6 @@ impl IdTypeMap {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn hash(type_id: TypeId, id: Id) -> u64 {
|
||||
type_id.value() ^ id.value()
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// How [`IdTypeMap`] is persisted.
|
||||
@@ -613,13 +708,13 @@ impl PersistedMap {
|
||||
|
||||
{
|
||||
profiling::scope!("gather");
|
||||
for (hash, element) in &map.map {
|
||||
for (key, element) in &map.map {
|
||||
if let Some(element) = element.to_serialize() {
|
||||
let stats = types_map.entry(element.type_id).or_default();
|
||||
stats.num_bytes += element.ron.len();
|
||||
let generation_stats = stats.generations.entry(element.generation).or_default();
|
||||
generation_stats.num_bytes += element.ron.len();
|
||||
generation_stats.elements.push((*hash, element));
|
||||
generation_stats.elements.push((key.0, element));
|
||||
} else {
|
||||
// temporary value that shouldn't be serialized
|
||||
}
|
||||
@@ -659,7 +754,7 @@ impl PersistedMap {
|
||||
.into_iter()
|
||||
.map(
|
||||
|(
|
||||
hash,
|
||||
raw,
|
||||
SerializedElement {
|
||||
type_id,
|
||||
ron,
|
||||
@@ -667,7 +762,7 @@ impl PersistedMap {
|
||||
},
|
||||
)| {
|
||||
(
|
||||
hash,
|
||||
RawKey(raw),
|
||||
Element::Serialized(SerializedElement {
|
||||
type_id,
|
||||
ron,
|
||||
|
||||
@@ -371,6 +371,12 @@ impl<'a> Button<'a> {
|
||||
AtomLayoutResponse::empty(prepared.response)
|
||||
};
|
||||
|
||||
if let Some(cursor) = ui.visuals().interact_cursor
|
||||
&& response.response.hovered()
|
||||
{
|
||||
ui.ctx().set_cursor_icon(cursor);
|
||||
}
|
||||
|
||||
response.response.widget_info(|| {
|
||||
if let Some(text) = &text {
|
||||
WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), text)
|
||||
|
||||
@@ -37,7 +37,7 @@ serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
|
||||
syntect = ["egui_demo_lib/syntect"]
|
||||
|
||||
glow = ["eframe/glow"]
|
||||
wgpu = ["eframe/wgpu", "bytemuck", "dep:wgpu"]
|
||||
wgpu = ["eframe/wgpu", "bytemuck"]
|
||||
wayland = ["eframe/wayland"]
|
||||
x11 = ["eframe/x11"]
|
||||
|
||||
@@ -60,9 +60,6 @@ accesskit_consumer = { workspace = true, optional = true }
|
||||
bytemuck = { workspace = true, optional = true }
|
||||
puffin = { workspace = true, optional = true }
|
||||
puffin_http = { workspace = true, optional = true }
|
||||
# Enable both WebGL & WebGPU when targeting the web (these features have no effect when not targeting wasm32)
|
||||
# Also enable the default features so we have a supported backend for every platform.
|
||||
wgpu = { workspace = true, features = ["default", "webgpu", "webgl"], optional = true }
|
||||
|
||||
|
||||
# feature "http":
|
||||
|
||||
@@ -211,7 +211,7 @@ fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
let wgpu_adapter_details_ui = |ui: &mut egui::Ui, adapter: &eframe::wgpu::Adapter| {
|
||||
let info = &adapter.get_info();
|
||||
|
||||
let wgpu::AdapterInfo {
|
||||
let eframe::wgpu::AdapterInfo {
|
||||
name,
|
||||
vendor,
|
||||
device,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b9ad01a55950f96a3ae9e48a2c026143d11ffee62bff4f83b4529cd884ce11f0
|
||||
size 169683
|
||||
oid sha256:84f0e72ce337d56f3767ebed1ab6a47f3d27c9fbcce4d8a19aeab358e12920f5
|
||||
size 169664
|
||||
|
||||
@@ -27,7 +27,7 @@ impl crate::Demo for StripDemo {
|
||||
impl crate::View for StripDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let dark_mode = ui.visuals().dark_mode;
|
||||
let faded_color = ui.visuals().window_fill();
|
||||
let faded_color = ui.stack().bg_color();
|
||||
let faded_color = |color: Color32| -> Color32 {
|
||||
use egui::Rgba;
|
||||
let t = if dark_mode { 0.95 } else { 0.8 };
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8c630df841e98043132b920338793745549116890c1bc52d8d10b0def896a1e6
|
||||
size 114409
|
||||
oid sha256:deff441cd1d9142352f8759dff4b759f4572f0ddf93752349314da77abe4b254
|
||||
size 115028
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:627114dcbda4f3d2255d34926ed0b77c679d248ed1d822d723479b1e6652c67a
|
||||
size 249258
|
||||
oid sha256:a46457b23b7b32694564d03b42bccac2f017a756225bc54b508bb6fe2ad8ee7b
|
||||
size 249548
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b5b965a7c690fd8e8646812513e2417170b687fd37e29d220c29127ba0cc200c
|
||||
size 172609
|
||||
oid sha256:927a497e8b6f9ce3b71dcb67086f477e19d327c163b2b8ad868af10009c2faf2
|
||||
size 172981
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:931f38ade8373ff79801c05c5d4397f2c5fcfa27022f2e1abe9eb29d561a3aef
|
||||
size 76022
|
||||
oid sha256:db4c0cf1c4cdae3d416afce5c58efd1cc382be86431e547fa66bcc95a0a17ddb
|
||||
size 76364
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:89986132c5d2a3ccccf0a16cb2b0be07b7c5512838cc10ec9067022e7a238515
|
||||
size 63918
|
||||
oid sha256:57018beba5e4fb4f1e6de9c58bf898560b3a7669159d5bad91a4e2382ef57ce0
|
||||
size 64004
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9c81d5a5915dac60297293b90cd3fc0d188a9335e99b318996e3e2934de7bee1
|
||||
size 483497
|
||||
oid sha256:999f9cc006302b8951d97b510a02f1209969c376ecc7909ed5d7b46da27c0637
|
||||
size 483753
|
||||
|
||||
@@ -5,6 +5,18 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
* Fix media type with optional parameters [#7739](https://github.com/emilk/egui/pull/7739) by [@leungkkf](https://github.com/leungkkf)
|
||||
* Fix: CodeTheme functions - ui and add builder [#7684](https://github.com/emilk/egui/pull/7684) by [@Its-Just-Nans](https://github.com/Its-Just-Nans)
|
||||
* Update selected dependencies [#7920](https://github.com/emilk/egui/pull/7920) by [@oscargus](https://github.com/oscargus)
|
||||
* Add `DatePickerButton::reverse_years/year_scroll_to` [#7978](https://github.com/emilk/egui/pull/7978) by [@Deuracell](https://github.com/Deuracell)
|
||||
* Replace `chrono` with `jiff` [#8008](https://github.com/emilk/egui/pull/8008) by [@emilk](https://github.com/emilk)
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
* Bump `ehttp` to 0.6.0 [#7757](https://github.com/emilk/egui/pull/7757) by [@jprochazk](https://github.com/jprochazk)
|
||||
|
||||
|
||||
@@ -656,14 +656,13 @@ impl TableState {
|
||||
}
|
||||
|
||||
fn store(self, ui: &egui::Ui, state_id: egui::Id) {
|
||||
#![expect(clippy::needless_return)]
|
||||
#[cfg(feature = "serde")]
|
||||
{
|
||||
return ui.data_mut(|d| d.insert_persisted(state_id, self));
|
||||
ui.data_mut(|d| d.insert_persisted(state_id, self));
|
||||
}
|
||||
#[cfg(not(feature = "serde"))]
|
||||
{
|
||||
return ui.data_mut(|d| d.insert_temp(state_id, self));
|
||||
ui.data_mut(|d| d.insert_temp(state_id, self));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,14 @@ Changes since the last release can be found at <https://github.com/emilk/egui/co
|
||||
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -6,6 +6,17 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
* Turn `HarnessBuilder::with_options` into a proper builder method [#7697](https://github.com/emilk/egui/pull/7697) by [@emilk](https://github.com/emilk)
|
||||
* Paint mouse cursor in kittest snapshot images [#7721](https://github.com/emilk/egui/pull/7721) by [@emilk](https://github.com/emilk)
|
||||
* Add `kittest.toml` config file [#7643](https://github.com/emilk/egui/pull/7643) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* Close debug_open_snapshot temp file before viewing it [#7841](https://github.com/emilk/egui/pull/7841) by [@yuriks](https://github.com/yuriks)
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
* Enforce consistent snapshot updates [#7744](https://github.com/emilk/egui/pull/7744) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
* `kittest`: add drag-and-drop helpers [#7690](https://github.com/emilk/egui/pull/7690) by [@emilk](https://github.com/emilk)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:eb8737af84c3d3b0c054b7e2a8bcb04685243d84cb13b72a1372dc40dbfd14fb
|
||||
size 7267
|
||||
oid sha256:6154c8bb550575bcb9fa0bba06da4d47079a00dffc5754b62ef2a6e7529e2090
|
||||
size 7489
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f1651bb1b9bbaa3c65ecd07c39c57527f4beb4c607581a5b2596a49dcf4c5db3
|
||||
size 7996
|
||||
oid sha256:df2578c198b29950254ec62c6cc615a4b1c003e7ae3ea027da22fc868b392c74
|
||||
size 8342
|
||||
|
||||
@@ -6,6 +6,14 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -5,6 +5,29 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
### ⭐ Added
|
||||
* Replace ab_glyph with Skrifa + vello_cpu; enable font hinting [#7694](https://github.com/emilk/egui/pull/7694) by [@valadaptive](https://github.com/valadaptive)
|
||||
* Add font variations API [#7859](https://github.com/emilk/egui/pull/7859) by [@valadaptive](https://github.com/valadaptive)
|
||||
* Allow rotation of rectangles and ellipses [#7682](https://github.com/emilk/egui/pull/7682) by [@RyanBluth](https://github.com/RyanBluth)
|
||||
|
||||
### 🔧 Changed
|
||||
* Apply preferred font weight when loading variable fonts [#7790](https://github.com/emilk/egui/pull/7790) by [@pmnxis](https://github.com/pmnxis)
|
||||
* Make `Galley::pos_from_layout_cursor` `pub` [#7864](https://github.com/emilk/egui/pull/7864) by [@dionb](https://github.com/dionb)
|
||||
|
||||
### 🐛 Fixed
|
||||
* Fixes the overly aggressive overflow elision in `truncate()` and similar for os scaling other than 100% [#7867](https://github.com/emilk/egui/pull/7867) by [@RndUsr123](https://github.com/RndUsr123)
|
||||
* Fix text color when selecting newline character [#7951](https://github.com/emilk/egui/pull/7951) by [@emilk](https://github.com/emilk)
|
||||
* Fix galley width calculation being off due to subpixel binning [#7972](https://github.com/emilk/egui/pull/7972) by [@lucasmerlin](https://github.com/lucasmerlin)
|
||||
|
||||
### 🚀 Performance
|
||||
* Avoid cloning `Row`s during `Galley::concat` [#7649](https://github.com/emilk/egui/pull/7649) by [@afishhh](https://github.com/afishhh)
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
27
crates/epaint/src/direction.rs
Normal file
27
crates/epaint/src/direction.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
/// A cardinal direction, one of [`LeftToRight`](Direction::LeftToRight), [`RightToLeft`](Direction::RightToLeft), [`TopDown`](Direction::TopDown), [`BottomUp`](Direction::BottomUp).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum Direction {
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
TopDown,
|
||||
BottomUp,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
#[inline(always)]
|
||||
pub fn is_horizontal(self) -> bool {
|
||||
match self {
|
||||
Self::LeftToRight | Self::RightToLeft => true,
|
||||
Self::TopDown | Self::BottomUp => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_vertical(self) -> bool {
|
||||
match self {
|
||||
Self::LeftToRight | Self::RightToLeft => false,
|
||||
Self::TopDown | Self::BottomUp => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ mod brush;
|
||||
pub mod color;
|
||||
mod corner_radius;
|
||||
mod corner_radius_f32;
|
||||
mod direction;
|
||||
pub mod image;
|
||||
mod margin;
|
||||
mod margin_f32;
|
||||
@@ -50,6 +51,7 @@ pub use self::{
|
||||
color::ColorMode,
|
||||
corner_radius::CornerRadius,
|
||||
corner_radius_f32::CornerRadiusF32,
|
||||
direction::Direction,
|
||||
image::{AlphaFromCoverage, ColorImage, ImageData, ImageDelta},
|
||||
margin::Margin,
|
||||
margin_f32::*,
|
||||
|
||||
@@ -23,6 +23,18 @@ pub struct Vertex {
|
||||
pub color: Color32, // 32 bit
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
/// An untextured vertex
|
||||
#[inline]
|
||||
pub fn untextured(pos: Pos2, color: Color32) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
uv: WHITE_UV,
|
||||
color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
#[cfg(all(feature = "unity", not(feature = "_override_unity")))]
|
||||
@@ -159,11 +171,7 @@ impl Mesh {
|
||||
self.texture_id == TextureId::default(),
|
||||
"Mesh has an assigned texture"
|
||||
);
|
||||
self.vertices.push(Vertex {
|
||||
pos,
|
||||
uv: WHITE_UV,
|
||||
color,
|
||||
});
|
||||
self.vertices.push(Vertex::untextured(pos, color));
|
||||
}
|
||||
|
||||
/// Add a triangle.
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
use emath::{Align2, Pos2, Rangef, Rect, TSTransform, Vec2, pos2};
|
||||
|
||||
use crate::{
|
||||
Color32, CornerRadius, Mesh, Stroke, StrokeKind, TextureId,
|
||||
Color32, CornerRadius, Direction, Mesh, Stroke, StrokeKind, TextureId, Vertex,
|
||||
stroke::PathStroke,
|
||||
text::{FontId, FontsView, Galley},
|
||||
};
|
||||
@@ -297,6 +297,32 @@ impl Shape {
|
||||
Self::Rect(RectShape::stroke(rect, corner_radius, stroke, stroke_kind))
|
||||
}
|
||||
|
||||
/// Paints a gradient rectangle that transitions from `color_from` to `color_to`
|
||||
/// along the given `direction`.
|
||||
///
|
||||
/// For example, [`Direction::TopDown`] paints `color_from` at the top edge fading
|
||||
/// to `color_to` at the bottom edge.
|
||||
#[inline]
|
||||
pub fn gradient_rect(rect: Rect, direction: Direction, [from, to]: [Color32; 2]) -> Self {
|
||||
let (left_top, right_top, left_bottom, right_bottom) = match direction {
|
||||
Direction::TopDown => (from, from, to, to),
|
||||
Direction::BottomUp => (to, to, from, from),
|
||||
Direction::LeftToRight => (from, to, from, to),
|
||||
Direction::RightToLeft => (to, from, to, from),
|
||||
};
|
||||
|
||||
Self::from(Mesh {
|
||||
indices: vec![0, 1, 2, 2, 1, 3],
|
||||
vertices: vec![
|
||||
Vertex::untextured(rect.left_top(), left_top),
|
||||
Vertex::untextured(rect.right_top(), right_top),
|
||||
Vertex::untextured(rect.left_bottom(), left_bottom),
|
||||
Vertex::untextured(rect.right_bottom(), right_bottom),
|
||||
],
|
||||
texture_id: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
pub fn text(
|
||||
fonts: &mut FontsView<'_>,
|
||||
|
||||
@@ -10,8 +10,8 @@ use emath::{GuiRounding as _, NumExt as _, Pos2, Rect, Rot2, Vec2, pos2, remap,
|
||||
use crate::{
|
||||
CircleShape, ClippedPrimitive, ClippedShape, Color32, CornerRadiusF32, CubicBezierShape,
|
||||
EllipseShape, Mesh, PathShape, Primitive, QuadraticBezierShape, RectShape, Shape, Stroke,
|
||||
StrokeKind, TextShape, TextureId, Vertex, WHITE_UV, color::ColorMode, emath,
|
||||
stroke::PathStroke, texture_atlas::PreparedDisc,
|
||||
StrokeKind, TextShape, TextureId, Vertex, color::ColorMode, emath, stroke::PathStroke,
|
||||
texture_atlas::PreparedDisc,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -809,11 +809,8 @@ fn fill_closed_path(feathering: f32, path: &mut [PathPoint], fill_color: Color32
|
||||
} else {
|
||||
out.reserve_triangles(n as usize);
|
||||
let idx = out.vertices.len() as u32;
|
||||
out.vertices.extend(path.iter().map(|p| Vertex {
|
||||
pos: p.pos,
|
||||
uv: WHITE_UV,
|
||||
color: fill_color,
|
||||
}));
|
||||
out.vertices
|
||||
.extend(path.iter().map(|p| Vertex::untextured(p.pos, fill_color)));
|
||||
for i in 2..n {
|
||||
out.add_triangle(idx, idx + i - 1, idx + i);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,14 @@ This file is updated upon each release.
|
||||
Changes since the last release can be found at <https://github.com/emilk/egui/compare/latest...HEAD> or by running the `scripts/generate_changelog.py` script.
|
||||
|
||||
|
||||
## 0.34.1 - 2026-03-27
|
||||
Nothing new
|
||||
|
||||
|
||||
## 0.34.0 - 2026-03-26
|
||||
* Fix emoji icon font [#7940](https://github.com/emilk/egui/pull/7940) by [@Jhynjhiruu](https://github.com/Jhynjhiruu)
|
||||
|
||||
|
||||
## 0.33.3 - 2025-12-11
|
||||
Nothing new
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:81620caad6d420f3bd0f224e5b07a02960a42436208a98d3aa012e5db61a743a
|
||||
size 1510
|
||||
oid sha256:6f2a57ad8dbdd121cb181e74d76db68d800aba8fdc980d8de4e962e1e85fe8f6
|
||||
size 1803
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f915eafb6490ff456c5b0a7c74c38ef143262bdf74a0c6561b9cf6ee66a679ea
|
||||
size 1501
|
||||
oid sha256:43dc457cac18107772dbb7e5773961cf502dd685ce1cb4e94338e6b7daedaa77
|
||||
size 1861
|
||||
|
||||
Reference in New Issue
Block a user