1
0
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:
lucasmerlin
2026-03-28 21:42:23 +01:00
49 changed files with 807 additions and 149 deletions

View File

@@ -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)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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;

View 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()
}
}
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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()

View File

@@ -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

View File

@@ -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"]

View File

@@ -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

View File

@@ -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],
));
}
}

View File

@@ -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,

View File

@@ -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".
///
/// ```

View File

@@ -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::*,

View File

@@ -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");
});
}
}
}

View File

@@ -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> {

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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":

View File

@@ -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,

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b9ad01a55950f96a3ae9e48a2c026143d11ffee62bff4f83b4529cd884ce11f0
size 169683
oid sha256:84f0e72ce337d56f3767ebed1ab6a47f3d27c9fbcce4d8a19aeab358e12920f5
size 169664

View File

@@ -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 };

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8c630df841e98043132b920338793745549116890c1bc52d8d10b0def896a1e6
size 114409
oid sha256:deff441cd1d9142352f8759dff4b759f4572f0ddf93752349314da77abe4b254
size 115028

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:627114dcbda4f3d2255d34926ed0b77c679d248ed1d822d723479b1e6652c67a
size 249258
oid sha256:a46457b23b7b32694564d03b42bccac2f017a756225bc54b508bb6fe2ad8ee7b
size 249548

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b5b965a7c690fd8e8646812513e2417170b687fd37e29d220c29127ba0cc200c
size 172609
oid sha256:927a497e8b6f9ce3b71dcb67086f477e19d327c163b2b8ad868af10009c2faf2
size 172981

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:931f38ade8373ff79801c05c5d4397f2c5fcfa27022f2e1abe9eb29d561a3aef
size 76022
oid sha256:db4c0cf1c4cdae3d416afce5c58efd1cc382be86431e547fa66bcc95a0a17ddb
size 76364

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:89986132c5d2a3ccccf0a16cb2b0be07b7c5512838cc10ec9067022e7a238515
size 63918
oid sha256:57018beba5e4fb4f1e6de9c58bf898560b3a7669159d5bad91a4e2382ef57ce0
size 64004

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9c81d5a5915dac60297293b90cd3fc0d188a9335e99b318996e3e2934de7bee1
size 483497
oid sha256:999f9cc006302b8951d97b510a02f1209969c376ecc7909ed5d7b46da27c0637
size 483753

View File

@@ -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)

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eb8737af84c3d3b0c054b7e2a8bcb04685243d84cb13b72a1372dc40dbfd14fb
size 7267
oid sha256:6154c8bb550575bcb9fa0bba06da4d47079a00dffc5754b62ef2a6e7529e2090
size 7489

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f1651bb1b9bbaa3c65ecd07c39c57527f4beb4c607581a5b2596a49dcf4c5db3
size 7996
oid sha256:df2578c198b29950254ec62c6cc615a4b1c003e7ae3ea027da22fc868b392c74
size 8342

View File

@@ -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

View File

@@ -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

View 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,
}
}
}

View File

@@ -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::*,

View File

@@ -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.

View File

@@ -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<'_>,

View File

@@ -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);
}

View File

@@ -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

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:81620caad6d420f3bd0f224e5b07a02960a42436208a98d3aa012e5db61a743a
size 1510
oid sha256:6f2a57ad8dbdd121cb181e74d76db68d800aba8fdc980d8de4e962e1e85fe8f6
size 1803

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f915eafb6490ff456c5b0a7c74c38ef143262bdf74a0c6561b9cf6ee66a679ea
size 1501
oid sha256:43dc457cac18107772dbb7e5773961cf502dd685ce1cb4e94338e6b7daedaa77
size 1861