diff --git a/.github/workflows/cargo_machete.yml b/.github/workflows/cargo_machete.yml deleted file mode 100644 index 1dc162e56..000000000 --- a/.github/workflows/cargo_machete.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Cargo Machete - -on: [push, pull_request] - -jobs: - cargo-machete: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: dtolnay/rust-toolchain@stable - with: - toolchain: 1.92 - - name: Machete install - ## The official cargo-machete action - uses: bnjbvr/cargo-machete@v0.9.1 - - name: Checkout - uses: actions/checkout@v4 - - name: Machete Check - run: cargo machete diff --git a/.github/workflows/cargo_shear.yml b/.github/workflows/cargo_shear.yml new file mode 100644 index 000000000..734abacb5 --- /dev/null +++ b/.github/workflows/cargo_shear.yml @@ -0,0 +1,25 @@ +# Looks for unused crates. +name: Cargo Shear + +on: + push: + branches: + - "main" + pull_request: + types: [opened, synchronize] + +jobs: + cargo-shear: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Cargo Shear + uses: taiki-e/install-action@v2.48.7 + with: + tool: cargo-shear@1.11.2 + + - name: Run Cargo Shear + run: | + cargo shear diff --git a/.github/workflows/enforce_branch_name.yml b/.github/workflows/enforce_branch_name.yml index 8c2b28d37..b9df4030d 100644 --- a/.github/workflows/enforce_branch_name.yml +++ b/.github/workflows/enforce_branch_name.yml @@ -4,17 +4,23 @@ on: pull_request_target: types: [opened, reopened, synchronize] +permissions: + issues: write + jobs: check-source-branch: runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Check PR source branch + env: + IS_FORK: ${{ github.event.pull_request.head.repo.fork }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} run: | # Check if PR is from a fork - if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then + if [[ "$IS_FORK" == "true" ]]; then # Check if PR is from the master/main branch of a fork - if [[ "${{ github.event.pull_request.head.ref }}" == "master" || "${{ github.event.pull_request.head.ref }}" == "main" ]]; then + if [[ "$HEAD_REF" == "master" || "$HEAD_REF" == "main" ]]; then echo "ERROR: Pull requests from the master/main branch of forks are not allowed, because it prevents maintainers from contributing to your PR" echo "Please create a feature branch in your fork and submit the PR from that branch instead." exit 1 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 9018d251b..2122e5b99 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -94,7 +94,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml + version: "0.2.108" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml - run: ./scripts/wasm_bindgen_check.sh --skip-setup diff --git a/.github/workflows/taplo.yml b/.github/workflows/taplo.yml new file mode 100644 index 000000000..11a7b978a --- /dev/null +++ b/.github/workflows/taplo.yml @@ -0,0 +1,25 @@ +# Checks that all TOML files are formatted with taplo. +name: Taplo + +on: + push: + branches: + - "main" + pull_request: + types: [opened, synchronize] + +jobs: + taplo: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Taplo + uses: taiki-e/install-action@v2.48.7 + with: + tool: taplo-cli@0.9.3 + + - name: Check TOML formatting + run: | + taplo fmt --check diff --git a/CHANGELOG.md b/CHANGELOG.md index 12010e287..4bae7a734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,146 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.2 - 2026-05-04 +### ⭐ Added +* Add regression test for O(n²) word boundary scan [#8077](https://github.com/emilk/egui/pull/8077) by [@hallyhaa](https://github.com/hallyhaa) + +### šŸ› Fixed +* Fix wrong color of last glyph of selected text [#8075](https://github.com/emilk/egui/pull/8075) by [@emilk](https://github.com/emilk) +* Fix text selection of centered and right-aligned text [#8076](https://github.com/emilk/egui/pull/8076) by [@emilk](https://github.com/emilk) +* Fix `Context::is_pointer_over_egui` and `Context::egui_wants_pointer_input` [#8081](https://github.com/emilk/egui/pull/8081) by [@emilk](https://github.com/emilk) +* Fix centered & right aligned `TextEdit` [#8082](https://github.com/emilk/egui/pull/8082) by [@lucasmerlin](https://github.com/lucasmerlin) + +### šŸš€ Performance +* Optimize text selection performance for large documents [#7917](https://github.com/emilk/egui/pull/7917) by [@rustbasic](https://github.com/rustbasic) + + +## 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` 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) diff --git a/Cargo.lock b/Cargo.lock index 800618e4a..942ddc6e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,7 +96,7 @@ dependencies = [ "hashbrown 0.16.1", "static_assertions", "windows", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -124,9 +124,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" @@ -135,7 +135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.1", + "getrandom 0.3.4", "once_cell", "serde", "version_check", @@ -159,23 +159,22 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-activity" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" dependencies = [ "android-properties", "bitflags 2.9.4", "cc", - "cesu8", "jni", - "jni-sys", "libc", "log", "ndk", "ndk-context", "ndk-sys", "num_enum", - "thiserror 1.0.66", + "simd_cesu8", + "thiserror 2.0.18", ] [[package]] @@ -281,7 +280,7 @@ dependencies = [ "clipboard-win", "image", "log", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", @@ -369,7 +368,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.38", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -401,7 +400,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 0.38.38", + "rustix 0.38.44", "tracing", ] @@ -428,7 +427,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.38", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -496,9 +495,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -512,7 +511,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -536,7 +535,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec 0.9.1", ] [[package]] @@ -545,6 +553,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + [[package]] name = "bitflags" version = "1.3.2" @@ -560,12 +574,6 @@ dependencies = [ "serde", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block2" version = "0.5.1" @@ -581,7 +589,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.3", + "objc2 0.6.4", ] [[package]] @@ -610,15 +618,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -661,7 +669,7 @@ dependencies = [ "bitflags 2.9.4", "log", "polling", - "rustix 0.38.38", + "rustix 0.38.44", "slab", "thiserror 1.0.66", ] @@ -673,7 +681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.38", + "rustix 0.38.44", "wayland-backend", "wayland-client", ] @@ -686,26 +694,21 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -722,19 +725,6 @@ dependencies = [ "libc", ] -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link 0.2.1", -] - [[package]] name = "ciborium" version = "0.2.2" @@ -804,9 +794,9 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ "serde", "termcolor", @@ -923,7 +913,7 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types 0.1.3", + "core-graphics-types", "foreign-types", "libc", ] @@ -939,17 +929,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.10.1", - "libc", -] - [[package]] name = "core_maths" version = "0.1.1" @@ -961,9 +940,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -1161,7 +1140,7 @@ dependencies = [ "bitflags 2.9.4", "block2 0.6.2", "libc", - "objc2 0.6.3", + "objc2 0.6.4", ] [[package]] @@ -1186,9 +1165,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -1207,7 +1186,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.33.3" +version = "0.34.2" dependencies = [ "bytemuck", "cint", @@ -1219,7 +1198,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.33.3" +version = "0.34.2" dependencies = [ "ahash", "bytemuck", @@ -1236,9 +1215,9 @@ dependencies = [ "image", "js-sys", "log", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", "pollster", @@ -1258,7 +1237,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.33.3" +version = "0.34.2" dependencies = [ "accesskit", "ahash", @@ -1278,7 +1257,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.33.3" +version = "0.34.2" dependencies = [ "ahash", "bytemuck", @@ -1287,7 +1266,7 @@ dependencies = [ "epaint", "log", "profiling", - "thiserror 2.0.17", + "thiserror 2.0.18", "type-map", "web-time", "wgpu", @@ -1296,7 +1275,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.33.3" +version = "0.34.2" dependencies = [ "accesskit_winit", "arboard", @@ -1304,9 +1283,9 @@ dependencies = [ "document-features", "egui", "log", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit", + "objc2 0.6.4", + "objc2-foundation 0.3.2", + "objc2-ui-kit 0.3.2", "profiling", "raw-window-handle", "serde", @@ -1319,12 +1298,11 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.33.3" +version = "0.34.2" dependencies = [ "accesskit", "accesskit_consumer", "bytemuck", - "chrono", "eframe", "egui", "egui_demo_lib", @@ -1333,6 +1311,7 @@ dependencies = [ "ehttp", "env_logger", "image", + "jiff", "log", "mimalloc", "poll-promise", @@ -1344,37 +1323,36 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu", ] [[package]] name = "egui_demo_lib" -version = "0.33.3" +version = "0.34.2" dependencies = [ - "chrono", "criterion", "document-features", "egui", "egui_extras", "egui_kittest", "image", + "jiff", "mimalloc", - "rand 0.9.2", + "rand 0.9.3", "serde", "unicode_names2", ] [[package]] name = "egui_extras" -version = "0.33.3" +version = "0.34.2" dependencies = [ "ahash", - "chrono", "document-features", "egui", "ehttp", "enum-map", "image", + "jiff", "log", "mime_guess2", "profiling", @@ -1385,7 +1363,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.33.3" +version = "0.34.2" dependencies = [ "bytemuck", "document-features", @@ -1397,14 +1375,12 @@ dependencies = [ "log", "memoffset", "profiling", - "wasm-bindgen", - "web-sys", "winit", ] [[package]] name = "egui_kittest" -version = "0.33.3" +version = "0.34.2" dependencies = [ "dify", "document-features", @@ -1424,7 +1400,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.33.3" +version = "0.34.2" dependencies = [ "egui", "egui_extras", @@ -1434,9 +1410,9 @@ dependencies = [ [[package]] name = "ehttp" -version = "0.6.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04499d3c719edecfad5c9b46031726c8540905d73be6d7e4f9788c4a298da908" +checksum = "b2f1b93eb2e039aaff63ce07cca59bd1dca02f2ce30075a17b619d2c42f56efc" dependencies = [ "document-features", "js-sys", @@ -1454,7 +1430,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.33.3" +version = "0.34.2" dependencies = [ "bytemuck", "document-features", @@ -1552,7 +1528,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.33.3" +version = "0.34.2" dependencies = [ "ahash", "bytemuck", @@ -1562,6 +1538,7 @@ dependencies = [ "emath", "epaint_default_fonts", "font-types", + "harfrust", "log", "mimalloc", "nohash-hasher", @@ -1573,12 +1550,14 @@ dependencies = [ "similar-asserts", "skrifa", "smallvec", + "unicode-general-category", + "unicode-segmentation", "vello_cpu", ] [[package]] name = "epaint_default_fonts" -version = "0.33.3" +version = "0.34.2" [[package]] name = "equivalent" @@ -1604,9 +1583,9 @@ checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" [[package]] name = "euclid" -version = "0.22.11" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" dependencies = [ "num-traits", ] @@ -1658,7 +1637,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" dependencies = [ - "bit-set", + "bit-set 0.8.0", "regex-automata", "regex-syntax", ] @@ -1680,12 +1659,9 @@ dependencies = [ [[package]] name = "fearless_simd" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb2907d1f08b2b316b9223ced5b0e89d87028ba8deae9764741dba8ff7f3903" -dependencies = [ - "bytemuck", -] +checksum = "76258897e51fd156ee03b6246ea53f3e0eb395d0b327e9961c4fc4c8b2fa151a" [[package]] name = "file_dialog" @@ -1697,10 +1673,16 @@ dependencies = [ ] [[package]] -name = "flate2" -version = "1.1.4" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1732,9 +1714,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "font-types" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4d2d0cf79d38430cc9dc9aadec84774bff2e1ba30ae2bf6c16cfce9385a23" +checksum = "73829a7b5c91198af28a99159b7ae4afbb252fb906159ff7f189f3a2ceaa3df2" dependencies = [ "bytemuck", "serde", @@ -1859,7 +1841,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.4", "windows-targets 0.52.6", ] @@ -1874,25 +1856,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasip2", ] [[package]] @@ -1924,9 +1906,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" dependencies = [ "js-sys", "slotmap", @@ -1948,7 +1930,7 @@ dependencies = [ "glutin_glx_sys", "glutin_wgl_sys", "libloading", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -2010,7 +1992,7 @@ dependencies = [ "hashbrown 0.16.1", "log", "presser", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows", ] @@ -2034,6 +2016,15 @@ dependencies = [ "bitflags 2.9.4", ] +[[package]] +name = "guillotiere" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b17e70c989c36bad147b27a58d148c0741c51448aa5653436547323e524d0ab" +dependencies = [ + "euclid", +] + [[package]] name = "half" version = "2.6.0" @@ -2045,6 +2036,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "harfrust" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "core_maths", + "read-fonts", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -2131,28 +2135,20 @@ dependencies = [ ] [[package]] -name = "iana-time-zone" -version = "0.1.63" +name = "http" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.61.2", + "bytes", + "itoa", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "httparse" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "icu_collections" @@ -2360,28 +2356,31 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", + "js-sys", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", + "wasm-bindgen", + "windows-sys 0.61.2", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -2390,25 +2389,61 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-macros", + "jni-sys 0.4.1", "log", - "thiserror 1.0.66", + "simd_cesu8", + "thiserror 2.0.18", "walkdir", - "windows-sys 0.45.0", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" @@ -2427,9 +2462,9 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -2462,8 +2497,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kittest" -version = "0.3.0" -source = "git+https://github.com/rerun-io/kittest?branch=main#ce7a2f3b12c36021889b50bdff671cec8016b0fb" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ceaa75eb0036a32b6b9833962eb18137449e9817e2e586006471925b727fd5" dependencies = [ "accesskit", "accesskit_consumer", @@ -2498,9 +2534,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.176" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" @@ -2536,7 +2572,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.4", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.18", ] [[package]] @@ -2553,15 +2589,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -2571,9 +2607,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "litrs" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" @@ -2592,18 +2628,9 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lz4_flex" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" [[package]] name = "memchr" @@ -2629,21 +2656,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "metal" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" -dependencies = [ - "bitflags 2.9.4", - "block", - "core-graphics-types 0.2.0", - "foreign-types", - "log", - "objc", - "paste", -] - [[package]] name = "mimalloc" version = "0.1.48" @@ -2694,7 +2706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -2708,12 +2720,12 @@ dependencies = [ [[package]] name = "naga" -version = "28.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" +checksum = "aa2630921705b9b01dcdd0b6864b9562ca3c1951eecd0f0c4f5f04f61e412647" dependencies = [ "arrayvec", - "bit-set", + "bit-set 0.9.1", "bitflags 2.9.4", "cfg-if", "cfg_aliases", @@ -2728,7 +2740,7 @@ dependencies = [ "once_cell", "rustc-hash 1.1.0", "spirv", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-ident", ] @@ -2739,7 +2751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.9.4", - "jni-sys", + "jni-sys 0.3.1", "log", "ndk-sys", "num_enum", @@ -2759,7 +2771,7 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", + "jni-sys 0.3.1", ] [[package]] @@ -2818,15 +2830,6 @@ dependencies = [ "syn", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -2845,9 +2848,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -2865,7 +2868,7 @@ dependencies = [ "objc2-core-data", "objc2-core-image", "objc2-foundation 0.2.2", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -2876,7 +2879,7 @@ checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.9.4", "block2 0.6.2", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -2926,7 +2929,7 @@ checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.9.4", "dispatch2", - "objc2 0.6.3", + "objc2 0.6.4", ] [[package]] @@ -2937,7 +2940,7 @@ checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.9.4", "dispatch2", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-core-foundation", "objc2-io-surface", ] @@ -2951,7 +2954,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] @@ -2992,7 +2995,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.3", + "block2 0.6.2", + "objc2 0.6.4", "objc2-core-foundation", ] @@ -3003,7 +3007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.9.4", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-core-foundation", ] @@ -3031,6 +3035,18 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -3041,7 +3057,20 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", ] [[package]] @@ -3069,12 +3098,24 @@ dependencies = [ "objc2-core-location", "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", ] +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-uniform-type-identifiers" version = "0.2.2" @@ -3110,9 +3151,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -3204,17 +3245,11 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pathdiff" version = "0.2.3" @@ -3365,9 +3400,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "piper" @@ -3432,7 +3467,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 0.38.38", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -3445,7 +3480,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.33.3" +version = "0.34.2" dependencies = [ "eframe", "env_logger", @@ -3498,18 +3533,18 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3594,13 +3629,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -3614,9 +3655,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -3648,7 +3689,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] @@ -3657,7 +3698,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.4", ] [[package]] @@ -3672,6 +3713,18 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + [[package]] name = "rayon" version = "1.11.0" @@ -3699,6 +3752,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" dependencies = [ "bytemuck", + "core_maths", "font-types", ] @@ -3713,9 +3767,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.9.4", ] @@ -3726,9 +3780,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3791,7 +3845,7 @@ dependencies = [ "js-sys", "libc", "log", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -3824,7 +3878,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3832,14 +3886,15 @@ dependencies = [ [[package]] name = "ron" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" dependencies = [ - "base64", "bitflags 2.9.4", + "once_cell", "serde", "serde_derive", + "typeid", "unicode-ident", ] @@ -3868,36 +3923,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] -name = "rustix" -version = "0.38.38" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags 2.9.4", - "errno", - "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "semver", ] [[package]] name = "rustix" -version = "1.0.8" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.4", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.9.4", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.18" +version = "0.23.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" dependencies = [ "log", "once_cell", @@ -3910,15 +3974,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -3927,9 +3994,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustybuzz" @@ -3998,6 +4065,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -4087,9 +4160,25 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" @@ -4138,12 +4227,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slotmap" @@ -4176,7 +4262,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix 0.38.38", + "rustix 0.38.44", "thiserror 1.0.66", "wayland-backend", "wayland-client", @@ -4220,9 +4306,9 @@ dependencies = [ [[package]] name = "spirv" -version = "0.3.0+sdk-1.3.268.0" +version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ "bitflags 2.9.4", ] @@ -4266,9 +4352,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -4302,7 +4388,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "walkdir", "yaml-rust", ] @@ -4314,9 +4400,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.8", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -4389,11 +4475,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4409,9 +4495,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -4536,26 +4622,17 @@ dependencies = [ [[package]] name = "toml" -version = "1.0.2+spec-1.1.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1dfefef6a142e93f346b64c160934eb13b5594b84ab378133ac6815cb2bd57f" +checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" dependencies = [ "serde_core", "serde_spanned", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime", "toml_parser", "winnow", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -4567,12 +4644,12 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", - "toml_datetime 0.7.5+spec-1.1.0", + "toml_datetime", "toml_parser", "winnow", ] @@ -4636,14 +4713,20 @@ dependencies = [ ] [[package]] -name = "uds_windows" -version = "1.1.0" +name = "typeid" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -4671,10 +4754,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" [[package]] -name = "unicode-ident" -version = "1.0.19" +name = "unicode-general-category" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-properties" @@ -4734,20 +4823,33 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.10.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" dependencies = [ "base64", "flate2", "log", - "once_cell", + "percent-encoding", "rustls", "rustls-pki-types", - "url", + "ureq-proto", + "utf8-zero", "webpki-roots", ] +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.4" @@ -4794,6 +4896,12 @@ dependencies = [ "xmlwriter", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4818,28 +4926,41 @@ dependencies = [ ] [[package]] -name = "vello_common" -version = "0.0.6" +name = "vello_api" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd1a4c633ce09e7d713df1a6e036644a125e15e0c169cfb5180ddf5836ca04b" +checksum = "a5088cd0113bc5332c753f24503825e3bc93e26c7883c9dc3ad9637bb62c4634" +dependencies = [ + "bytemuck", + "peniko", +] + +[[package]] +name = "vello_common" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986dc49a501a683477614bf07b8e7b6c79ae4828efce3bf22e51850f4a0a8a4c" dependencies = [ "bytemuck", "fearless_simd", + "guillotiere", "hashbrown 0.16.1", "log", "peniko", "skrifa", "smallvec", + "thiserror 2.0.18", ] [[package]] name = "vello_cpu" -version = "0.0.6" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162bfe48aabf6a9fdcd401b628c7d9f260c2cbabb343c70a65feba6f7849edc" +checksum = "a678f91c7524a3a9ac9a19df9f83552866ec70b2ca26441b916a6b219b6aa2de" dependencies = [ "bytemuck", "hashbrown 0.16.1", + "vello_api", "vello_common", ] @@ -4861,52 +4982,40 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -4915,9 +5024,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4925,22 +5034,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -4953,7 +5062,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" dependencies = [ "cc", "downcast-rs", - "rustix 1.0.8", + "rustix 1.1.4", "scoped-tls", "smallvec", "wayland-sys", @@ -4966,7 +5075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" dependencies = [ "bitflags 2.9.4", - "rustix 1.0.8", + "rustix 1.1.4", "wayland-backend", "wayland-scanner", ] @@ -4988,7 +5097,7 @@ version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29" dependencies = [ - "rustix 1.0.8", + "rustix 1.1.4", "wayland-client", "xcursor", ] @@ -5056,9 +5165,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -5076,15 +5185,15 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.5" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" +checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" dependencies = [ "core-foundation 0.10.1", "jni", "log", "ndk-context", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-foundation 0.3.2", "url", "web-sys", @@ -5092,9 +5201,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.6" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -5107,9 +5216,9 @@ checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" -version = "28.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" +checksum = "72c239a9a747bbd379590985bac952c2e53cb19873f7072b3370c6a6a8e06837" dependencies = [ "arrayvec", "bitflags 2.9.4", @@ -5137,13 +5246,13 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "28.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb4c8b5db5f00e56f1f08869d870a0dff7c8bc7ebc01091fec140b0cf0211a9" +checksum = "1e80ac6cf1895df6342f87d975162108f9d98772a0d74bc404ab7304ac29469e" dependencies = [ "arrayvec", - "bit-set", - "bit-vec", + "bit-set 0.9.1", + "bit-vec 0.9.1", "bitflags 2.9.4", "bytemuck", "cfg_aliases", @@ -5159,67 +5268,67 @@ dependencies = [ "raw-window-handle", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wgpu-core-deps-apple", "wgpu-core-deps-emscripten", "wgpu-core-deps-wasm", "wgpu-core-deps-windows-linux-android", "wgpu-hal", + "wgpu-naga-bridge", "wgpu-types", ] [[package]] name = "wgpu-core-deps-apple" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da" +checksum = "43acd053312501689cd92a01a9638d37f3e41a5fd9534875efa8917ee2d11ac0" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79" +checksum = "ef043bf135cc68b6f667c55ff4e345ce2b5924d75bad36a47921b0287ca4b24a" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-wasm" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a2cf578ce8d7d50d0e63ddc2345c7dcb599f6eb90b888813406ea78b9b7010" +checksum = "2f7b75e72f49035f000dd5262e4126242e92a090a4fd75931ecfe7e60784e6fa" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae" +checksum = "725d5c006a8c02967b6d93ef04f6537ec4593313e330cfe86d9d3f946eb90f28" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "28.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293080d77fdd14d6b08a67c5487dfddbf874534bb7921526db56a7b75d7e3bef" +checksum = "89a47aef47636562f3937285af4c44b4b5b404b46577471411cc5313a921da7e" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", + "bit-set 0.9.1", "bitflags 2.9.4", - "block", + "block2 0.6.2", "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", "gpu-allocator", @@ -5230,10 +5339,13 @@ dependencies = [ "libc", "libloading", "log", - "metal", "naga", "ndk-sys", - "objc", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", "once_cell", "ordered-float", "parking_lot", @@ -5242,45 +5354,43 @@ dependencies = [ "profiling", "range-alloc", "raw-window-handle", + "raw-window-metal", "renderdoc-sys", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wasm-bindgen", + "wayland-sys", "web-sys", + "wgpu-naga-bridge", "wgpu-types", "windows", - "windows-core 0.62.2", + "windows-core", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4684f4410da0cf95a4cb63bb5edaac022461dedb6adf0b64d0d9b5f6890d51" +dependencies = [ + "naga", + "wgpu-types", ] [[package]] name = "wgpu-types" -version = "28.0.0" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" +checksum = "ec2675540fb1a5cfa5ef122d3d5f390e2c75711a0b946410f2d6ac3a0f77d1f6" dependencies = [ "bitflags 2.9.4", "bytemuck", "js-sys", "log", + "raw-window-handle", "web-sys", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.9" @@ -5290,12 +5400,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows" version = "0.62.2" @@ -5303,7 +5407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.62.2", + "windows-core", "windows-future", "windows-numerics", ] @@ -5314,20 +5418,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -5338,9 +5429,9 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -5349,8 +5440,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", + "windows-core", + "windows-link", "windows-threading", ] @@ -5376,12 +5467,6 @@ dependencies = [ "syn", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -5394,17 +5479,8 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", - "windows-link 0.2.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-core", + "windows-link", ] [[package]] @@ -5413,16 +5489,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -5431,16 +5498,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-link", ] [[package]] @@ -5467,7 +5525,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] @@ -5476,22 +5534,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-link", ] [[package]] @@ -5512,11 +5555,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -5533,15 +5576,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -5554,12 +5591,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -5572,12 +5603,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5602,12 +5627,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -5620,12 +5639,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -5638,12 +5651,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -5656,12 +5663,6 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -5676,9 +5677,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winit" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" dependencies = [ "ahash", "android-activity", @@ -5700,13 +5701,13 @@ dependencies = [ "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", - "objc2-ui-kit", + "objc2-ui-kit 0.2.2", "orbclient", "percent-encoding", "pin-project", "raw-window-handle", "redox_syscall 0.4.1", - "rustix 0.38.38", + "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -5736,13 +5737,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.33.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags 2.9.4", -] +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -5772,7 +5770,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 1.0.8", + "rustix 1.1.4", "x11rb-protocol", ] @@ -5821,7 +5819,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.33.3" +version = "0.34.2" [[package]] name = "yaml-rust" @@ -5995,9 +5993,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index d12b040fe..b924a7db3 100644 --- a/Cargo.toml +++ b/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.2" [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.2", path = "crates/emath", default-features = false } +ecolor = { version = "0.34.2", path = "crates/ecolor", default-features = false } +epaint = { version = "0.34.2", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.34.2", path = "crates/epaint_default_fonts" } +egui = { version = "0.34.2", path = "crates/egui", default-features = false } +egui-winit = { version = "0.34.2", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.34.2", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.34.2", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.34.2", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.34.2", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.34.2", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.34.2", path = "crates/eframe", default-features = false } accesskit = "0.24.0" accesskit_consumer = "0.35.0" @@ -80,34 +80,35 @@ arboard = { version = "3.6.1", default-features = false } backtrace = "0.3.76" bitflags = "2.9.4" bytemuck = "1.24.0" -chrono = { version = "0.4.42", default-features = false } cint = "0.3.1" color-hex = "0.2.0" criterion = { version = "0.7.0", default-features = false } dify = { version = "0.8", default-features = false } directories = "6.0.0" document-features = "0.2.11" -ehttp = { version = "0.6.0", default-features = false } +ehttp = { version = "0.7.1", default-features = false } enum-map = "2.7.3" env_logger = { version = "0.11.8", default-features = false } font-types = { version = "0.11.0", default-features = false, features = ["std"] } -glow = "0.16.0" +glow = "0.17.0" glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } +harfrust = "0.5.2" home = "0.5.9" image = { version = "0.25.6", default-features = false } +jiff = { version = "0.2.23", default-features = false } js-sys = "0.3.77" -kittest = { version = "0.3.0" } +kittest = { version = "0.4.0" } log = { version = "0.4.28", features = ["std"] } memoffset = "0.9.1" mimalloc = "0.1.48" mime_guess2 = { version = "2.3.1", default-features = false } mint = "0.5.9" nohash-hasher = "0.2.0" -objc2 = "0.5.2" -objc2-app-kit = { version = "0.2.2", default-features = false } -objc2-foundation = { version = "0.2.2", default-features = false } -objc2-ui-kit = { version = "0.2.2", default-features = false } +objc2 = "0.6.4" +objc2-app-kit = { version = "0.3.2", default-features = false } +objc2-foundation = { version = "0.3.2", default-features = false } +objc2-ui-kit = { version = "0.3.2", default-features = false } open = "5.3.2" parking_lot = "0.12.5" percent-encoding = "2.3.2" @@ -121,7 +122,7 @@ raw-window-handle = "0.6.2" rayon = "1.11.0" resvg = { version = "0.45.1", default-features = false } rfd = "0.17.2" -ron = "0.11.0" +ron = "0.12.0" self_cell = "1.2.1" serde = { version = "1.0.228", features = ["derive"] } similar-asserts = "1.7.0" @@ -133,23 +134,25 @@ syntect = { version = "5.3.0", default-features = false } tempfile = "3.23.0" thiserror = "2.0.17" tokio = "1.49" -toml = {version = "1", default-features = false } +toml = { version = "1.0.0", default-features = false } type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } +unicode-general-category = "1.1.0" unicode-segmentation = "1.12.0" -vello_cpu = { version = "0.0.6", default-features = false, features = ["std", "u8_pipeline", "f32_pipeline"] } -wasm-bindgen = "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml -wasm-bindgen-futures = "0.4.0" +vello_cpu = { version = "0.0.7", default-features = false, features = [ + "std", + "u8_pipeline", + "f32_pipeline", +] } +wasm-bindgen = "0.2.108" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml. Don't update this spuriously, because of https://github.com/rerun-io/rerun/issues/8766 +wasm-bindgen-futures = "0.4.58" wayland-cursor = { version = "0.31.11", default-features = false } web-sys = "0.3.77" web-time = "1.1.0" # Timekeeping for native and web webbrowser = "1.0.5" -wgpu = { version = "28.0.0", default-features = false, features = ["std"] } +wgpu = { version = "29.0.1", default-features = false, features = ["std"] } windows-sys = "0.61.2" -winit = { version = "0.30.12", default-features = false } - -[patch.crates-io] -kittest = { git = "https://github.com/rerun-io/kittest", branch = "main" } +winit = { version = "0.30.13", default-features = false } [workspace.lints.rust] unsafe_code = "deny" @@ -221,10 +224,12 @@ flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" fn_to_numeric_cast_any = "warn" +format_push_string = "warn" from_iter_instead_of_collect = "warn" get_unwrap = "warn" if_let_mutex = "warn" ignore_without_reason = "warn" +ignored_unit_patterns = "warn" implicit_clone = "warn" implied_bounds_in_impls = "warn" imprecise_flops = "warn" @@ -273,6 +278,7 @@ mismatching_type_param_order = "warn" missing_assert_message = "warn" missing_enforced_import_renames = "warn" missing_errors_doc = "warn" +missing_fields_in_debug = "warn" missing_safety_doc = "warn" mixed_attributes_style = "warn" mut_mut = "warn" @@ -282,6 +288,7 @@ needless_continue = "warn" needless_for_each = "warn" needless_pass_by_ref_mut = "warn" needless_pass_by_value = "warn" +needless_raw_string_hashes = "warn" negative_feature_names = "warn" non_std_lazy_statics = "warn" non_zero_suggestions = "warn" @@ -302,6 +309,7 @@ rc_mutex = "warn" readonly_write_lock = "warn" redundant_type_annotations = "warn" ref_as_ptr = "warn" +ref_option = "warn" ref_option_ref = "warn" ref_patterns = "warn" rest_pat_in_fully_bound_structs = "warn" diff --git a/README.md b/README.md index f4a094465..7eebd0423 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,14 @@ [![Discord](https://img.shields.io/discord/900275882684477440?label=egui%20discord)](https://discord.gg/JFcEma9bJq) +
- + -egui development is sponsored by [Rerun](https://www.rerun.io/), a startup building
-an SDK for visualizing streams of multimodal data. -
- ---- +
šŸ‘‰ [Click to run the web demo](https://www.egui.rs/#demo) šŸ‘ˆ + egui (pronounced "e-gooey") is a simple, fast, and highly portable immediate mode GUI library for Rust. egui runs on the web, natively, and [in your favorite game engine](#integrations). diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index a4fe1f3de..1e10de83b 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,18 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.2 - 2026-05-04 +Nothing new + + +## 0.34.1 - 2026-03-27 +Nothing new + + +## 0.34.0 - 2026-03-26 +Nothing new + + ## 0.33.3 - 2025-12-11 Nothing new diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index f37aff109..43543113a 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui" categories = ["mathematics", "encoding"] keywords = ["gui", "color", "conversion", "gamedev", "images"] -include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +include = ["../../LICENSE-APACHE", "../../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [lints] workspace = true diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 74a705251..9071754f2 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,39 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.2 - 2026-05-04 +* Document glow-only fields in `NativeOptions` [#8104](https://github.com/emilk/egui/pull/8104) by [@emilk](https://github.com/emilk) + + +## 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 `` [#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 diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 86f63c50e..0530b64a8 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/eframe" categories = ["gui", "game-development"] keywords = ["egui", "gui", "gamedev"] -include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml", "data/icon.png"] +include = ["../../LICENSE-APACHE", "../../LICENSE-MIT", "**/*.rs", "Cargo.toml", "data/icon.png"] [package.metadata.docs.rs] all-features = true @@ -89,7 +89,7 @@ web_screen_reader = ["web-sys/SpeechSynthesis", "web-sys/SpeechSynthesisUtteranc ## See 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] @@ -169,10 +168,17 @@ objc2-foundation = { workspace = true, default-features = false, features = [ objc2-app-kit = { workspace = true, default-features = false, features = [ "std", "NSApplication", + "NSBitmapImageRep", + "NSButton", + "NSControl", + "NSGraphics", "NSImage", + "NSImageRep", "NSMenu", "NSMenuItem", "NSResponder", + "NSView", + "NSWindow", ] } # windows: diff --git a/crates/eframe/data/icon.png b/crates/eframe/data/icon.png index cf1e6c3eb..4ce7cc588 100644 Binary files a/crates/eframe/data/icon.png and b/crates/eframe/data/icon.png differ diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index b9a178a1d..fae4cbc7e 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -2,7 +2,7 @@ //! //! `epi` provides interfaces for window management and serialization. //! -//! Start by looking at the [`App`] trait, and implement [`App::update`]. +//! Start by looking at the [`App`] trait, and implement [`App::ui`]. #![warn(missing_docs)] // Let's keep `epi` well-documented. @@ -161,22 +161,6 @@ pub trait App { /// (A "viewport" in egui means an native OS window). fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame); - /// Called each time the UI needs repainting, which may be many times per second. - /// - /// Put your widgets into a [`egui::Panel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. - /// - /// The [`egui::Context`] can be cloned and saved if you like. - /// - /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread). - /// - /// This is called for the root viewport ([`egui::ViewportId::ROOT`]). - /// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows). - /// (A "viewport" in egui means an native OS window). - #[deprecated = "Use Self::ui instead"] - fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { - _ = (ctx, frame); - } - /// Get a handle to the app. /// /// Can be used from web to interact or other external context. @@ -256,7 +240,7 @@ pub trait App { true } - /// A hook for manipulating or filtering raw input before it is processed by [`Self::update`]. + /// A hook for manipulating or filtering raw input before it is processed by [`Self::ui`]. /// /// This function provides a way to modify or filter input events before they are processed by egui. /// @@ -275,22 +259,6 @@ pub trait App { fn raw_input_hook(&mut self, _ctx: &egui::Context, _raw_input: &mut egui::RawInput) {} } -/// Selects the level of hardware graphics acceleration. -#[cfg(not(target_arch = "wasm32"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum HardwareAcceleration { - /// Require graphics acceleration. - Required, - - /// Prefer graphics acceleration, but fall back to software. - Preferred, - - /// Do NOT use graphics acceleration. - /// - /// On some platforms (macOS) this is ignored and treated the same as [`Self::Preferred`]. - Off, -} - /// Options controlling the behavior of a native window. /// /// Additional windows can be opened using (egui viewports)[`egui::viewport`]. @@ -314,11 +282,6 @@ pub struct NativeOptions { /// To avoid this, set the icon to [`egui::IconData::default`]. pub viewport: egui::ViewportBuilder, - /// Turn on vertical syncing, limiting the FPS to the display refresh rate. - /// - /// The default is `true`. - pub vsync: bool, - /// Set the level of the multisampling anti-aliasing (MSAA). /// /// Must be a power-of-two. Higher = more smooth 3D. @@ -340,11 +303,6 @@ pub struct NativeOptions { /// `egui` doesn't need the stencil buffer, so the default value is 0. pub stencil_buffer: u8, - /// Specify whether or not hardware acceleration is preferred, required, or not. - /// - /// Default: [`HardwareAcceleration::Preferred`]. - pub hardware_acceleration: HardwareAcceleration, - /// What rendering backend to use. #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub renderer: Renderer, @@ -381,13 +339,6 @@ pub struct NativeOptions { #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub window_builder: Option, - #[cfg(feature = "glow")] - /// Needed for cross compiling for VirtualBox VMSVGA driver with OpenGL ES 2.0 and OpenGL 2.1 which doesn't support SRGB texture. - /// See . - /// - /// For OpenGL ES 2.0: set this to [`egui_glow::ShaderVersion::Es100`] to solve blank texture problem (by using the "fallback shader"). - pub shader_version: Option, - /// On desktop: make the window position to be centered at initialization. /// /// Platform specific: @@ -395,6 +346,10 @@ pub struct NativeOptions { /// Wayland desktop currently not supported. pub centered: bool, + /// Configures glow instance. + #[cfg(feature = "glow")] + pub glow_options: egui_glow::GlowConfiguration, + /// Configures wgpu instance/device/adapter/surface creation and renderloop. #[cfg(feature = "wgpu_no_default_features")] pub wgpu_options: egui_wgpu::WgpuConfiguration, @@ -439,6 +394,9 @@ impl Clone for NativeOptions { #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] window_builder: None, // Skip any builder callbacks if cloning + #[cfg(feature = "glow")] + glow_options: self.glow_options.clone(), + #[cfg(feature = "wgpu_no_default_features")] wgpu_options: self.wgpu_options.clone(), @@ -458,11 +416,9 @@ impl Default for NativeOptions { Self { viewport: Default::default(), - vsync: true, multisampling: 0, depth_buffer: 0, stencil_buffer: 0, - hardware_acceleration: HardwareAcceleration::Preferred, #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] renderer: Renderer::default(), @@ -475,13 +431,14 @@ impl Default for NativeOptions { #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] window_builder: None, - #[cfg(feature = "glow")] - shader_version: None, - centered: false, + #[cfg(feature = "glow")] + glow_options: egui_glow::GlowConfiguration::default(), + #[cfg(feature = "wgpu_no_default_features")] - wgpu_options: egui_wgpu::WgpuConfiguration::default(), + wgpu_options: egui_wgpu::WgpuConfiguration::default() + .with_surface_config(egui_wgpu::SurfaceConfig::LOW_LATENCY), persist_window: true, @@ -516,6 +473,10 @@ pub struct WebOptions { #[cfg(feature = "glow")] pub webgl_context_option: WebGlContextOption, + /// Configures glow instance. + #[cfg(feature = "glow")] + pub glow_options: egui_glow::GlowConfiguration, + /// Configures wgpu instance/device/adapter/surface creation and renderloop. #[cfg(feature = "wgpu_no_default_features")] pub wgpu_options: egui_wgpu::WgpuConfiguration, @@ -560,6 +521,9 @@ impl Default for WebOptions { #[cfg(feature = "glow")] webgl_context_option: WebGlContextOption::BestFirst, + #[cfg(feature = "glow")] + glow_options: egui_glow::GlowConfiguration::default(), + #[cfg(feature = "wgpu_no_default_features")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), @@ -776,7 +740,7 @@ impl Frame { /// * Read the pixel buffer from the previous frame (`glow::Context::read_pixels`). /// * Render things behind the egui windows. /// - /// Note that all egui painting is deferred to after the call to [`App::update`] + /// Note that all egui painting is deferred to after the call to [`App::ui`] /// ([`egui`] only collects [`egui::Shape`]s and then eframe paints them all in one go later on). /// /// To get a [`glow`] context you need to compile with the `glow` feature flag, @@ -805,6 +769,28 @@ impl Frame { pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> { self.wgpu_render_state.as_ref() } + + /// The currently-applied runtime surface config (present mode, frame latency) + /// used by the `wgpu` renderer, if any. + /// + /// Returns `None` when not using the `wgpu` backend. + #[cfg(feature = "wgpu_no_default_features")] + pub fn wgpu_surface_config(&self) -> Option { + self.wgpu_render_state + .as_ref() + .map(|state| state.surface_config) + } + + /// Set the runtime surface config (present mode, frame latency) for the `wgpu` + /// renderer. The surface is reconfigured on the next paint. + /// + /// No-op when not using the `wgpu` backend. + #[cfg(feature = "wgpu_no_default_features")] + pub fn set_wgpu_surface_config(&mut self, config: egui_wgpu::SurfaceConfig) { + if let Some(state) = &mut self.wgpu_render_state { + state.surface_config = config; + } + } } /// Information about the web environment (if applicable). @@ -882,7 +868,7 @@ pub struct IntegrationInfo { /// Seconds of cpu usage (in seconds) on the previous frame. /// - /// This includes [`App::update`] as well as rendering (except for vsync waiting). + /// This includes [`App::ui`] as well as rendering (except for vsync waiting). /// /// For a more detailed view of cpu usage, connect your preferred profiler by enabling it's feature in [`profiling`](https://crates.io/crates/profiling). /// diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index 151fb79ce..f0d049858 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -6,7 +6,7 @@ //! To get started, see the [examples](https://github.com/emilk/egui/tree/main/examples). //! To learn how to set up `eframe` for web and native, go to and follow the instructions there! //! -//! In short, you implement [`App`] (especially [`App::update`]) and then +//! In short, you implement [`App`] (especially [`App::ui`]) and then //! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`. //! //! ## Compiling for web @@ -19,7 +19,7 @@ //! //! ## Simplified usage //! If your app is only for native, and you don't need advanced features like state persistence, -//! then you can use the simpler function [`run_simple_native`]. +//! then you can use the simpler function [`run_ui_native`]. //! //! ## Usage, native: //! ``` no_run @@ -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::SurfaceConfig, egui_wgpu::WgpuConfiguration, 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; @@ -254,8 +257,27 @@ pub mod icon_data; #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] #[allow(clippy::allow_attributes, clippy::needless_pass_by_value)] pub fn run_native( + app_name: &str, + native_options: NativeOptions, + app_creator: AppCreator<'_>, +) -> Result { + run_native_ext(app_name, native_options, None, app_creator) +} + +/// Like [`run_native`], but lets you supply a pre-existing [`egui::Context`]. +/// +/// If `egui_ctx` is `Some`, that context will be used by eframe instead of creating a fresh one. +/// If it is `None`, eframe creates a new context (same behavior as [`run_native`]). +/// +/// # Errors +/// This function can fail if we fail to set up a graphics context. +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] +#[allow(clippy::allow_attributes, clippy::needless_pass_by_value)] +pub fn run_native_ext( app_name: &str, mut native_options: NativeOptions, + egui_ctx: Option, app_creator: AppCreator<'_>, ) -> Result { let renderer = init_native(app_name, &mut native_options); @@ -264,13 +286,13 @@ pub fn run_native( #[cfg(feature = "glow")] Renderer::Glow => { log::debug!("Using the glow renderer"); - native::run::run_glow(app_name, native_options, app_creator) + native::run::run_glow(app_name, native_options, egui_ctx, app_creator) } #[cfg(feature = "wgpu_no_default_features")] Renderer::Wgpu => { log::debug!("Using the wgpu renderer"); - native::run::run_wgpu(app_name, native_options, app_creator) + native::run::run_wgpu(app_name, native_options, egui_ctx, app_creator) } } } @@ -443,67 +465,6 @@ pub fn run_ui_native( ) } -/// The simplest way to get started when writing a native app. -/// -/// This does NOT support persistence of custom user data. For that you need to use [`run_native`]. -/// However, it DOES support persistence of egui data (window positions and sizes, how far the user has scrolled in a -/// [`ScrollArea`](egui::ScrollArea), etc.) if the persistence feature is enabled. -/// -/// # Example -/// ``` no_run -/// fn main() -> eframe::Result { -/// // Our application state: -/// let mut name = "Arthur".to_owned(); -/// let mut age = 42; -/// -/// let options = eframe::NativeOptions::default(); -/// eframe::run_simple_native("My egui App", options, move |ctx, _frame| { -/// egui::CentralPanel::default().show(ctx, |ui| { -/// ui.heading("My egui Application"); -/// ui.horizontal(|ui| { -/// let name_label = ui.label("Your name: "); -/// ui.text_edit_singleline(&mut name) -/// .labelled_by(name_label.id); -/// }); -/// ui.add(egui::Slider::new(&mut age, 0..=120).text("age")); -/// if ui.button("Increment").clicked() { -/// age += 1; -/// } -/// ui.label(format!("Hello '{name}', age {age}")); -/// }); -/// }) -/// } -/// ``` -/// -/// # Errors -/// This function can fail if we fail to set up a graphics context. -#[deprecated = "Use run_ui_native instead"] -#[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] -pub fn run_simple_native( - app_name: &str, - native_options: NativeOptions, - update_fun: impl FnMut(&egui::Context, &mut Frame) + 'static, -) -> Result { - struct SimpleApp { - update_fun: U, - } - - impl App for SimpleApp { - fn ui(&mut self, _ui: &mut egui::Ui, _frame: &mut Frame) {} - - fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { - (self.update_fun)(ctx, frame); - } - } - - run_native( - app_name, - native_options, - Box::new(|_cc| Ok(Box::new(SimpleApp { update_fun }))), - ) -} - // ---------------------------------------------------------------------------- /// The different problems that can occur when trying to run `eframe`. diff --git a/crates/eframe/src/native/app_icon.rs b/crates/eframe/src/native/app_icon.rs index 3ac61d8e6..85be6754b 100644 --- a/crates/eframe/src/native/app_icon.rs +++ b/crates/eframe/src/native/app_icon.rs @@ -204,7 +204,7 @@ fn set_title_and_icon_mac(title: &str, icon_data: Option<&IconData>) -> AppIconS use crate::icon_data::IconDataExt as _; profiling::function_scope!(); - use objc2::ClassType as _; + use objc2::AnyThread as _; use objc2_app_kit::{NSApplication, NSImage}; use objc2_foundation::NSString; diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 5b22eb08c..be7e3e787 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -214,15 +214,17 @@ impl EpiIntegration { Self { frame, last_auto_save: Instant::now(), - egui_ctx, pending_full_output: Default::default(), close: false, can_drag_window: false, #[cfg(feature = "persistence")] persist_window: native_options.persist_window, app_icon_setter, - beginning: Instant::now(), + beginning: Instant::now() + .checked_sub(web_time::Duration::from_secs_f64(egui_ctx.time())) + .unwrap_or_else(Instant::now), is_first_frame: true, + egui_ctx, } } @@ -259,7 +261,7 @@ impl EpiIntegration { /// Run user code - this can create immediate viewports, so hold no locks over this! /// - /// If `viewport_ui_cb` is None, we are in the root viewport and will call [`crate::App::update`]. + /// If `viewport_ui_cb` is None, we are in the root viewport and will call [`crate::App::ui`]. pub fn update( &mut self, app: &mut dyn epi::App, @@ -287,12 +289,6 @@ impl EpiIntegration { } if is_visible { - { - profiling::scope!("App::update"); - #[expect(deprecated)] - app.update(ui.ctx(), &mut self.frame); - } - { profiling::scope!("App::ui"); app.ui(ui, &mut self.frame); diff --git a/crates/eframe/src/native/file_storage.rs b/crates/eframe/src/native/file_storage.rs index f26a6df74..13e22fa75 100644 --- a/crates/eframe/src/native/file_storage.rs +++ b/crates/eframe/src/native/file_storage.rs @@ -207,7 +207,7 @@ fn save_to_disk(file_path: &PathBuf, kv: &HashMap) { profiling::scope!("ron::serialize"); if let Err(err) = ron::Options::default() .to_io_writer_pretty(&mut writer, &kv, config) - .and_then(|_| writer.flush().map_err(|err| err.into())) + .and_then(|()| writer.flush().map_err(|err| err.into())) { log::warn!("Failed to serialize app state: {err}"); } else { diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 724ddc6d5..fd61b0cd1 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -34,7 +34,7 @@ use egui_winit::accesskit_winit; use crate::{ App, AppCreator, CreationContext, NativeOptions, Result, Storage, - native::epi_integration::EpiIntegration, + native::{epi_integration::EpiIntegration, winit_integration::is_invisible_or_minimized}, }; use super::{ @@ -55,6 +55,10 @@ pub struct GlowWinitApp<'app> { // re-initializing the `GlowWinitRunning` state on Android if the application // suspends and resumes. app_creator: Option>, + + /// An optional pre-existing egui context. If `Some`, it is used instead of + /// creating a new one via [`create_egui_context`]. Taken during initialization. + egui_ctx: Option, } /// State that is initialized when the application is first starts running via @@ -128,6 +132,7 @@ impl<'app> GlowWinitApp<'app> { event_loop: &EventLoop, app_name: &str, native_options: NativeOptions, + egui_ctx: Option, app_creator: AppCreator<'app>, ) -> Self { profiling::function_scope!(); @@ -137,6 +142,7 @@ impl<'app> GlowWinitApp<'app> { native_options, running: None, app_creator: Some(app_creator), + egui_ctx, } } @@ -184,7 +190,7 @@ impl<'app> GlowWinitApp<'app> { let painter = egui_glow::Painter::new( gl, "", - native_options.shader_version, + native_options.glow_options.shader_version, native_options.dithering, )?; @@ -209,7 +215,10 @@ impl<'app> GlowWinitApp<'app> { ) }; - let egui_ctx = create_egui_context(storage.as_deref()); + let egui_ctx = self + .egui_ctx + .take() + .unwrap_or_else(|| create_egui_context(storage.as_deref())); let (mut glutin, painter) = Self::create_glutin_windowed_context( &egui_ctx, @@ -761,9 +770,11 @@ impl GlowWinitRunning<'_> { integration.maybe_autosave(app.as_mut(), Some(&window)); - if window.is_minimized() == Some(true) { + if is_invisible_or_minimized(&window) { // On Mac, a minimized Window uses up all CPU: // https://github.com/emilk/egui/issues/325 + // On Windows, an invisible window also uses up all CPU: + // https://github.com/emilk/egui/issues/7776 profiling::scope!("minimized_sleep"); std::thread::sleep(std::time::Duration::from_millis(10)); } @@ -950,12 +961,12 @@ impl GlutinWindowContext { use glutin::prelude::*; // convert native options to glutin options - let hardware_acceleration = match native_options.hardware_acceleration { - crate::HardwareAcceleration::Required => Some(true), - crate::HardwareAcceleration::Preferred => None, - crate::HardwareAcceleration::Off => Some(false), + let hardware_acceleration = match native_options.glow_options.hardware_acceleration { + egui_glow::HardwareAcceleration::Required => Some(true), + egui_glow::HardwareAcceleration::Preferred => None, + egui_glow::HardwareAcceleration::Off => Some(false), }; - let swap_interval = if native_options.vsync { + let swap_interval = if native_options.glow_options.vsync { glutin::surface::SwapInterval::Wait(NonZeroU32::MIN) } else { glutin::surface::SwapInterval::DontWait diff --git a/crates/eframe/src/native/macos.rs b/crates/eframe/src/native/macos.rs new file mode 100644 index 000000000..b1f2552e5 --- /dev/null +++ b/crates/eframe/src/native/macos.rs @@ -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 { + window_chrome_metrics(window_handle) + } +} + +fn window_chrome_metrics(window_handle: &RawWindowHandle) -> Option { + 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 { + // 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::(); + + // 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() + } + } +} diff --git a/crates/eframe/src/native/mod.rs b/crates/eframe/src/native/mod.rs index eb9413717..771964ae7 100644 --- a/crates/eframe/src/native/mod.rs +++ b/crates/eframe/src/native/mod.rs @@ -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; diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 0597d318c..b89212aa2 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -1,4 +1,4 @@ -use std::time::Instant; +use std::time::{Duration, Instant}; use winit::{ application::ApplicationHandler, @@ -11,9 +11,20 @@ use ahash::HashMap; use super::winit_integration::{UserEvent, WinitApp}; use crate::{ Result, epi, - native::{event_loop_context, winit_integration::EventResult}, + native::{ + event_loop_context, + winit_integration::{EventResult, is_invisible_or_minimized}, + }, }; +/// Minimum interval between repaints for invisible windows. +/// +/// On Windows, invisible windows don't receive `RedrawRequested` events, +/// so we throttle their repaints to avoid busy-looping while still +/// processing viewport commands like `Visible(true)`. +/// See . +const INVISIBLE_WINDOW_REPAINT_INTERVAL: Duration = Duration::from_millis(100); + // ---------------------------------------------------------------------------- fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result> { #[cfg(target_os = "android")] @@ -177,23 +188,54 @@ impl WinitAppWrapper { fn check_redraw_requests(&mut self, event_loop: &ActiveEventLoop) { let now = Instant::now(); + let mut invisible_window_ids = Vec::new(); + self.windows_next_repaint_times .retain(|window_id, repaint_time| { if now < *repaint_time { return true; // not yet ready } - event_loop.set_control_flow(ControlFlow::Poll); - if let Some(window) = self.winit_app.window(*window_id) { - log::trace!("request_redraw for {window_id:?}"); - window.request_redraw(); + // On Windows, invisible windows don't receive RedrawRequested + // events, so pending viewport commands (e.g. Visible(true)) would + // never be processed. We collect these windows to paint them + // directly below. + // See: https://github.com/emilk/egui/issues/5229 + if is_invisible_or_minimized(&window) { + invisible_window_ids.push(*window_id); + } else { + log::trace!("request_redraw for {window_id:?}"); + event_loop.set_control_flow(ControlFlow::Poll); + window.request_redraw(); + } } else { log::trace!("No window found for {window_id:?}"); } false }); + // Paint invisible windows directly, since they won't receive + // RedrawRequested events on Windows. This ensures that viewport + // commands like Visible(true) are still processed. + for window_id in &invisible_window_ids { + let event_result = self.winit_app.run_ui_and_paint(event_loop, *window_id); + self.handle_event_result(event_loop, event_result); + } + + // Throttle any already-scheduled repaints for invisible windows + // to avoid busy-looping. If no repaint was requested by the app, + // the window will simply sleep. + // See: https://github.com/emilk/egui/issues/7776 + if !invisible_window_ids.is_empty() { + let next_paint = Instant::now() + INVISIBLE_WINDOW_REPAINT_INTERVAL; + for window_id in &invisible_window_ids { + self.windows_next_repaint_times + .entry(*window_id) + .and_modify(|t| *t = (*t).min(next_paint)); + } + } + let next_repaint_time = self.windows_next_repaint_times.values().min().copied(); if let Some(next_repaint_time) = next_repaint_time { event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time)); @@ -270,6 +312,16 @@ impl ApplicationHandler for WinitAppWrapper { if let Some(window_id) = self.winit_app.window_id_from_viewport_id(viewport_id) { + // Throttle repaints for invisible windows to prevent + // high CPU usage on Windows. + // See: https://github.com/emilk/egui/issues/7776 + let when = if let Some(window) = self.winit_app.window(window_id) + && is_invisible_or_minimized(&window) + { + when.max(Instant::now() + INVISIBLE_WINDOW_REPAINT_INTERVAL) + } else { + when + }; Ok(EventResult::RepaintAt(window_id, when)) } else { Ok(EventResult::Wait) @@ -347,6 +399,7 @@ fn run_and_exit(event_loop: EventLoop, winit_app: impl WinitApp) -> R pub fn run_glow( app_name: &str, mut native_options: epi::NativeOptions, + egui_ctx: Option, app_creator: epi::AppCreator<'_>, ) -> Result { use super::glow_integration::GlowWinitApp; @@ -354,13 +407,15 @@ pub fn run_glow( #[cfg(not(target_os = "ios"))] if native_options.run_and_return { return with_event_loop(native_options, |event_loop, native_options| { - let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator); + let glow_eframe = + GlowWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator); run_and_return(event_loop, glow_eframe) })?; } let event_loop = create_event_loop(&mut native_options)?; - let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator); + let glow_eframe = + GlowWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator); run_and_exit(event_loop, glow_eframe) } @@ -373,7 +428,7 @@ pub fn create_glow<'a>( ) -> impl ApplicationHandler + 'a { use super::glow_integration::GlowWinitApp; - let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator); + let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, None, app_creator); WinitAppWrapper::new(glow_eframe, true) } @@ -383,6 +438,7 @@ pub fn create_glow<'a>( pub fn run_wgpu( app_name: &str, mut native_options: epi::NativeOptions, + egui_ctx: Option, app_creator: epi::AppCreator<'_>, ) -> Result { use super::wgpu_integration::WgpuWinitApp; @@ -390,13 +446,15 @@ pub fn run_wgpu( #[cfg(not(target_os = "ios"))] if native_options.run_and_return { return with_event_loop(native_options, |event_loop, native_options| { - let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator); + let wgpu_eframe = + WgpuWinitApp::new(event_loop, app_name, native_options, egui_ctx, app_creator); run_and_return(event_loop, wgpu_eframe) })?; } let event_loop = create_event_loop(&mut native_options)?; - let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator); + let wgpu_eframe = + WgpuWinitApp::new(&event_loop, app_name, native_options, egui_ctx, app_creator); run_and_exit(event_loop, wgpu_eframe) } @@ -409,7 +467,7 @@ pub fn create_wgpu<'a>( ) -> impl ApplicationHandler + 'a { use super::wgpu_integration::WgpuWinitApp; - let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator); + let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, None, app_creator); WinitAppWrapper::new(wgpu_eframe, true) } diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 9d6283808..161c3f84b 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -27,7 +27,10 @@ use winit_integration::UserEvent; use crate::{ App, AppCreator, CreationContext, NativeOptions, Result, Storage, - native::{epi_integration::EpiIntegration, winit_integration::EventResult}, + native::{ + epi_integration::EpiIntegration, + winit_integration::{EventResult, is_invisible_or_minimized}, + }, }; use super::{epi_integration, event_loop_context, winit_integration, winit_integration::WinitApp}; @@ -45,6 +48,10 @@ pub struct WgpuWinitApp<'app> { /// Set when we are actually up and running. running: Option>, + + /// An optional pre-existing egui context. If `Some`, it is used instead of + /// creating a new one via [`winit_integration::create_egui_context`]. Taken during initialization. + egui_ctx: Option, } /// State that is initialized when the application is first starts running via @@ -102,6 +109,7 @@ impl<'app> WgpuWinitApp<'app> { event_loop: &EventLoop, app_name: &str, native_options: NativeOptions, + egui_ctx: Option, app_creator: AppCreator<'app>, ) -> Self { profiling::function_scope!(); @@ -118,6 +126,7 @@ impl<'app> WgpuWinitApp<'app> { native_options, running: None, app_creator: Some(app_creator), + egui_ctx, } } @@ -184,9 +193,17 @@ impl<'app> WgpuWinitApp<'app> { builder: ViewportBuilder, ) -> crate::Result<&mut WgpuWinitRunning<'app>> { profiling::function_scope!(); + // Inject the display handle into the wgpu setup so that wgpu can create + // surfaces on platforms that require it (e.g. GLES on Wayland). + let mut wgpu_options = self.native_options.wgpu_options.clone(); + if let egui_wgpu::WgpuSetup::CreateNew(ref mut create_new) = wgpu_options.wgpu_setup + && create_new.display_handle.is_none() + { + create_new.display_handle = Some(Box::new(event_loop.owned_display_handle())); + } let mut painter = pollster::block_on(egui_wgpu::winit::Painter::new( egui_ctx.clone(), - self.native_options.wgpu_options.clone(), + wgpu_options, self.native_options.viewport.transparent.unwrap_or(false), egui_wgpu::RendererOptions { msaa_samples: self.native_options.multisampling as _, @@ -417,7 +434,10 @@ impl WinitApp for WgpuWinitApp<'_> { .unwrap_or(&self.app_name), ) }; - let egui_ctx = winit_integration::create_egui_context(storage.as_deref()); + let egui_ctx = self + .egui_ctx + .take() + .unwrap_or_else(|| winit_integration::create_egui_context(storage.as_deref())); let (window, builder) = create_window( &egui_ctx, event_loop, @@ -711,6 +731,7 @@ impl WgpuWinitRunning<'_> { &clipped_primitives, &textures_delta, screenshot_commands, + window, ); for action in viewport.actions_requested.drain(..) { @@ -770,10 +791,12 @@ impl WgpuWinitRunning<'_> { integration.maybe_autosave(app.as_mut(), window.map(|w| w.as_ref())); if let Some(window) = window - && window.is_minimized() == Some(true) + && is_invisible_or_minimized(window) { // On Mac, a minimized Window uses up all CPU: // https://github.com/emilk/egui/issues/325 + // On Windows, an invisible window also uses up all CPU: + // https://github.com/emilk/egui/issues/7776 profiling::scope!("minimized_sleep"); std::thread::sleep(std::time::Duration::from_millis(10)); } @@ -1098,6 +1121,7 @@ fn render_immediate_viewport( &clipped_primitives, &textures_delta, vec![], + window, ); egui_winit.handle_platform_output(window, platform_output); diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 012c22f8e..b4ec62c09 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -9,6 +9,14 @@ use egui::ViewportId; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; +/// Returns `true` if the window is invisible or minimized. +/// +/// These windows don't receive `RedrawRequested` events on Windows, +/// so they need special handling to keep processing viewport commands. +pub fn is_invisible_or_minimized(window: &Window) -> bool { + window.is_visible() == Some(false) || window.is_minimized() == Some(true) +} + /// Create an egui context, restoring it from storage if possible. pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Context { profiling::function_scope!(); diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index b90b8a5e1..c15e78d68 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -284,9 +284,6 @@ impl AppRunner { self.app.logic(ui.ctx(), &mut self.frame); if is_visible { - #[expect(deprecated)] - self.app.update(ui.ctx(), &mut self.frame); - self.app.ui(ui, &mut self.frame); } }); @@ -393,7 +390,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. diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index d77444563..e24e99fee 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -1114,16 +1114,16 @@ fn get_display_size(resize_observer_entries: &js_sys::Array) -> Result<(u32, u32 } else if JsValue::from_str("contentBoxSize").js_in(entry.as_ref()) { let content_box_size = entry.content_box_size(); let idx0 = content_box_size.at(0); - if !idx0.is_undefined() { - let size: web_sys::ResizeObserverSize = idx0.dyn_into()?; - width = size.inline_size(); - height = size.block_size(); - } else { + if idx0.is_undefined() { // legacy let size = JsValue::clone(content_box_size.as_ref()); let size: web_sys::ResizeObserverSize = size.dyn_into()?; width = size.inline_size(); height = size.block_size(); + } else { + let size: web_sys::ResizeObserverSize = idx0.dyn_into()?; + width = size.inline_size(); + height = size.block_size(); } if DEBUG_RESIZE { log::info!("contentBoxSize {width}x{height}"); diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 1e54d7a84..dc743ec49 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -38,6 +38,7 @@ mod web_painter_wgpu; pub use backend::*; use egui::Theme; +use js_sys::Object; use wasm_bindgen::prelude::*; use web_sys::{Document, MediaQueryList, Node}; @@ -177,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() @@ -370,5 +369,5 @@ pub fn percent_decode(s: &str) -> String { /// Are we running inside the Safari browser? pub fn is_safari_browser() -> bool { - web_sys::window().is_some_and(|window| window.has_own_property(&JsValue::from("safari"))) + web_sys::window().is_some_and(|window| Object::has_own(&window, &JsValue::from("safari"))) } diff --git a/crates/eframe/src/web/storage.rs b/crates/eframe/src/web/storage.rs index 170798dc6..baa3ad83b 100644 --- a/crates/eframe/src/web/storage.rs +++ b/crates/eframe/src/web/storage.rs @@ -9,7 +9,19 @@ pub fn local_storage_get(key: &str) -> Option { /// Write data to local storage. pub fn local_storage_set(key: &str, value: &str) { - local_storage().map(|storage| storage.set_item(key, value)); + match local_storage() { + Some(storage) => { + if let Err(err) = storage.set_item(key, value) { + log::warn!( + "local_storage_set failed: key={key}, err={}", + crate::web::string_from_js_value(&err) + ); + } + } + None => { + log::warn!("local_storage unavailable"); + } + } } #[cfg(feature = "persistence")] diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index ac917329f..20b904244 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -56,8 +56,13 @@ impl TextAgent { let input = input.clone(); move |event: web_sys::InputEvent, runner: &mut AppRunner| { let text = input.value(); - // Fix android virtual keyboard Gboard - // This removes the virtual keyboard's suggestion. + // Workaround for an Android Gboard issue: after typing a word, + // the user has to delete invisible characters (whose count + // matches the length of the current suggestion) before actual + // characters are deleted, unless the focus has been reset. + // + // this issue appears to have been fixed in Gboard sometime + // between versions 14.7.09 and 17.0.12. if !event.is_composing() { input.blur().ok(); input.focus().ok(); @@ -75,11 +80,7 @@ impl TextAgent { }; let on_composition_start = { - let input = input.clone(); move |_: web_sys::CompositionEvent, runner: &mut AppRunner| { - input.set_value(""); - let event = egui::Event::Ime(egui::ImeEvent::Enabled); - runner.input.raw.events.push(event); // Repaint moves the text agent into place, // see `move_to` in `AppRunner::handle_platform_output`. runner.needs_repaint.repaint_asap(); @@ -136,6 +137,12 @@ impl TextAgent { let Some(ime) = ime else { return Ok(()) }; + if ime.should_interrupt_composition { + // no-op for now: currently, the text agent is sizeless, so any + // click shifts focus to the canvas, which naturally interrupts the + // composition. + } + let mut canvas_rect = super::canvas_content_rect(canvas); // Fix for safari with virtual keyboard flapping position if is_mobile_safari() { diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index 470fa40d3..c9b846d50 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -31,8 +31,13 @@ impl WebPainterGlow { #[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] // For wasm let gl = std::sync::Arc::new(gl); - let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering) - .map_err(|err| format!("Error starting glow painter: {err}"))?; + let painter = egui_glow::Painter::new( + gl, + shader_prefix, + options.glow_options.shader_version, + options.dithering, + ) + .map_err(|err| format!("Error starting glow painter: {err}"))?; Ok(Self { canvas, diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index f7adb8fbb..8735530aa 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -15,13 +15,32 @@ pub(crate) struct WebPainterWgpu { surface: wgpu::Surface<'static>, surface_configuration: wgpu::SurfaceConfiguration, render_state: Option, - on_surface_error: Arc SurfaceErrorAction>, + on_surface_status: Arc SurfaceErrorAction>, depth_stencil_format: Option, depth_texture_view: Option, screen_capture_state: Option, capture_tx: CaptureSender, capture_rx: CaptureReceiver, ctx: egui::Context, + needs_reconfigure: bool, +} + +/// Owned web display handle that is `Send + Sync`. +/// +/// `DisplayHandle` from `raw-window-handle` is `!Send`/`!Sync` because the enum +/// contains platform variants with raw pointers. On web the handle is always empty, +/// so this wrapper is safe. +#[cfg(target_arch = "wasm32")] +#[derive(Clone, Debug)] +struct WebDisplay; + +#[cfg(target_arch = "wasm32")] +impl egui_wgpu::wgpu::rwh::HasDisplayHandle for WebDisplay { + fn display_handle( + &self, + ) -> Result, egui_wgpu::wgpu::rwh::HandleError> { + Ok(egui_wgpu::wgpu::rwh::DisplayHandle::web()) + } } impl WebPainterWgpu { @@ -63,7 +82,17 @@ impl WebPainterWgpu { ) -> Result { log::debug!("Creating wgpu painter"); - let instance = options.wgpu_options.wgpu_setup.new_instance().await; + // Inject the display handle into the wgpu setup so that wgpu can create surfaces on WebGL. + let mut wgpu_options = options.wgpu_options.clone(); + if let egui_wgpu::WgpuSetup::CreateNew(ref mut create_new) = wgpu_options.wgpu_setup + && create_new.display_handle.is_none() + { + // Force WebGL, useful for quick & dirty testing: + //create_new.instance_descriptor.backends = wgpu::Backends::GL; + create_new.display_handle = Some(Box::new(WebDisplay)); + } + + let instance = wgpu_options.wgpu_setup.new_instance().await; let surface = instance .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .map_err(|err| format!("failed to create wgpu surface: {err}"))?; @@ -71,7 +100,7 @@ impl WebPainterWgpu { let depth_stencil_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0); let render_state = RenderState::create( - &options.wgpu_options, + &wgpu_options, &instance, Some(&surface), egui_wgpu::RendererOptions { @@ -89,7 +118,7 @@ impl WebPainterWgpu { let surface_configuration = wgpu::SurfaceConfiguration { format: render_state.target_format, - present_mode: options.wgpu_options.present_mode, + present_mode: wgpu_options.surface.present_mode, view_formats: vec![render_state.target_format], ..default_configuration }; @@ -105,11 +134,12 @@ impl WebPainterWgpu { surface_configuration, depth_stencil_format, depth_texture_view: None, - on_surface_error: Arc::clone(&options.wgpu_options.on_surface_error) as _, + on_surface_status: Arc::clone(&wgpu_options.on_surface_status) as _, screen_capture_state: None, capture_tx, capture_rx, ctx, + needs_reconfigure: false, }) } } @@ -195,18 +225,28 @@ impl WebPainter for WebPainterWgpu { ); } + if self.needs_reconfigure { + self.surface + .configure(&render_state.device, &self.surface_configuration); + self.needs_reconfigure = false; + } + let output_frame = match self.surface.get_current_texture() { - Ok(frame) => frame, - Err(err) => match (*self.on_surface_error)(err) { - SurfaceErrorAction::RecreateSurface => { - self.surface - .configure(&render_state.device, &self.surface_configuration); - return Ok(()); + wgpu::CurrentSurfaceTexture::Success(frame) => frame, + wgpu::CurrentSurfaceTexture::Suboptimal(frame) => { + self.needs_reconfigure = true; + frame + } + other => { + match (*self.on_surface_status)(&other) { + SurfaceErrorAction::RecreateSurface => { + self.surface + .configure(&render_state.device, &self.surface_configuration); + } + SurfaceErrorAction::SkipFrame => {} } - SurfaceErrorAction::SkipFrame => { - return Ok(()); - } - }, + return Ok(()); + } }; { diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index cef640184..2eda803fc 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,29 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.2 - 2026-05-04 +* Update to wgpu 29.0.1 [#8073](https://github.com/emilk/egui/pull/8073) by [@emilk](https://github.com/emilk) +* Warn if using a software rasterizer [#8101](https://github.com/emilk/egui/pull/8101) by [@emilk](https://github.com/emilk) + + +## 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 diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index c514e0a49..0b081e134 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -15,7 +15,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/egui-wgpu" categories = ["gui", "game-development"] keywords = ["wgpu", "egui", "gui", "gamedev"] -include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "**/*.wgsl", "Cargo.toml"] +include = ["../../LICENSE-APACHE", "../../LICENSE-MIT", "**/*.rs", "**/*.wgsl", "Cargo.toml"] [lints] workspace = true @@ -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"] diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 46becf8f7..3e95f7885 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -24,7 +24,10 @@ mod renderer; mod setup; pub use renderer::*; -pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting}; +pub use setup::{ + EguiDisplayHandle, NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, + WgpuSetupExisting, +}; /// Helpers for capturing screenshots of the UI. #[cfg(feature = "capture")] @@ -61,6 +64,43 @@ pub enum WgpuError { HandleError(#[from] ::winit::raw_window_handle::HandleError), } +/// Runtime-mutable subset of [`WgpuConfiguration`]. +/// +/// Edit any field to have the surface reconfigured on the next paint. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct SurfaceConfig { + /// Present mode used for the primary surface. + pub present_mode: wgpu::PresentMode, + + /// Desired maximum number of frames that the presentation engine should queue in advance. + /// + /// Use `1` for low-latency, and `2` for high-throughput. + /// + /// See [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`] for details. + /// + /// `None` => Let `wgpu` pick a default (currently `2`). + pub desired_maximum_frame_latency: Option, +} + +impl SurfaceConfig { + /// Good default for GUIs with very little (or no) extra GPU work. + pub const LOW_LATENCY: Self = Self { + present_mode: wgpu::PresentMode::AutoVsync, + desired_maximum_frame_latency: if cfg!(target_os = "ios") { + None // The default is good on iOS, while `Some(1)` cuts FPS in half + } else { + Some(1) // Low-latency by default. + }, + }; + + /// Good default for GUIs with a lot of extra GPU work, + /// or that want to prioritize smoothness over latency. + pub const HIGH_THROUGHPUT: Self = Self { + present_mode: wgpu::PresentMode::AutoVsync, + desired_maximum_frame_latency: Some(2), // High-throughput. + }; +} + /// Access to the render state for egui. #[derive(Clone)] pub struct RenderState { @@ -85,6 +125,11 @@ pub struct RenderState { /// Egui renderer responsible for drawing the UI. pub renderer: Arc>, + + /// Runtime-mutable subset of the wgpu configuration. + /// + /// Update this to have the surface reconfigured on the next paint. + pub surface_config: SurfaceConfig, } async fn request_adapter( @@ -135,29 +180,12 @@ async fn request_adapter( } })?; - if cfg!(target_arch = "wasm32") { - log::debug!( - "Picked wgpu adapter: {}", - adapter_info_summary(&adapter.get_info()) + if 1 < available_adapters.len() { + log::info!( + "There are {} available wgpu adapters: {}", + available_adapters.len(), + describe_adapters(available_adapters) ); - } else { - // native: - if available_adapters.len() == 1 { - log::debug!( - "Picked the only available wgpu adapter: {}", - adapter_info_summary(&adapter.get_info()) - ); - } else { - log::info!( - "There were {} available wgpu adapters: {}", - available_adapters.len(), - describe_adapters(available_adapters) - ); - log::debug!( - "Picked wgpu adapter: {}", - adapter_info_summary(&adapter.get_info()) - ); - } } Ok(adapter) @@ -191,6 +219,7 @@ impl RenderState { let (adapter, device, queue) = match config.wgpu_setup.clone() { WgpuSetup::CreateNew(WgpuSetupCreateNew { instance_descriptor: _, + display_handle: _, power_preference, native_adapter_selector: _native_adapter_selector, device_descriptor, @@ -232,6 +261,8 @@ impl RenderState { }) => (adapter, device, queue), }; + log_adapter_info(&adapter.get_info()); + let surface_formats = { profiling::scope!("get_capabilities"); compatible_surface.map_or_else( @@ -254,6 +285,7 @@ impl RenderState { queue, target_format, renderer: Arc::new(RwLock::new(renderer)), + surface_config: config.surface, }) } } @@ -272,7 +304,7 @@ fn describe_adapters(adapters: &[wgpu::Adapter]) -> String { } } -/// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`] +/// Specifies which action should be taken as consequence of a surface error. pub enum SurfaceErrorAction { /// Do nothing and skip the current frame. SkipFrame, @@ -284,23 +316,24 @@ pub enum SurfaceErrorAction { /// Configuration for using wgpu with eframe or the egui-wgpu winit feature. #[derive(Clone)] pub struct WgpuConfiguration { - /// Present mode used for the primary surface. - pub present_mode: wgpu::PresentMode, - - /// Desired maximum number of frames that the presentation engine should queue in advance. + /// Runtime-mutable configuration for the surface (present mode, frame latency). /// - /// Use `1` for low-latency, and `2` for high-throughput. - /// - /// See [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`] for details. - /// - /// `None` = `wgpu` default. - pub desired_maximum_frame_latency: Option, + /// These are the fields exposed via [`RenderState::surface_config`] for live + /// reconfiguration at runtime. + pub surface: SurfaceConfig, /// How to create the wgpu adapter & device pub wgpu_setup: WgpuSetup, - /// Callback for surface errors. - pub on_surface_error: Arc SurfaceErrorAction + Send + Sync>, + /// Callback for surface status changes. + /// + /// Called with the [`wgpu::CurrentSurfaceTexture`] result whenever acquiring a frame + /// does not return [`wgpu::CurrentSurfaceTexture::Success`]. For + /// [`wgpu::CurrentSurfaceTexture::Suboptimal`], egui uses the frame as-is and + /// defers surface reconfiguration to the next frame — the callback is not invoked + /// in that case either. + pub on_surface_status: + Arc SurfaceErrorAction + Send + Sync>, } #[test] @@ -312,36 +345,49 @@ fn wgpu_config_impl_send_sync() { impl std::fmt::Debug for WgpuConfiguration { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let Self { - present_mode, - desired_maximum_frame_latency, + surface, wgpu_setup, - on_surface_error: _, + on_surface_status: _, } = self; f.debug_struct("WgpuConfiguration") - .field("present_mode", &present_mode) - .field( - "desired_maximum_frame_latency", - &desired_maximum_frame_latency, - ) + .field("surface", &surface) .field("wgpu_setup", &wgpu_setup) .finish_non_exhaustive() } } +impl WgpuConfiguration { + #[inline] + pub fn with_surface_config(mut self, surface_config: SurfaceConfig) -> Self { + self.surface = surface_config; + self + } +} + impl Default for WgpuConfiguration { fn default() -> Self { Self { - present_mode: wgpu::PresentMode::AutoVsync, - desired_maximum_frame_latency: None, - wgpu_setup: Default::default(), - on_surface_error: Arc::new(|err| { - if err == wgpu::SurfaceError::Outdated { - // This error occurs when the app is minimized on Windows. - // Silently return here to prevent spamming the console with: - // "The underlying surface has changed, and therefore the swap chain must be updated" - } else { - log::warn!("Dropped frame with error: {err}"); + surface: SurfaceConfig::HIGH_THROUGHPUT, + + // No display handle available at this point — callers should replace this with + // `WgpuSetup::from_display_handle(...)` before creating the instance if one is available. + wgpu_setup: WgpuSetup::without_display_handle(), + on_surface_status: Arc::new(|status| { + match status { + wgpu::CurrentSurfaceTexture::Outdated => { + // This error occurs when the app is minimized on Windows. + // Silently return here to prevent spamming the console with: + // "The underlying surface has changed, and therefore the swap chain must be updated" + } + wgpu::CurrentSurfaceTexture::Occluded => { + // This error occurs when the application is occluded (e.g. minimized or behind another window). + log::debug!("Dropped frame with error: {status:?}"); + } + _ => { + log::warn!("Dropped frame with error: {status:?}"); + } } + SurfaceErrorAction::SkipFrame }), } @@ -385,6 +431,18 @@ pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option String { let wgpu::AdapterInfo { @@ -406,37 +464,52 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String { // > name: "Apple M1 Pro", device_type: IntegratedGpu, backend: Metal, driver: "", driver_info: "" // > name: "ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)", device_type: IntegratedGpu, backend: Gl, driver: "", driver_info: "" + use std::fmt::Write as _; + let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}"); if !name.is_empty() { - summary += &format!(", name: {name:?}"); + write!(summary, ", name: {name:?}").ok(); } if !driver.is_empty() { - summary += &format!(", driver: {driver:?}"); + write!(summary, ", driver: {driver:?}").ok(); } if !driver_info.is_empty() { - summary += &format!(", driver_info: {driver_info:?}"); + write!(summary, ", driver_info: {driver_info:?}").ok(); } if *vendor != 0 { #[cfg(not(target_arch = "wasm32"))] { - summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor)); + write!( + summary, + ", vendor: {} (0x{vendor:04X})", + parse_vendor_id(*vendor) + ) + .ok(); } #[cfg(target_arch = "wasm32")] { - summary += &format!(", vendor: 0x{vendor:04X}"); + write!(summary, ", vendor: 0x{vendor:04X}").ok(); } } if *device != 0 { - summary += &format!(", device: 0x{device:02X}"); + write!(summary, ", device: 0x{device:02X}").ok(); } if !device_pci_bus_id.is_empty() { - summary += &format!(", pci_bus_id: {device_pci_bus_id:?}"); + write!(summary, ", pci_bus_id: {device_pci_bus_id:?}").ok(); } if *subgroup_min_size != 0 || *subgroup_max_size != 0 { - summary += &format!(", subgroup_size: {subgroup_min_size}..={subgroup_max_size}"); + write!( + summary, + ", subgroup_size: {subgroup_min_size}..={subgroup_max_size}" + ) + .ok(); } - summary += &format!(", transient_saves_memory: {transient_saves_memory}"); + write!( + summary, + ", transient_saves_memory: {transient_saves_memory}" + ) + .ok(); summary } diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index c37802448..e55f7581a 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -1,5 +1,3 @@ -#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps - use std::{borrow::Cow, num::NonZeroU64, ops::Range}; use ahash::HashMap; @@ -352,7 +350,10 @@ impl Renderer { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("egui_pipeline_layout"), - bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout], + bind_group_layouts: &[ + Some(&uniform_bind_group_layout), + Some(&texture_bind_group_layout), + ], immediate_size: 0, }); @@ -360,8 +361,8 @@ impl Renderer { .depth_stencil_format .map(|format| wgpu::DepthStencilState { format, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::Always, + depth_write_enabled: Some(false), + depth_compare: Some(wgpu::CompareFunction::Always), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }); @@ -469,6 +470,9 @@ impl Renderer { /// The render pass internally keeps all referenced resources alive as long as necessary. /// The only consequence of `forget_lifetime` is that any operation on the parent encoder will cause a runtime error /// instead of a compile time error. + /// + /// # Panic + /// Always ensure that [`Renderer::update_buffers`] has been called otherwise calling [`Renderer::render`] will panic! pub fn render( &self, render_pass: &mut wgpu::RenderPass<'static>, @@ -513,8 +517,12 @@ impl Renderer { // Skip rendering zero-sized clip areas. if let Primitive::Mesh(_) = primitive { // If this is a mesh, we need to advance the index and vertex buffer iterators: - index_buffer_slices.next().unwrap(); - vertex_buffer_slices.next().unwrap(); + index_buffer_slices + .next() + .expect("You must call .update_buffers() before .render()"); + vertex_buffer_slices + .next() + .expect("You must call .update_buffers() before .render()"); } continue; } @@ -524,8 +532,12 @@ impl Renderer { match primitive { Primitive::Mesh(mesh) => { - let index_buffer_slice = index_buffer_slices.next().unwrap(); - let vertex_buffer_slice = vertex_buffer_slices.next().unwrap(); + let index_buffer_slice = index_buffer_slices + .next() + .expect("You must call .update_buffers() before .render()"); + let vertex_buffer_slice = vertex_buffer_slices + .next() + .expect("You must call .update_buffers() before .render()"); if let Some(Texture { bind_group, .. }) = self.textures.get(&mesh.texture_id) { render_pass.set_bind_group(1, bind_group, &[]); @@ -951,6 +963,7 @@ impl Renderer { let index_buffer_staging = queue.write_buffer_with( &self.index_buffer.buffer, 0, + #[expect(clippy::unwrap_used)] // Checked above NonZeroU64::new(required_index_buffer_size).unwrap(), ); @@ -968,7 +981,8 @@ impl Renderer { Primitive::Mesh(mesh) => { let size = mesh.indices.len() * std::mem::size_of::(); let slice = index_offset..(size + index_offset); - index_buffer_staging[slice.clone()] + index_buffer_staging + .slice(slice.clone()) .copy_from_slice(bytemuck::cast_slice(&mesh.indices)); self.index_buffer.slices.push(slice); index_offset += size; @@ -994,6 +1008,7 @@ impl Renderer { let vertex_buffer_staging = queue.write_buffer_with( &self.vertex_buffer.buffer, 0, + #[expect(clippy::unwrap_used)] // Checked above NonZeroU64::new(required_vertex_buffer_size).unwrap(), ); @@ -1011,7 +1026,8 @@ impl Renderer { Primitive::Mesh(mesh) => { let size = mesh.vertices.len() * std::mem::size_of::(); let slice = vertex_offset..(size + vertex_offset); - vertex_buffer_staging[slice.clone()] + vertex_buffer_staging + .slice(slice.clone()) .copy_from_slice(bytemuck::cast_slice(&mesh.vertices)); self.vertex_buffer.slices.push(slice); vertex_offset += size; diff --git a/crates/egui-wgpu/src/setup.rs b/crates/egui-wgpu/src/setup.rs index 0c3cb8c39..cf8b652c2 100644 --- a/crates/egui-wgpu/src/setup.rs +++ b/crates/egui-wgpu/src/setup.rs @@ -1,5 +1,43 @@ use std::sync::Arc; +/// A cloneable display handle for use with [`wgpu::InstanceDescriptor`]. +/// +/// [`wgpu::InstanceDescriptor`] stores its display handle as a non-cloneable +/// `Box`. This trait wraps it so it can be cloned +/// alongside the rest of the egui wgpu configuration. +/// +/// Automatically implemented for all types that satisfy the bounds +/// (including [`winit::event_loop::OwnedDisplayHandle`]). +pub trait EguiDisplayHandle: + wgpu::rwh::HasDisplayHandle + std::fmt::Debug + Send + Sync + 'static +{ + /// Clone into a `Box` for [`wgpu::InstanceDescriptor::display`]. + fn clone_for_wgpu(&self) -> Box; + + /// Clone into a new `Box`. + fn clone_display_handle(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Self { + // We need to deref here, otherwise this causes infinite recursion stack overflow. + (**self).clone_display_handle() + } +} + +impl EguiDisplayHandle for T +where + T: wgpu::rwh::HasDisplayHandle + Clone + std::fmt::Debug + Send + Sync + 'static, +{ + fn clone_for_wgpu(&self) -> Box { + Box::new(self.clone()) + } + + fn clone_display_handle(&self) -> Box { + Box::new(self.clone()) + } +} + #[derive(Clone)] pub enum WgpuSetup { /// Construct a wgpu setup using some predefined settings & heuristics. @@ -22,9 +60,19 @@ pub enum WgpuSetup { Existing(WgpuSetupExisting), } -impl Default for WgpuSetup { - fn default() -> Self { - Self::CreateNew(WgpuSetupCreateNew::default()) +impl WgpuSetup { + /// Creates a new [`WgpuSetup::CreateNew`] with the given display handle. + /// + /// See [`WgpuSetupCreateNew::from_display_handle`] for details. + pub fn from_display_handle(display_handle: impl EguiDisplayHandle) -> Self { + Self::CreateNew(WgpuSetupCreateNew::from_display_handle(display_handle)) + } + + /// Creates a new [`WgpuSetup::CreateNew`] without a display handle. + /// + /// See [`WgpuSetupCreateNew::without_display_handle`] for details. + pub fn without_display_handle() -> Self { + Self::CreateNew(WgpuSetupCreateNew::without_display_handle()) } } @@ -65,8 +113,18 @@ impl WgpuSetup { } log::debug!("Creating wgpu instance with backends {backends:?}"); - wgpu::util::new_instance_with_webgpu_detection(&create_new.instance_descriptor) - .await + let desc = &create_new.instance_descriptor; + let descriptor = wgpu::InstanceDescriptor { + backends: desc.backends, + flags: desc.flags, + backend_options: desc.backend_options.clone(), + memory_budget_thresholds: desc.memory_budget_thresholds, + display: create_new + .display_handle + .as_ref() + .map(|handle| handle.clone_for_wgpu()), + }; + wgpu::util::new_instance_with_webgpu_detection(descriptor).await } Self::Existing(existing) => existing.instance.clone(), } @@ -98,18 +156,35 @@ pub type NativeAdapterSelectorMethod = Arc< /// Configuration for creating a new wgpu setup. /// /// Used for [`WgpuSetup::CreateNew`]. +/// +/// Prefer [`Self::from_display_handle`] when you have a display handle available. +/// Most platforms work without one, but some (e.g. Wayland with GLES, or WebGL) +/// require it, so providing one ensures maximum compatibility. +/// With winit, pass [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle). +/// +/// Note: The display handle is stored in [`Self::display_handle`] rather than in +/// [`Self::instance_descriptor`] so the config can be cloned +/// ([`wgpu::InstanceDescriptor`] is not `Clone`). It is injected at instance creation time. pub struct WgpuSetupCreateNew { - /// Instance descriptor for creating a wgpu instance. + /// Descriptor for the wgpu instance. /// - /// The most important field is [`wgpu::InstanceDescriptor::backends`], which - /// controls which backends are supported (wgpu will pick one of these). - /// If you only want to support WebGL (and not WebGPU), - /// you can set this to [`wgpu::Backends::GL`]. - /// By default on web, WebGPU will be used if available. - /// WebGL will only be used as a fallback, - /// and only if you have enabled the `webgl` feature of crate `wgpu`. + /// Leave [`wgpu::InstanceDescriptor::display`] as `None` — use [`Self::display_handle`] + /// instead (injected at instance creation time). + /// + /// The most important field is [`wgpu::InstanceDescriptor::backends`], which controls + /// which backends are supported (wgpu will pick one of these). For example, set it to + /// [`wgpu::Backends::GL`] to use only WebGL. By default on web, WebGPU is preferred + /// with WebGL as a fallback (requires the `webgl` feature of crate `wgpu`). pub instance_descriptor: wgpu::InstanceDescriptor, + /// Display handle passed to wgpu at instance creation time. + /// + /// Required on some platforms (e.g. Wayland with GLES, WebGL); optional elsewhere. + /// With winit, use [`winit::event_loop::OwnedDisplayHandle`]. + /// + /// `eframe` 's winit & web integrations will attempt to fill the display handle automatically if it is left empty. + pub display_handle: Option>, + /// Power preference for the adapter if [`Self::native_adapter_selector`] is not set or targeting web. pub power_preference: wgpu::PowerPreference, @@ -128,32 +203,37 @@ pub struct WgpuSetupCreateNew { Arc wgpu::DeviceDescriptor<'static> + Send + Sync>, } -impl Clone for WgpuSetupCreateNew { - fn clone(&self) -> Self { +impl WgpuSetupCreateNew { + /// Creates a new configuration with the given display handle. + /// + /// This is the recommended constructor. Most platforms (Windows, macOS/iOS, Android, web) + /// work fine without a display handle, but some (e.g. Wayland on Linux with GLES) require + /// one. Providing it unconditionally ensures your app works everywhere. + /// + /// If you don't have a display handle available, use [`Self::without_display_handle`] + /// instead — it will still work on the majority of platforms. + /// + /// With winit, pass [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle). + pub fn from_display_handle(display_handle: impl EguiDisplayHandle) -> Self { Self { - instance_descriptor: self.instance_descriptor.clone(), - power_preference: self.power_preference, - native_adapter_selector: self.native_adapter_selector.clone(), - device_descriptor: Arc::clone(&self.device_descriptor), + display_handle: Some(Box::new(display_handle)), + ..Self::without_display_handle() } } -} -impl std::fmt::Debug for WgpuSetupCreateNew { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("WgpuSetupCreateNew") - .field("instance_descriptor", &self.instance_descriptor) - .field("power_preference", &self.power_preference) - .field( - "native_adapter_selector", - &self.native_adapter_selector.is_some(), - ) - .finish() - } -} - -impl Default for WgpuSetupCreateNew { - fn default() -> Self { + /// Creates a new configuration without a display handle. + /// + /// A display handle is not required for headless operation (offscreen rendering, tests, + /// compute-only workloads). It also isn't needed on most platforms even when presenting + /// to a window — only some configurations (e.g. Wayland on Linux with GLES) require one. + /// + /// If you do have a display handle available, prefer [`Self::from_display_handle`] for + /// maximum compatibility. + /// + /// With winit you can obtain one via [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle). + /// + /// `eframe` 's winit & web integrations will attempt to fill the display handle automatically if it is left empty. + pub fn without_display_handle() -> Self { Self { instance_descriptor: wgpu::InstanceDescriptor { // Add GL backend, primarily because WebGPU is not stable enough yet. @@ -163,8 +243,11 @@ impl Default for WgpuSetupCreateNew { flags: wgpu::InstanceFlags::from_build_config().with_env(), backend_options: wgpu::BackendOptions::from_env_or_default(), memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(), + display: None, }, + display_handle: None, + power_preference: wgpu::PowerPreference::from_env() .unwrap_or(wgpu::PowerPreference::HighPerformance), @@ -192,6 +275,46 @@ impl Default for WgpuSetupCreateNew { } } +impl Clone for WgpuSetupCreateNew { + fn clone(&self) -> Self { + let desc = &self.instance_descriptor; + Self { + instance_descriptor: wgpu::InstanceDescriptor { + backends: desc.backends, + flags: desc.flags, + backend_options: desc.backend_options.clone(), + memory_budget_thresholds: desc.memory_budget_thresholds, + display: None, + }, + display_handle: self.display_handle.clone(), + power_preference: self.power_preference, + native_adapter_selector: self.native_adapter_selector.clone(), + device_descriptor: Arc::clone(&self.device_descriptor), + } + } +} + +impl std::fmt::Debug for WgpuSetupCreateNew { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + instance_descriptor, + display_handle, + power_preference, + native_adapter_selector, + device_descriptor: _, + } = self; + f.debug_struct("WgpuSetupCreateNew") + .field("instance_descriptor", instance_descriptor) + .field("display_handle", display_handle) + .field("power_preference", power_preference) + .field( + "native_adapter_selector", + &native_adapter_selector.is_some(), + ) + .finish_non_exhaustive() + } +} + /// Configuration for using an existing wgpu setup. /// /// Used for [`WgpuSetup::Existing`]. diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 5fb8d123a..8b5a91904 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -3,7 +3,7 @@ #![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps #![expect(unsafe_code)] -use crate::{RenderState, SurfaceErrorAction, WgpuConfiguration, renderer}; +use crate::{RenderState, SurfaceConfig, SurfaceErrorAction, WgpuConfiguration, renderer}; use crate::{ RendererOptions, capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}, @@ -17,6 +17,7 @@ struct SurfaceState { width: u32, height: u32, resizing: bool, + needs_reconfigure: bool, } /// Everything you need to paint egui with [`wgpu`] on [`winit`]. @@ -26,7 +27,7 @@ struct SurfaceState { /// NOTE: all egui viewports share the same painter. pub struct Painter { context: Context, - configuration: WgpuConfiguration, + config: WgpuConfiguration, options: RendererOptions, support_transparent_backbuffer: bool, screen_capture_state: Option, @@ -57,16 +58,16 @@ impl Painter { /// associated. pub async fn new( context: Context, - configuration: WgpuConfiguration, + config: WgpuConfiguration, support_transparent_backbuffer: bool, options: RendererOptions, ) -> Self { let (capture_tx, capture_rx) = capture_channel(); - let instance = configuration.wgpu_setup.new_instance().await; + let instance = config.wgpu_setup.new_instance().await; Self { context, - configuration, + config, options, support_transparent_backbuffer, screen_capture_state: None, @@ -93,17 +94,22 @@ impl Painter { fn configure_surface( surface_state: &SurfaceState, render_state: &RenderState, - config: &WgpuConfiguration, + config: &SurfaceConfig, ) { profiling::function_scope!(); + let SurfaceConfig { + present_mode, + desired_maximum_frame_latency, + } = *config; + let width = surface_state.width; let height = surface_state.height; let mut surf_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: render_state.target_format, - present_mode: config.present_mode, + present_mode, alpha_mode: surface_state.alpha_mode, view_formats: vec![render_state.target_format], ..surface_state @@ -112,7 +118,7 @@ impl Painter { .expect("The surface isn't supported by this adapter") }; - if let Some(desired_maximum_frame_latency) = config.desired_maximum_frame_latency { + if let Some(desired_maximum_frame_latency) = desired_maximum_frame_latency { surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency; } @@ -200,13 +206,9 @@ impl Painter { let render_state = if let Some(render_state) = &self.render_state { render_state } else { - let render_state = RenderState::create( - &self.configuration, - &self.instance, - Some(&surface), - self.options, - ) - .await?; + let render_state = + RenderState::create(&self.config, &self.instance, Some(&surface), self.options) + .await?; self.render_state.get_or_insert(render_state) }; let alpha_mode = if self.support_transparent_backbuffer { @@ -234,6 +236,7 @@ impl Painter { height: size.height, alpha_mode, resizing: false, + needs_reconfigure: false, }, ); let Some(width) = NonZeroU32::new(size.width) else { @@ -276,7 +279,7 @@ impl Painter { surface_state.width = width; surface_state.height = height; - Self::configure_surface(surface_state, render_state, &self.configuration); + Self::configure_surface(surface_state, render_state, &self.config.surface); if let Some(depth_format) = self.options.depth_stencil_format { self.depth_texture_view.insert( @@ -368,12 +371,12 @@ impl Painter { hal_surface .render_layer() .lock() - .set_presents_with_transaction(resizing); + .setPresentsWithTransaction(resizing); Self::configure_surface( state, self.render_state.as_ref().unwrap(), - &self.configuration, + &self.config.surface, ); } } @@ -409,6 +412,7 @@ impl Painter { /// and the captures captured screenshot if it was requested. /// /// If `capture_data` isn't empty, a screenshot will be captured. + #[expect(clippy::too_many_arguments)] pub fn paint_and_update_textures( &mut self, viewport_id: ViewportId, @@ -417,6 +421,7 @@ impl Painter { clipped_primitives: &[epaint::ClippedPrimitive], textures_delta: &epaint::textures::TexturesDelta, capture_data: Vec, + window: &winit::window::Window, ) -> f32 { profiling::function_scope!(); @@ -445,6 +450,20 @@ impl Painter { let capture = !capture_data.is_empty(); let mut vsync_sec = 0.0; + // Apply any runtime changes requested via `RenderState::surface_config`. + // We diff against the already-applied values in `self.config.surface` + // and, if anything differs, mark every surface as needing reconfiguration so + // the existing `needs_reconfigure` pathway below picks them up. + if let Some(render_state) = self.render_state.as_ref() + && render_state.surface_config != self.config.surface + { + self.config.surface = render_state.surface_config; + #[expect(clippy::iter_over_hash_type)] + for surface in self.surfaces.values_mut() { + surface.needs_reconfigure = true; + } + } + let Some(render_state) = self.render_state.as_mut() else { return vsync_sec; }; @@ -454,7 +473,7 @@ impl Painter { commands_submitted: false, }; - let Some(surface_state) = self.surfaces.get(&viewport_id) else { + let Some(surface_state) = self.surfaces.get_mut(&viewport_id) else { return vsync_sec; }; @@ -491,6 +510,11 @@ impl Painter { ) }; + if surface_state.needs_reconfigure { + Self::configure_surface(surface_state, render_state, &self.config.surface); + surface_state.needs_reconfigure = false; + } + let output_frame = { profiling::scope!("get_current_texture"); // This is what vsync-waiting happens on my Mac. @@ -501,16 +525,20 @@ impl Painter { }; let output_frame = match output_frame { - Ok(frame) => frame, - Err(err) => match (*self.configuration.on_surface_error)(err) { - SurfaceErrorAction::RecreateSurface => { - Self::configure_surface(surface_state, render_state, &self.configuration); - return vsync_sec; + wgpu::CurrentSurfaceTexture::Success(frame) => frame, + wgpu::CurrentSurfaceTexture::Suboptimal(frame) => { + surface_state.needs_reconfigure = true; + frame + } + other => { + match (*self.config.on_surface_status)(&other) { + SurfaceErrorAction::RecreateSurface => { + Self::configure_surface(surface_state, render_state, &self.config.surface); + } + SurfaceErrorAction::SkipFrame => {} } - SurfaceErrorAction::SkipFrame => { - return vsync_sec; - } - }, + return vsync_sec; + } }; let mut capture_buffer = None; @@ -643,6 +671,8 @@ impl Painter { ); } + window.pre_present_notify(); + { profiling::scope!("present"); // wgpu doesn't document where vsync can happen. Maybe here? diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index f88a8c84a..15c7c2d52 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/CHANGELOG.md @@ -5,6 +5,21 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.34.2 - 2026-05-04 +Nothing new + + +## 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 diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index d1b2ab220..7fea2341b 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/egui-winit" categories = ["gui", "game-development"] keywords = ["winit", "egui", "gui", "gamedev"] -include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +include = ["../../LICENSE-APACHE", "../../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [lints] workspace = true @@ -20,6 +20,9 @@ workspace = true all-features = true rustdoc-args = ["--generate-link-to-definition"] +[package.metadata.cargo-shear] +ignored = ["wayland-cursor"] # TODO(emilk): remove when we update winit + [features] default = ["clipboard", "links", "wayland", "winit/default", "x11"] @@ -81,6 +84,7 @@ objc2.workspace = true objc2-foundation = { workspace = true, features = ["std", "NSThread"] } objc2-ui-kit = { workspace = true, features = [ "std", + "objc2-core-foundation", "UIApplication", "UIGeometry", "UIResponder", diff --git a/crates/egui-winit/src/clipboard.rs b/crates/egui-winit/src/clipboard.rs index 75d0469ec..2410c3ee6 100644 --- a/crates/egui-winit/src/clipboard.rs +++ b/crates/egui-winit/src/clipboard.rs @@ -65,13 +65,12 @@ impl Clipboard { feature = "smithay-clipboard" ))] if let Some(clipboard) = &mut self.smithay { - return match clipboard.load() { - Ok(text) => Some(text), + match clipboard.load() { + Ok(text) => return Some(text), Err(err) => { log::error!("smithay paste error: {err}"); - None } - }; + } } #[cfg(all( diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 234a9989b..bbb22f0ab 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -9,6 +9,9 @@ #![expect(clippy::manual_range_contains)] +#[cfg(target_os = "windows")] +use std::collections::HashSet; + #[cfg(feature = "accesskit")] pub use accesskit_winit; pub use egui; @@ -98,14 +101,17 @@ pub struct State { /// Only one touch will be interpreted as pointer at any time. pointer_touch_id: Option, - /// track ime state - has_sent_ime_enabled: bool, - #[cfg(feature = "accesskit")] pub accesskit: Option, allow_ime: bool, ime_rect_px: Option, + + /// Used by [`State::try_on_ime_processed_keyboard_input`] to track key + /// release events that should be filtered out. See comments in that method + /// for details. + #[cfg(target_os = "windows")] + pressed_processed_physical_keys: HashSet, } impl State { @@ -126,9 +132,11 @@ impl State { }; let mut slf = Self { - egui_ctx, viewport_id, - start_time: web_time::Instant::now(), + start_time: web_time::Instant::now() + .checked_sub(web_time::Duration::from_secs_f64(egui_ctx.time())) + .unwrap_or_else(web_time::Instant::now), + egui_ctx, egui_input, pointer_pos_in_points: None, any_pointer_button_down: false, @@ -141,13 +149,13 @@ impl State { simulate_touch_screen: false, pointer_touch_id: None, - has_sent_ime_enabled: false, - #[cfg(feature = "accesskit")] accesskit: None, allow_ime: false, ime_rect_px: None, + #[cfg(target_os = "windows")] + pressed_processed_physical_keys: HashSet::new(), }; slf.egui_input @@ -364,25 +372,33 @@ impl State { is_synthetic, .. } => { - // Winit generates fake "synthetic" KeyboardInput events when the focus - // is changed to the window, or away from it. Synthetic key presses - // represent no real key presses and should be ignored. - // See https://github.com/rust-windowing/winit/issues/3543 if *is_synthetic && event.state == ElementState::Pressed { + // Winit generates fake "synthetic" KeyboardInput events when the focus + // is changed to the window, or away from it. Synthetic key presses + // represent no real key presses and should be ignored. + // See https://github.com/rust-windowing/winit/issues/3543 EventResponse { repaint: true, consumed: false, } } else { - self.on_keyboard_input(event); + let egui_wants_keyboard_input = self.egui_ctx.egui_wants_keyboard_input(); - // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. - let consumed = self.egui_ctx.egui_wants_keyboard_input() - || event.logical_key - == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab); - EventResponse { - repaint: true, - consumed, + if let Some(response) = + self.try_on_ime_processed_keyboard_input(event, egui_wants_keyboard_input) + { + response + } else { + self.on_keyboard_input(event); + + // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. + let consumed = egui_wants_keyboard_input + || event.logical_key + == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab); + EventResponse { + repaint: true, + consumed, + } } } } @@ -526,6 +542,91 @@ impl State { } } + #[cfg(not(target_os = "windows"))] + #[expect(clippy::unused_self, clippy::needless_pass_by_ref_mut)] + #[inline(always)] + fn try_on_ime_processed_keyboard_input( + &mut self, + _event: &winit::event::KeyEvent, + _egui_wants_keyboard_input: bool, + ) -> Option { + // `KeyboardInput` events processed by the IME are not emitted by + // `winit` on non-Windows platforms, so we don't need to do anything + // here. + + None + } + + #[cfg(target_os = "windows")] + #[inline(always)] + fn try_on_ime_processed_keyboard_input( + &mut self, + event: &winit::event::KeyEvent, + egui_wants_keyboard_input: bool, + ) -> Option { + if !self.allow_ime { + None + } else if event.logical_key == winit::keyboard::NamedKey::Process { + // On Windows, the current version of `winit` (0.30.12) has a bug + // where `KeyboardInput` events processed by the IME are still + // emitted. [^1] + // + // As a workaround, we detect these events by checking whether their + // `logical_key` is `winit::keyboard::NamedKey::Process`, and filter + // them out to keep behavior consistent with other platforms. + // + // `winit::keyboard::NamedKey::Process` is not documented in + // `winit`. Reading through its source code, we find that it is + // mapped from `VK_PROCESSKEY` on Windows [^2]. (On an unrelated + // note, Web is the only other platform that also uses it [^3].) + // According to Microsoft, ā€œthe IME sets the virtual key value + // to `VK_PROCESSKEY` after processing a key input messageā€ [^4]. + // See also [^5]. + // (I can't find a documentation page dedicated to this value.) + // + // TODO(umajho): Remove this workaround once the `winit` bug is fixed + // and we've updated to a version that includes the fix. NOTE: Don't + // forget to also remove the `pressed_processed_physical_keys` field + // and its related code. + // + // [^1]: https://github.com/rust-windowing/winit/issues/4508 + // [^2]: https://github.com/rust-windowing/winit/blob/e9809ef54b18499bb4f2cac945719ecc2a61061b/src/platform_impl/windows/keyboard_layout.rs#L946 + // [^3]: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values + // [^4]: https://learn.microsoft.com/en-us/windows/win32/api/imm/nf-imm-immgetvirtualkey#remarks + // [^5]: https://learn.microsoft.com/en-us/windows/win32/learnwin32/keyboard-input#character-messages + + self.pressed_processed_physical_keys + .insert(event.physical_key); + + Some(EventResponse { + repaint: false, + consumed: egui_wants_keyboard_input, + }) + } else if event.state == ElementState::Released + && self + .pressed_processed_physical_keys + .remove(&event.physical_key) + { + // Unlike key-presses, we can not tell whether a key-release event + // is processed by the IME or not by looking at its `logical_key`, + // because their `logical_key` is the original value (e.g. + // `winit::keyboard::Key::Character(…)`) rather than + // `winit::keyboard::Key::Named(winit::keyboard::NamedKey::Process)`. + // (See the screencast for Windows in [^1].) + // So we track the physical keys of processed key-presses and + // filter out the corresponding key-releases. + // + // [^1]: https://github.com/rust-windowing/winit/issues/4508 + + Some(EventResponse { + repaint: false, + consumed: egui_wants_keyboard_input, + }) + } else { + None + } + } + /// ## NOTE /// /// on Mac even Cmd-C is pressed during ime, a `c` is pushed to Preedit. @@ -585,17 +686,11 @@ impl State { // } match ime { - winit::event::Ime::Enabled => { - if cfg!(target_os = "linux") { - // This event means different things in X11 and Wayland, but we can just - // ignore it and enable IME on the preedit event. - // See - } else { - self.ime_event_enable(); - } - } - winit::event::Ime::Preedit(text, Some(_cursor)) => { - self.ime_event_enable(); + // [`winit::event::Ime::Enabled`] means different things in X11 and + // Wayland, but it doesn't matter to us. + // See + winit::event::Ime::Enabled | winit::event::Ime::Disabled => {} + winit::event::Ime::Preedit(text, _) => { self.egui_input .events .push(egui::Event::Ime(egui::ImeEvent::Preedit(text.clone()))); @@ -604,53 +699,10 @@ impl State { self.egui_input .events .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone()))); - self.ime_event_disable(); - } - winit::event::Ime::Disabled => { - self.ime_event_disable(); - } - winit::event::Ime::Preedit(_, None) => { - if cfg!(target_os = "macos") { - // On macOS, when the user presses backspace to delete the - // last character in an IME composition, `winit` only emits - // `winit::event::Ime::Preedit("", None)` without a - // preceding `winit::event::Ime::Preedit("", Some(0, 0))`. - // - // The current implementation of `egui::TextEdit` relies on - // receiving an `egui::ImeEvent::Preedit("")` to remove the - // last character in the composition in this case, so we - // emit it here. - // - // This is guarded to macOS-only, as applying it on other - // platforms is unnecessary and can cause undesired - // behavior. - // See: https://github.com/emilk/egui/pull/7973 - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Preedit(String::new()))); - } - - self.ime_event_disable(); } } } - pub fn ime_event_enable(&mut self) { - if !self.has_sent_ime_enabled { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Enabled)); - self.has_sent_ime_enabled = true; - } - } - - pub fn ime_event_disable(&mut self) { - self.egui_input - .events - .push(egui::Event::Ime(egui::ImeEvent::Disabled)); - self.has_sent_ime_enabled = false; - } - /// Returns `true` if the event was sent to egui. pub fn on_mouse_motion(&mut self, delta: (f64, f64)) -> bool { if !self.is_pointer_in_window() && !self.any_pointer_button_down { @@ -999,13 +1051,32 @@ impl State { self.set_cursor_icon(window, cursor_icon); let allow_ime = ime.is_some(); - if self.allow_ime != allow_ime { + let is_toggling_ime = self.allow_ime != allow_ime; + if is_toggling_ime { self.allow_ime = allow_ime; + #[cfg(target_os = "windows")] + if !self.allow_ime { + // Defensively clear the set to avoid unexpected behavior. + // + // We don't do the same in `ime_event_disable` because the key + // release events for IME confirmation keys arrive after + // `winit::event::Ime::Disabled`. + self.pressed_processed_physical_keys.clear(); + } + profiling::scope!("set_ime_allowed"); window.set_ime_allowed(allow_ime); } if let Some(ime) = ime { + if !is_toggling_ime && ime.should_interrupt_composition { + // TODO(umajho): use a more proper way to interrupt composition + // if `winit` provides one in the future. + + window.set_ime_allowed(false); + window.set_ime_allowed(true); + } + let pixels_per_point = pixels_per_point(&self.egui_ctx, window); let ime_rect_px = pixels_per_point * ime.rect; if self.ime_rect_px != Some(ime_rect_px) diff --git a/crates/egui-winit/src/safe_area.rs b/crates/egui-winit/src/safe_area.rs index 5f4a9f9cf..378f44a94 100644 --- a/crates/egui-winit/src/safe_area.rs +++ b/crates/egui-winit/src/safe_area.rs @@ -36,8 +36,8 @@ mod ios { | UISceneActivationState::ForegroundInactive ) { - // Safe to cast, the class kind was checked above - let window_scene = Retained::cast::(scene.clone()); + // SAFETY: class kind was checked above with `isKindOfClass` + let window_scene = Retained::cast_unchecked::(scene.clone()); if let Some(window) = window_scene.keyWindow() { let insets = window.safeAreaInsets(); return SafeAreaInsets(MarginF32 { diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 764d2401e..3a22bf529 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -11,7 +11,7 @@ readme = "../../README.md" repository = "https://github.com/emilk/egui" categories = ["gui", "game-development"] keywords = ["gui", "imgui", "immediate", "portable", "gamedev"] -include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] +include = ["../../LICENSE-APACHE", "../../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [lints] workspace = true diff --git a/crates/egui/src/atomics/atom.rs b/crates/egui/src/atomics/atom.rs index 4db7f12a9..6f289fcfb 100644 --- a/crates/egui/src/atomics/atom.rs +++ b/crates/egui/src/atomics/atom.rs @@ -4,7 +4,21 @@ use epaint::text::TextWrapMode; /// A low-level ui building block. /// -/// Implements [`From`] for [`String`], [`str`], [`crate::Image`] and much more for convenience. +/// This can be a piece of text, an image, or even a custom widget. +/// It can be decorated with various layout hints, such as `grow`, `shrink`, `align`, and more. +/// +/// `Atom` implements [`From`] for [`String`], [`str`], [`crate::Image`] and much more for convenience. +/// +/// Many widgets take an `impl` [`crate::IntoAtoms`] parameter, +/// which allows you to easily create atoms from tuples of text, images, and other atoms: +/// ``` +/// # use egui::{Vec2, AtomExt, AtomKind, Atom, Image, Id}; +/// # egui::__run_test_ui(|ui| { +/// let image = egui::include_image!("../../../eframe/data/icon.png"); +/// ui.button((image, "Click me!")); +/// # }); +/// ``` +/// /// You can directly call the `atom_*` methods on anything that implements `Into`. /// ``` /// # use egui::{Image, emath::Vec2}; diff --git a/crates/egui/src/atomics/atom_layout.rs b/crates/egui/src/atomics/atom_layout.rs index b78f23536..7894273f3 100644 --- a/crates/egui/src/atomics/atom_layout.rs +++ b/crates/egui/src/atomics/atom_layout.rs @@ -203,7 +203,7 @@ impl<'a> AtomLayout<'a> { // If the TextWrapMode is not Extend, ensure there is some item marked as `shrink`. // If none is found, mark the first text item as `shrink`. if wrap_mode != TextWrapMode::Extend { - let any_shrink = atoms.iter().any(|a| a.shrink); + let any_shrink = atoms.any_shrink(); if !any_shrink { let first_text = atoms .iter_mut() @@ -318,8 +318,9 @@ impl<'a> AtomLayout<'a> { let (_, rect) = ui.allocate_space(frame_size); let mut response = ui.interact(rect, id, sense); - response.intrinsic_size = - Some((Vec2::new(intrinsic_width, intrinsic_height) + margin.sum()).at_least(min_size)); + response.set_intrinsic_size( + (Vec2::new(intrinsic_width, intrinsic_height) + margin.sum()).at_least(min_size), + ); AllocatedAtomLayout { sized_atoms: sized_items, @@ -520,6 +521,20 @@ impl AtomLayoutResponse { } } +impl Deref for AtomLayoutResponse { + type Target = Response; + + fn deref(&self) -> &Self::Target { + &self.response + } +} + +impl DerefMut for AtomLayoutResponse { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.response + } +} + impl Widget for AtomLayout<'_> { fn ui(self, ui: &mut Ui) -> Response { self.show(ui).response diff --git a/crates/egui/src/atomics/atoms.rs b/crates/egui/src/atomics/atoms.rs index fb04ee2dd..761db8eb6 100644 --- a/crates/egui/src/atomics/atoms.rs +++ b/crates/egui/src/atomics/atoms.rs @@ -8,6 +8,15 @@ use std::ops::{Deref, DerefMut}; pub(crate) const ATOMS_SMALL_VEC_SIZE: usize = 2; /// A list of [`Atom`]s. +/// +/// Many widgets take an `impl` [`IntoAtoms`] parameter, +/// which allows you to easily create atoms from tuples of text, images, and other atoms: +/// ``` +/// # use egui::{AtomExt, AtomKind, Atom, Image, Id, Vec2}; +/// # egui::__run_test_ui(|ui| { +/// let image = egui::include_image!("../../../eframe/data/icon.png"); +/// ui.button((image, "Click me!")); +/// # }); #[derive(Clone, Debug, Default)] pub struct Atoms<'a>(SmallVec<[Atom<'a>; ATOMS_SMALL_VEC_SIZE]>); @@ -69,6 +78,11 @@ impl<'a> Atoms<'a> { string } + /// Do any of the atoms have shrink set to `true`? + pub fn any_shrink(&self) -> bool { + self.iter().any(|a| a.shrink) + } + pub fn iter_kinds(&self) -> impl Iterator> { self.0.iter().map(|atom| &atom.kind) } @@ -187,6 +201,16 @@ where } /// Trait for turning a tuple of [`Atom`]s into [`Atoms`]. +/// +/// Many widgets take an `impl` [`IntoAtoms`] parameter, +/// which allows you to easily create atoms from tuples of text, images, and other atoms: +/// ``` +/// # use egui::{AtomExt, AtomKind, Atom, Image, Id, Vec2}; +/// # egui::__run_test_ui(|ui| { +/// let image = egui::include_image!("../../../eframe/data/icon.png"); +/// ui.button((image, "Click me!")); +/// # }); +/// ``` pub trait IntoAtoms<'a> { fn collect(self, atoms: &mut Atoms<'a>); diff --git a/crates/egui/src/callstack.rs b/crates/egui/src/callstack.rs index fef9b2166..b1239db01 100644 --- a/crates/egui/src/callstack.rs +++ b/crates/egui/src/callstack.rs @@ -1,3 +1,5 @@ +use std::fmt::Write as _; + #[derive(Clone)] struct Frame { /// `_main` is usually as the deepest depth. @@ -23,7 +25,7 @@ pub fn capture() -> String { if let Some(file_and_line) = &mut file_and_line && let Some(line_nr) = symbol.lineno() { - file_and_line.push_str(&format!(":{line_nr}")); + write!(file_and_line, ":{line_nr}").ok(); } let file_and_line = file_and_line.unwrap_or_default(); @@ -130,12 +132,14 @@ pub fn capture() -> String { if frame.depth + 1 < last_depth || last_depth + 1 < frame.depth { // Show that some frames were elided - formatted.push_str(&format!("{:widest_depth$} …\n", "")); + writeln!(formatted, "{:widest_depth$} …", "").ok(); } - formatted.push_str(&format!( - "{depth:widest_depth$}: {file_and_line:widest_file_line$} {name}\n" - )); + writeln!( + formatted, + "{depth:widest_depth$}: {file_and_line:widest_file_line$} {name}" + ) + .ok(); last_depth = frame.depth; } diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 4333cf73a..09488058d 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -280,7 +280,7 @@ impl Area { self } - /// Constrains this area to [`Context::screen_rect`]? + /// Constrains this area to [`Context::content_rect`]? /// /// Default: `true`. #[inline] @@ -291,7 +291,7 @@ impl Area { /// Constrain the movement of the window to the given rectangle. /// - /// For instance: `.constrain_to(ctx.screen_rect())`. + /// For instance: `.constrain_to(ctx.content_rect())`. #[inline] pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { self.constrain = true; @@ -516,6 +516,7 @@ impl Area { let move_response = ctx.create_widget( WidgetRect { id: interact_id, + parent_id: id, layer_id, rect: state.rect(), interact_rect: state.rect().intersect(constrain_rect), diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index aca8ab138..de3581288 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -451,15 +451,6 @@ impl CollapsingHeader { self } - /// Explicitly set the source of the [`Id`] of this widget, instead of using title label. - /// This is useful if the title label is dynamic or not unique. - #[deprecated = "Renamed id_salt"] - #[inline] - pub fn id_source(mut self, id_salt: impl Hash) -> Self { - self.id_salt = Id::new(id_salt); - self - } - /// If you set this to `false`, the [`CollapsingHeader`] will be grayed out and un-clickable. /// /// This is a convenience for [`Ui::disable`]. diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index c4097f803..adbda583c 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -94,12 +94,6 @@ impl ComboBox { } } - /// Without label. - #[deprecated = "Renamed from_id_salt"] - pub fn from_id_source(id_salt: impl std::hash::Hash) -> Self { - Self::from_id_salt(id_salt) - } - /// Set the outer width of the button and menu. /// /// Default is [`Spacing::combo_width`]. diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index d556f827e..5bbce626d 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -174,11 +174,6 @@ impl Frame { Self::NONE } - #[deprecated = "Use `Frame::NONE` or `Frame::new()` instead."] - pub const fn none() -> Self { - Self::NONE - } - /// For when you want to group a few widgets together within a frame. pub fn group(style: &Style) -> Self { Self::new() @@ -283,16 +278,6 @@ impl Frame { self } - /// The rounding of the _outer_ corner of the [`Self::stroke`] - /// (or, if there is no stroke, the outer corner of [`Self::fill`]). - /// - /// In other words, this is the corner radius of the _widget rect_. - #[inline] - #[deprecated = "Renamed to `corner_radius`"] - pub fn rounding(self, corner_radius: impl Into) -> Self { - self.corner_radius(corner_radius) - } - /// Margin outside the painted frame. /// /// Similar to what is called `margin` in CSS. diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index 1bd5954c8..d4c95b298 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -10,7 +10,7 @@ use crate::style::StyleModifier; use crate::{ - Button, Color32, Context, Frame, Id, InnerResponse, IntoAtoms, Layout, Popup, + Button, Color32, Context, Frame, Id, InnerResponse, IntoAtoms, Layout, PointerButton, Popup, PopupCloseBehavior, Response, Style, Ui, UiBuilder, UiKind, UiStack, UiStackInfo, Widget as _, }; use emath::{Align, RectAlign, Vec2, vec2}; @@ -197,7 +197,7 @@ impl MenuState { /// Horizontal menu bar where you can add [`MenuButton`]s. /// -/// The menu bar goes well in a [`crate::TopBottomPanel::top`], +/// The menu bar goes well in a [`crate::Panel::top`], /// but can also be placed in a [`crate::Window`]. /// In the latter case you may want to wrap it in [`Frame`]. /// @@ -219,9 +219,6 @@ pub struct MenuBar { style: StyleModifier, } -#[deprecated = "Renamed to `egui::MenuBar`"] -pub type Bar = MenuBar; - impl Default for MenuBar { fn default() -> Self { Self { @@ -458,6 +455,7 @@ impl SubMenu { let is_any_open = open_item.is_some(); let mut is_open = open_item == Some(id); + let was_open = is_open; let mut set_open = None; // We expand the button rect so there is no empty space where no menu is shown @@ -470,9 +468,21 @@ impl SubMenu { // But since we check if no other menu is open, nothing should be able to cover the button let is_hovered = hover_pos.is_some_and(|pos| button_rect.contains(pos)); + // `clicked` includes keyboard and accessibility click actions. + // We want Enter/Space to toggle an already open submenu, while pointer clicks should keep + // the submenu open (for touch and pointer interactions). + let clicked = button_response.clicked(); + let clicked_by_pointer = button_response.clicked_by(PointerButton::Primary); + let clicked_by_keyboard_or_access = clicked && !clicked_by_pointer; + + if ui.is_enabled() && is_open && clicked_by_keyboard_or_access { + set_open = Some(false); + is_open = false; + } + // The clicked handler is there for accessibility (keyboard navigation) let should_open = - ui.is_enabled() && (button_response.clicked() || (is_hovered && !is_any_open)); + ui.is_enabled() && ((!was_open && clicked) || (is_hovered && !is_any_open)); if should_open { set_open = Some(true); is_open = true; diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index a8f3306e9..d828ce0f8 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -9,7 +9,6 @@ mod combo_box; pub mod frame; pub mod menu; pub mod modal; -pub mod old_popup; pub mod panel; mod popup; pub(crate) mod resize; @@ -26,7 +25,6 @@ pub use { combo_box::*, frame::Frame, modal::{Modal, ModalResponse}, - old_popup::*, panel::*, popup::*, resize::Resize, diff --git a/crates/egui/src/containers/old_popup.rs b/crates/egui/src/containers/old_popup.rs deleted file mode 100644 index f8e7cc900..000000000 --- a/crates/egui/src/containers/old_popup.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! Old and deprecated API for popups. Use [`Popup`] instead. -#![expect(deprecated)] - -use crate::containers::tooltip::Tooltip; -use crate::{ - Align, Context, Id, LayerId, Layout, Popup, PopupAnchor, PopupCloseBehavior, Pos2, Rect, - Response, Ui, Widget as _, WidgetText, -}; -use emath::RectAlign; -// ---------------------------------------------------------------------------- - -/// Show a tooltip at the current pointer position (if any). -/// -/// Most of the time it is easier to use [`Response::on_hover_ui`]. -/// -/// See also [`show_tooltip_text`]. -/// -/// Returns `None` if the tooltip could not be placed. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// # #[expect(deprecated)] -/// if ui.ui_contains_pointer() { -/// egui::show_tooltip(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), |ui| { -/// ui.label("Helpful text"); -/// }); -/// } -/// # }); -/// ``` -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - show_tooltip_at_pointer(ctx, parent_layer, widget_id, add_contents) -} - -/// Show a tooltip at the current pointer position (if any). -/// -/// Most of the time it is easier to use [`Response::on_hover_ui`]. -/// -/// See also [`show_tooltip_text`]. -/// -/// Returns `None` if the tooltip could not be placed. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// if ui.ui_contains_pointer() { -/// egui::show_tooltip_at_pointer(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), |ui| { -/// ui.label("Helpful text"); -/// }); -/// } -/// # }); -/// ``` -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip_at_pointer( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - Tooltip::always_open(ctx.clone(), parent_layer, widget_id, PopupAnchor::Pointer) - .gap(12.0) - .show(add_contents) - .map(|response| response.inner) -} - -/// Show a tooltip under the given area. -/// -/// If the tooltip does not fit under the area, it tries to place it above it instead. -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip_for( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - widget_rect: &Rect, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - Tooltip::always_open(ctx.clone(), parent_layer, widget_id, *widget_rect) - .show(add_contents) - .map(|response| response.inner) -} - -/// Show a tooltip at the given position. -/// -/// Returns `None` if the tooltip could not be placed. -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip_at( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - suggested_position: Pos2, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - Tooltip::always_open(ctx.clone(), parent_layer, widget_id, suggested_position) - .show(add_contents) - .map(|response| response.inner) -} - -/// Show some text at the current pointer position (if any). -/// -/// Most of the time it is easier to use [`Response::on_hover_text`]. -/// -/// See also [`show_tooltip`]. -/// -/// Returns `None` if the tooltip could not be placed. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// if ui.ui_contains_pointer() { -/// egui::show_tooltip_text(ui.ctx(), ui.layer_id(), egui::Id::new("my_tooltip"), "Helpful text"); -/// } -/// # }); -/// ``` -#[deprecated = "Use `egui::Tooltip` instead"] -pub fn show_tooltip_text( - ctx: &Context, - parent_layer: LayerId, - widget_id: Id, - text: impl Into, -) -> Option<()> { - show_tooltip(ctx, parent_layer, widget_id, |ui| { - crate::widgets::Label::new(text).ui(ui); - }) -} - -/// Was this tooltip visible last frame? -#[deprecated = "Use `Tooltip::was_tooltip_open_last_frame` instead"] -pub fn was_tooltip_open_last_frame(ctx: &Context, widget_id: Id) -> bool { - Tooltip::was_tooltip_open_last_frame(ctx, widget_id) -} - -/// Indicate whether a popup will be shown above or below the box. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum AboveOrBelow { - Above, - Below, -} - -/// Helper for [`popup_above_or_below_widget`]. -#[deprecated = "Use `egui::Popup` instead"] -pub fn popup_below_widget( - ui: &Ui, - popup_id: Id, - widget_response: &Response, - close_behavior: PopupCloseBehavior, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - popup_above_or_below_widget( - ui, - popup_id, - widget_response, - AboveOrBelow::Below, - close_behavior, - add_contents, - ) -} - -/// Shows a popup above or below another widget. -/// -/// Useful for drop-down menus (combo boxes) or suggestion menus under text fields. -/// -/// The opened popup will have a minimum width matching its parent. -/// -/// You must open the popup with [`crate::Memory::open_popup`] or [`crate::Memory::toggle_popup`]. -/// -/// Returns `None` if the popup is not open. -/// -/// ``` -/// # egui::__run_test_ui(|ui| { -/// let response = ui.button("Open popup"); -/// let popup_id = ui.make_persistent_id("my_unique_id"); -/// if response.clicked() { -/// ui.memory_mut(|mem| mem.toggle_popup(popup_id)); -/// } -/// let below = egui::AboveOrBelow::Below; -/// let close_on_click_outside = egui::PopupCloseBehavior::CloseOnClickOutside; -/// # #[expect(deprecated)] -/// egui::popup_above_or_below_widget(ui, popup_id, &response, below, close_on_click_outside, |ui| { -/// ui.set_min_width(200.0); // if you want to control the size -/// ui.label("Some more info, or things you can select:"); -/// ui.label("…"); -/// }); -/// # }); -/// ``` -#[deprecated = "Use `egui::Popup` instead"] -pub fn popup_above_or_below_widget( - _parent_ui: &Ui, - popup_id: Id, - widget_response: &Response, - above_or_below: AboveOrBelow, - close_behavior: PopupCloseBehavior, - add_contents: impl FnOnce(&mut Ui) -> R, -) -> Option { - let response = Popup::from_response(widget_response) - .layout(Layout::top_down_justified(Align::LEFT)) - .open_memory(None) - .close_behavior(close_behavior) - .id(popup_id) - .align(match above_or_below { - AboveOrBelow::Above => RectAlign::TOP_START, - AboveOrBelow::Below => RectAlign::BOTTOM_START, - }) - .width(widget_response.rect.width()) - .show(|ui| { - ui.set_min_width(ui.available_width()); - add_contents(ui) - })?; - Some(response.inner) -} diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index f2a4c3b67..d75a90bf4 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -18,9 +18,8 @@ use emath::{GuiRounding as _, Pos2}; use crate::{ - Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef, - Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetType, lerp, - vec2, + Align, Context, CursorIcon, Frame, Id, InnerResponse, Layout, NumExt as _, Rangef, Rect, Sense, + Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, lerp, vec2, }; fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 { @@ -451,59 +450,6 @@ impl Panel { } } -// Deprecated -impl Panel { - #[deprecated = "Renamed default_size"] - pub fn default_width(self, default_size: f32) -> Self { - self.default_size(default_size) - } - - #[deprecated = "Renamed min_size"] - pub fn min_width(self, min_size: f32) -> Self { - self.min_size(min_size) - } - - #[deprecated = "Renamed max_size"] - pub fn max_width(self, max_size: f32) -> Self { - self.max_size(max_size) - } - - #[deprecated = "Renamed size_range"] - pub fn width_range(self, size_range: impl Into) -> Self { - self.size_range(size_range) - } - - #[deprecated = "Renamed exact_size"] - pub fn exact_width(self, size: f32) -> Self { - self.exact_size(size) - } - - #[deprecated = "Renamed default_size"] - pub fn default_height(self, default_size: f32) -> Self { - self.default_size(default_size) - } - - #[deprecated = "Renamed min_size"] - pub fn min_height(self, min_size: f32) -> Self { - self.min_size(min_size) - } - - #[deprecated = "Renamed max_size"] - pub fn max_height(self, max_size: f32) -> Self { - self.max_size(max_size) - } - - #[deprecated = "Renamed size_range"] - pub fn height_range(self, size_range: impl Into) -> Self { - self.size_range(size_range) - } - - #[deprecated = "Renamed exact_size"] - pub fn exact_height(self, size: f32) -> Self { - self.exact_size(size) - } -} - // Public showing methods impl Panel { /// Show the panel inside a [`Ui`]. @@ -515,41 +461,6 @@ impl Panel { self.show_inside_dyn(ui, Box::new(add_contents)) } - /// Show the panel at the top level. - #[deprecated = "Use show_inside() instead"] - pub fn show( - self, - ctx: &Context, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - self.show_dyn(ctx, Box::new(add_contents)) - } - - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - #[deprecated = "Use show_animated_inside() instead"] - pub fn show_animated( - self, - ctx: &Context, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { - #![expect(deprecated)] - - let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); - - let animated_panel = self.get_animated_panel(ctx, is_expanded)?; - - if how_expanded < 1.0 { - // Show a fake panel in this in-between animation state: - animated_panel.show(ctx, |_ui| {}); - None - } else { - // Show the real panel: - Some(animated_panel.show(ctx, add_contents)) - } - } - /// Show the panel if `is_expanded` is `true`, /// otherwise don't show it, but with a nice animation between collapsed and expanded. pub fn show_animated_inside( @@ -561,7 +472,11 @@ impl Panel { let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded); // Get either the fake or the real panel to animate - let animated_panel = self.get_animated_panel(ui.ctx(), is_expanded)?; + let Some(animated_panel) = self.get_animated_panel(ui.ctx(), is_expanded) else { + // Make sure the ids of the next widgets are the same whether we show the panel or not: + ui.skip_ahead_auto_ids(1); + return None; + }; if how_expanded < 1.0 { // Show a fake panel in this in-between animation state: @@ -573,34 +488,6 @@ impl Panel { } } - /// Show either a collapsed or a expanded panel, with a nice animation between. - #[deprecated = "Use show_animated_between_inside() instead"] - pub fn show_animated_between( - ctx: &Context, - is_expanded: bool, - collapsed_panel: Self, - expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> Option> { - #![expect(deprecated)] - - let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); - - // Get either the fake or the real panel to animate - let animated_between_panel = - Self::get_animated_between_panel(ctx, is_expanded, collapsed_panel, expanded_panel); - - if 0.0 == how_expanded { - Some(animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded))) - } else if how_expanded < 1.0 { - // Show animation: - animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded)); - None - } else { - Some(animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded))) - } - } - /// Show either a collapsed or a expanded panel, with a nice animation between. pub fn show_animated_between_inside( ui: &mut Ui, @@ -752,59 +639,6 @@ impl Panel { inner_response } - /// Show the panel at the top level. - fn show_dyn<'c, R>( - self, - ctx: &Context, - add_contents: Box R + 'c>, - ) -> InnerResponse { - #![expect(deprecated)] - - let side = self.side; - let available_rect = ctx.available_rect(); - let mut panel_ui = Ui::new( - ctx.clone(), - self.id, - UiBuilder::new() - .layer_id(LayerId::background()) - .max_rect(available_rect), - ); - panel_ui.set_clip_rect(ctx.content_rect()); - panel_ui - .response() - .widget_info(|| WidgetInfo::new(WidgetType::Panel)); - - let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); - let rect = inner_response.response.rect; - - match side { - PanelSide::Vertical(side) => match side { - VerticalSide::Left => ctx.pass_state_mut(|state| { - state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max)); - }), - VerticalSide::Right => ctx.pass_state_mut(|state| { - state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)); - }), - }, - PanelSide::Horizontal(side) => match side { - HorizontalSide::Top => { - ctx.pass_state_mut(|state| { - state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max)); - }); - } - HorizontalSide::Bottom => { - ctx.pass_state_mut(|state| { - state.allocate_bottom_panel(Rect::from_min_max( - rect.min, - available_rect.max, - )); - }); - } - }, - } - inner_response - } - fn prepare_resizable_panel(&self, panel_sizer: &mut PanelSizer<'_>, ui: &Ui) { let resize_id = self.id.with("__resize"); let resize_response = ui.ctx().read_response(resize_id); @@ -1040,61 +874,9 @@ impl CentralPanel { response } - - /// Show the panel at the top level. - #[deprecated = "Use show_inside() instead"] - pub fn show( - self, - ctx: &Context, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - self.show_dyn(ctx, Box::new(add_contents)) - } - - /// Show the panel at the top level. - fn show_dyn<'c, R>( - self, - ctx: &Context, - add_contents: Box R + 'c>, - ) -> InnerResponse { - #![expect(deprecated)] - - let id = Id::new((ctx.viewport_id(), "central_panel")); - - let mut panel_ui = Ui::new( - ctx.clone(), - id, - UiBuilder::new() - .layer_id(LayerId::background()) - .max_rect(ctx.available_rect()), - ); - panel_ui.set_clip_rect(ctx.content_rect()); - - if false { - // TODO(emilk): @lucasmerlin shouldn't we enable this? - panel_ui - .response() - .widget_info(|| WidgetInfo::new(WidgetType::Panel)); - } - - let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); - - // Only inform ctx about what we actually used, so we can shrink the native window to fit. - ctx.pass_state_mut(|state| state.allocate_central_panel(inner_response.response.rect)); - - inner_response - } } fn clamp_to_range(x: f32, range: Rangef) -> f32 { let range = range.as_positive(); x.clamp(range.min, range.max) } - -// ---------------------------------------------------------------------------- - -#[deprecated = "Use Panel::left or Panel::right instead"] -pub type SidePanel = super::Panel; - -#[deprecated = "Use Panel::top or Panel::bottom instead"] -pub type TopBottomPanel = super::Panel; diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 0fb2a9f2a..cf78a6650 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -1,5 +1,3 @@ -#![expect(deprecated)] // This is a new, safe wrapper around the old `Memory::popup` API. - use std::iter::once; use emath::{Align, Pos2, Rect, RectAlign, Vec2, vec2}; @@ -87,7 +85,7 @@ pub enum PopupCloseBehavior { /// but in the popup's body CloseOnClickOutside, - /// Clicks will be ignored. Popup might be closed manually by calling [`crate::Memory::close_all_popups`] + /// Clicks will be ignored. Popup might be closed manually by calling [`Popup::close_all`] /// or by pressing the escape button IgnoreClicks, } @@ -666,10 +664,6 @@ impl Popup<'_> { } /// Open the given popup and close all others. - /// - /// If you are NOT using [`Popup::show`], you must - /// also call [`crate::Memory::keep_popup_open`] as long as - /// you're showing the popup. pub fn open_id(ctx: &Context, popup_id: Id) { ctx.memory_mut(|mem| mem.open_popup(popup_id)); } diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 7ff943b3f..8dcce6b20 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -69,13 +69,6 @@ impl Resize { self } - /// A source for the unique [`Id`], e.g. `.id_source("second_resize_area")` or `.id_source(loop_index)`. - #[inline] - #[deprecated = "Renamed id_salt"] - pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self { - self.id_salt(id_salt) - } - /// A source for the unique [`Id`], e.g. `.id_salt("second_resize_area")` or `.id_salt(loop_index)`. #[inline] pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self { diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 2616fb414..c0c29a9ab 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -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, @@ -423,13 +423,6 @@ impl ScrollArea { self } - /// A source for the unique [`Id`], e.g. `.id_source("second_scroll_area")` or `.id_source(loop_index)`. - #[inline] - #[deprecated = "Renamed id_salt"] - pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self { - self.id_salt(id_salt) - } - /// A source for the unique [`Id`], e.g. `.id_salt("second_scroll_area")` or `.id_salt(loop_index)`. #[inline] pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self { @@ -530,32 +523,6 @@ impl ScrollArea { /// This can be used, for example, to optionally freeze scrolling while the user /// is typing text in a [`crate::TextEdit`] widget contained within the scroll area. /// - /// This controls both scrolling directions. - #[deprecated = "Use `ScrollArea::scroll_source()"] - #[inline] - pub fn enable_scrolling(mut self, enable: bool) -> Self { - self.scroll_source = if enable { - ScrollSource::ALL - } else { - ScrollSource::NONE - }; - self - } - - /// Can the user drag the scroll area to scroll? - /// - /// This is useful for touch screens. - /// - /// If `true`, the [`ScrollArea`] will sense drags. - /// - /// Default: `true`. - #[deprecated = "Use `ScrollArea::scroll_source()"] - #[inline] - pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { - self.scroll_source.drag = drag_to_scroll; - self - } - /// What sources does the [`ScrollArea`] use for scrolling the contents. #[inline] pub fn scroll_source(mut self, scroll_source: ScrollSource) -> Self { @@ -1019,13 +986,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 +1475,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(ui: &Ui, scroll_output: &ScrollAreaOutput) { + 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], + )); + } +} diff --git a/crates/egui/src/containers/tooltip.rs b/crates/egui/src/containers/tooltip.rs index 78c5a726b..22c319569 100644 --- a/crates/egui/src/containers/tooltip.rs +++ b/crates/egui/src/containers/tooltip.rs @@ -16,24 +16,6 @@ pub struct Tooltip<'a> { } impl Tooltip<'_> { - /// Show a tooltip that is always open. - #[deprecated = "Use `Tooltip::always_open` instead."] - pub fn new( - parent_widget: Id, - ctx: Context, - anchor: impl Into, - parent_layer: LayerId, - ) -> Self { - Self { - popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer) - .kind(PopupKind::Tooltip) - .gap(4.0) - .sense(Sense::hover()), - parent_layer, - parent_widget, - } - } - /// Show a tooltip that is always open. pub fn always_open( ctx: Context, diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index c6b739589..9f25d6131 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -262,7 +262,7 @@ impl<'open> Window<'open> { self } - /// Constrains this window to [`Context::screen_rect`]. + /// Constrains this window to [`Context::content_rect`]. /// /// To change the area to constrain to, use [`Self::constrain_to`]. /// @@ -275,7 +275,7 @@ impl<'open> Window<'open> { /// Constrain the movement of the window to the given rectangle. /// - /// For instance: `.constrain_to(ctx.screen_rect())`. + /// For instance: `.constrain_to(ctx.content_rect())`. #[inline] pub fn constrain_to(mut self, constrain_rect: Rect) -> Self { self.area = self.area.constrain_to(constrain_rect); @@ -427,7 +427,7 @@ impl<'open> Window<'open> { /// Enable/disable scrolling on the window by dragging with the pointer. `true` by default. /// - /// See [`ScrollArea::drag_to_scroll`] for more. + /// See [`ScrollArea::scroll_source`] for more. #[inline] pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self { self.scroll = self.scroll.scroll_source(ScrollSource { @@ -673,7 +673,7 @@ impl Window<'_> { title_bar.ui( &mut area_content_ui, - &content_response, + content_response.as_ref(), open.as_deref_mut(), &mut collapsing, collapsible, @@ -962,6 +962,7 @@ fn do_resize_interaction( WidgetRect { layer_id, id, + parent_id: layer_id.id, rect, interact_rect: rect, sense: Sense::DRAG, // Don't use Sense::drag() since we don't want these to be focusable @@ -1255,7 +1256,7 @@ impl TitleBar { fn ui( self, ui: &mut Ui, - content_response: &Option, + content_response: Option<&Response>, open: Option<&mut bool>, collapsing: &mut CollapsingState, collapsible: bool, @@ -1299,7 +1300,7 @@ impl TitleBar { ui.visuals().text_color(), ); - if let Some(content_response) = &content_response { + if let Some(content_response) = content_response { // Paint separator between title and content: let content_rect = content_response.rect; if false { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 3b36150e4..d348517ef 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -300,7 +300,7 @@ impl RepaintCause { struct ViewportRepaintInfo { /// Monotonically increasing counter. /// - /// Incremented at the end of [`Context::run`]. + /// Incremented at the end of [`Context::run_ui`]. /// This can be smaller than [`Self::cumulative_pass_nr`], /// but never larger. cumulative_frame_nr: u64, @@ -463,7 +463,7 @@ impl ContextImpl { let content_rect = viewport.input.content_rect(); - viewport.this_pass.begin_pass(content_rect); + viewport.this_pass.begin_pass(); { let mut layers: Vec = viewport.prev_pass.widgets.layer_ids().collect(); @@ -697,8 +697,8 @@ impl ContextImpl { /// // Game loop: /// loop { /// let raw_input = egui::RawInput::default(); -/// let full_output = ctx.run(raw_input, |ctx| { -/// egui::CentralPanel::default().show(&ctx, |ui| { +/// let full_output = ctx.run_ui(raw_input, |ui| { +/// egui::CentralPanel::default().show_inside(ui, |ui| { /// ui.label("Hello world!"); /// if ui.button("Click me").clicked() { /// // take some action here @@ -780,9 +780,6 @@ impl Context { /// }); /// // handle full_output /// ``` - /// - /// ## See also - /// * [`Self::run`] #[must_use] pub fn run_ui(&self, new_input: RawInput, mut run_ui: impl FnMut(&mut Ui)) -> FullOutput { self.run_ui_dyn(new_input, &mut run_ui) @@ -791,60 +788,28 @@ impl Context { #[must_use] fn run_ui_dyn(&self, new_input: RawInput, run_ui: &mut dyn FnMut(&mut Ui)) -> FullOutput { let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); - #[expect(deprecated)] - self.run(new_input, |ctx| { - let mut top_ui = Ui::new( + self.run_dyn(new_input, &mut |ctx| { + let mut root_ui = Ui::new( ctx.clone(), Id::new((ctx.viewport_id(), "__top_ui")), UiBuilder::new() .layer_id(LayerId::background()) - .max_rect(ctx.available_rect()), + .max_rect(ctx.viewport_rect()), ); { - plugins.on_begin_pass(&mut top_ui); - run_ui(&mut top_ui); - plugins.on_end_pass(&mut top_ui); + plugins.on_begin_pass(&mut root_ui); + run_ui(&mut root_ui); + plugins.on_end_pass(&mut root_ui); } - // Inform ctx about what we actually used, so we can shrink the native window to fit. - // TODO(emilk): make better use of this somehow - ctx.pass_state_mut(|state| state.allocate_central_panel(top_ui.min_rect())); + ctx.pass_state_mut(|state| { + state.root_ui_available_rect = Some(root_ui.available_rect_before_wrap()); + state.root_ui_min_rect = Some(root_ui.min_rect()); + }); }) } - /// Run the ui code for one frame. - /// - /// At most [`Options::max_passes`] calls will be issued to `run_ui`, - /// and only on the rare occasion that [`Context::request_discard`] is called. - /// Usually, it `run_ui` will only be called once. - /// - /// Put your widgets into a [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. - /// - /// Instead of calling `run`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`]. - /// - /// ``` - /// // One egui context that you keep reusing: - /// let mut ctx = egui::Context::default(); - /// - /// // Each frame: - /// let input = egui::RawInput::default(); - /// let full_output = ctx.run(input, |ctx| { - /// egui::CentralPanel::default().show(&ctx, |ui| { - /// ui.label("Hello egui!"); - /// }); - /// }); - /// // handle full_output - /// ``` - /// - /// ## See also - /// * [`Self::run_ui`] - #[must_use] - #[deprecated = "Call run_ui instead"] - pub fn run(&self, new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput { - self.run_dyn(new_input, &mut run_ui) - } - #[must_use] fn run_dyn(&self, mut new_input: RawInput, run_ui: &mut dyn FnMut(&Self)) -> FullOutput { profiling::function_scope!(); @@ -914,10 +879,10 @@ impl Context { output } - /// An alternative to calling [`Self::run`]. + /// An alternative to calling [`Self::run_ui`]. /// - /// It is usually better to use [`Self::run`], because - /// `run` supports multi-pass layout using [`Self::request_discard`]. + /// It is usually better to use [`Self::run_ui`], because + /// `run_ui` supports multi-pass layout using [`Self::request_discard`]. /// /// ``` /// // One egui context that you keep reusing: @@ -927,9 +892,7 @@ impl Context { /// let input = egui::RawInput::default(); /// ctx.begin_pass(input); /// - /// egui::CentralPanel::default().show(&ctx, |ui| { - /// ui.label("Hello egui!"); - /// }); + /// // … add panels and windows here … /// /// let full_output = ctx.end_pass(); /// // handle full_output @@ -942,12 +905,6 @@ impl Context { self.write(|ctx| ctx.begin_pass(new_input)); } - - /// See [`Self::begin_pass`]. - #[deprecated = "Renamed begin_pass"] - pub fn begin_frame(&self, new_input: RawInput) { - self.begin_pass(new_input); - } } /// ## Borrows parts of [`Context`] @@ -1048,7 +1005,7 @@ impl Context { /// Read-only access to [`PassState`]. /// - /// This is only valid during the call to [`Self::run`] (between [`Self::begin_pass`] and [`Self::end_pass`]). + /// This is only valid during the call to [`Self::run_ui`] (between [`Self::begin_pass`] and [`Self::end_pass`]). #[inline] pub(crate) fn pass_state(&self, reader: impl FnOnce(&PassState) -> R) -> R { self.write(move |ctx| reader(&ctx.viewport().this_pass)) @@ -1056,7 +1013,7 @@ impl Context { /// Read-write access to [`PassState`]. /// - /// This is only valid during the call to [`Self::run`] (between [`Self::begin_pass`] and [`Self::end_pass`]). + /// This is only valid during the call to [`Self::run_ui`] (between [`Self::begin_pass`] and [`Self::end_pass`]). #[inline] pub(crate) fn pass_state_mut(&self, writer: impl FnOnce(&mut PassState) -> R) -> R { self.write(move |ctx| writer(&mut ctx.viewport().this_pass)) @@ -1072,7 +1029,7 @@ impl Context { /// Read-only access to [`Fonts`]. /// - /// Not valid until first call to [`Context::run()`]. + /// Not valid until first call to [`Context::run_ui()`]. /// That's because since we don't know the proper `pixels_per_point` until then. #[inline] pub fn fonts(&self, reader: impl FnOnce(&FontsView<'_>) -> R) -> R { @@ -1089,7 +1046,7 @@ impl Context { /// Read-write access to [`Fonts`]. /// - /// Not valid until first call to [`Context::run()`]. + /// Not valid until first call to [`Context::run_ui()`]. /// That's because since we don't know the proper `pixels_per_point` until then. #[inline] pub fn fonts_mut(&self, reader: impl FnOnce(&mut FontsView<'_>) -> R) -> R { @@ -1360,6 +1317,7 @@ impl Context { let WidgetRect { id, + parent_id: _, layer_id, rect, interact_rect, @@ -1378,8 +1336,8 @@ impl Context { interact_rect, sense, flags: Flags::empty(), - interact_pointer_pos: None, - intrinsic_size: None, + interact_pointer_pos_or_nan: Pos2::NAN, + intrinsic_size_or_nan: Vec2::NAN, }; res.flags.set(Flags::ENABLED, enabled); @@ -1470,14 +1428,11 @@ impl Context { || res.long_touched() || clicked || res.drag_stopped(); - if is_interacted_with { - res.interact_pointer_pos = input.pointer.interact_pos(); - if let (Some(to_global), Some(pos)) = ( - memory.to_global.get(&res.layer_id), - &mut res.interact_pointer_pos, - ) { - *pos = to_global.inverse() * *pos; + if is_interacted_with && let Some(mut pos) = input.pointer.interact_pos() { + if let Some(to_global) = memory.to_global.get(&res.layer_id) { + pos = to_global.inverse() * pos; } + res.interact_pointer_pos_or_nan = pos; } if input.pointer.any_down() && !is_interacted_with { @@ -1547,6 +1502,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 @@ -1662,7 +1622,7 @@ impl Context { /// The total number of completed frames. /// - /// Starts at zero, and is incremented once at the end of each call to [`Self::run`]. + /// Starts at zero, and is incremented once at the end of each call to [`Self::run_ui`]. /// /// This is always smaller or equal to [`Self::cumulative_pass_nr`]. pub fn cumulative_frame_nr(&self) -> u64 { @@ -1671,7 +1631,7 @@ impl Context { /// The total number of completed frames. /// - /// Starts at zero, and is incremented once at the end of each call to [`Self::run`]. + /// Starts at zero, and is incremented once at the end of each call to [`Self::run_ui`]. /// /// This is always smaller or equal to [`Self::cumulative_pass_nr_for`]. pub fn cumulative_frame_nr_for(&self, id: ViewportId) -> u64 { @@ -1691,7 +1651,7 @@ impl Context { /// The total number of completed passes (usually there is one pass per rendered frame). /// - /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). + /// Starts at zero, and is incremented for each completed pass inside of [`Self::run_ui`] (usually once). /// /// If you instead want to know which pass index this is within the current frame, /// use [`Self::current_pass_index`]. @@ -1701,7 +1661,7 @@ impl Context { /// The total number of completed passes (usually there is one pass per rendered frame). /// - /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). + /// Starts at zero, and is incremented for each completed pass inside of [`Self::run_ui`] (usually once). pub fn cumulative_pass_nr_for(&self, id: ViewportId) -> u64 { self.read(|ctx| { ctx.viewports @@ -1935,7 +1895,7 @@ impl Context { } } -/// Callbacks +/// Plugins impl Context { /// Call the given callback at the start of each pass of each viewport. /// @@ -2076,7 +2036,7 @@ impl Context { self.options(|opt| opt.theme()) } - /// The [`Theme`] used to select between dark and light [`Self::style`] + /// The [`Theme`] used to select between dark and light [`Self::global_style`] /// as the active style used by all subsequent popups, menus, etc. /// /// Example: @@ -2093,12 +2053,6 @@ impl Context { self.options(|opt| Arc::clone(opt.style())) } - /// The currently active [`Style`] used by all subsequent popups, menus, etc. - #[deprecated = "Renamed to `global_style` to avoid confusion with `ui.style()`"] - pub fn style(&self) -> Arc