diff --git a/.github/workflows/cargo_machete.yml b/.github/workflows/cargo_machete.yml index dab6725553c..2b06dfdef36 100644 --- a/.github/workflows/cargo_machete.yml +++ b/.github/workflows/cargo_machete.yml @@ -9,4 +9,4 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Machete - uses: bnjbvr/cargo-machete@main + run: cargo install cargo-machete --locked && cargo machete diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index 7be0b42f358..6eb54696139 100644 --- a/.github/workflows/deploy_web_demo.yml +++ b/.github/workflows/deploy_web_demo.yml @@ -39,7 +39,7 @@ jobs: with: profile: minimal target: wasm32-unknown-unknown - toolchain: 1.77.0 + toolchain: 1.80.0 override: true - uses: Swatinem/rust-cache@v2 diff --git a/.github/workflows/preview_build.yml b/.github/workflows/preview_build.yml index 70cd5ce2aae..c44c10f0c83 100644 --- a/.github/workflows/preview_build.yml +++ b/.github/workflows/preview_build.yml @@ -42,9 +42,11 @@ jobs: - name: Generate meta.json env: PR_NUMBER: ${{ github.event.number }} - PR_BRANCH: ${{ github.head_ref }} + URL_SLUG: ${{ github.event.number }}-${{ github.head_ref }} run: | - echo "{\"pr_number\": \"$PR_NUMBER\", \"pr_branch\": \"$PR_BRANCH\"}" > meta.json + # Sanitize the URL_SLUG to only contain alphanumeric characters and dashes + URL_SLUG=$(echo $URL_SLUG | tr -cd '[:alnum:]-') + echo "{\"pr_number\": \"$PR_NUMBER\", \"url_slug\": \"$URL_SLUG\"}" > meta.json - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/preview_cleanup.yml b/.github/workflows/preview_cleanup.yml index 3aba668a2b3..6e2b94b83aa 100644 --- a/.github/workflows/preview_cleanup.yml +++ b/.github/workflows/preview_cleanup.yml @@ -15,9 +15,14 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - run: mkdir -p empty_dir - - name: Url slug variable + - name: Generate URL_SLUG + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + URL_SLUG: ${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.ref }} run: | - echo "URL_SLUG=${{ github.event.pull_request.number }}-${{ github.event.pull_request.head.ref }}" >> $GITHUB_ENV + # Sanitize the URL_SLUG to only contain alphanumeric characters and dashes + URL_SLUG=$(echo $URL_SLUG | tr -cd '[:alnum:]-') + echo "URL_SLUG=$URL_SLUG" >> $GITHUB_ENV - name: Deploy uses: JamesIves/github-pages-deploy-action@v4 with: diff --git a/.github/workflows/preview_deploy.yml b/.github/workflows/preview_deploy.yml index 9fdcfaf755f..f98f96da162 100644 --- a/.github/workflows/preview_deploy.yml +++ b/.github/workflows/preview_deploy.yml @@ -40,11 +40,7 @@ jobs: - name: Parse meta.json run: | echo "PR_NUMBER=$(jq -r .pr_number meta.json)" >> $GITHUB_ENV - echo "PR_BRANCH=$(jq -r .pr_branch meta.json)" >> $GITHUB_ENV - - - name: Url slug variable - run: | - echo "URL_SLUG=${{ env.PR_NUMBER }}-${{ env.PR_BRANCH }}" >> $GITHUB_ENV + echo "URL_SLUG=$(jq -r .url_slug meta.json)" >> $GITHUB_ENV - name: Deploy uses: JamesIves/github-pages-deploy-action@v4 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 16df33e4720..e2a5896980e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.77.0 + toolchain: 1.80.0 - name: Install packages (Linux) if: runner.os == 'Linux' @@ -83,7 +83,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.77.0 + toolchain: 1.80.0 targets: wasm32-unknown-unknown - run: sudo apt-get update && sudo apt-get install libgtk-3-dev libatk1.0-dev @@ -155,7 +155,7 @@ jobs: - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 with: - rust-version: "1.77.0" + rust-version: "1.80.0" log-level: error command: check arguments: --target ${{ matrix.target }} @@ -170,7 +170,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.77.0 + toolchain: 1.80.0 targets: aarch64-linux-android - name: Set up cargo cache @@ -189,7 +189,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.77.0 + toolchain: 1.80.0 targets: aarch64-apple-ios - name: Set up cargo cache @@ -208,7 +208,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.77.0 + toolchain: 1.80.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 @@ -232,7 +232,7 @@ jobs: lfs: true - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.77.0 + toolchain: 1.80.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eeeb1e0b87..17a2b7a9585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ # egui changelog All notable changes to the `egui` crate will be documented in this file. -NOTE: this is just the changelog for the core `egui` crate. [`eframe`](crates/eframe/CHANGELOG.md), [`ecolor`](crates/ecolor/CHANGELOG.md), [`epaint`](crates/epaint/CHANGELOG.md), [`egui-winit`](crates/egui-winit/CHANGELOG.md), [`egui_glow`](crates/egui_glow/CHANGELOG.md) and [`egui-wgpu`](crates/egui-wgpu/CHANGELOG.md) have their own changelogs! +This is just the changelog for the core `egui` crate. Every crate in this repository has their own changelog: +* [`epaint` changelog](crates/epaint/CHANGELOG.md) +* [`egui-winit` changelog](crates/egui-winit/CHANGELOG.md) +* [`egui-wgpu` changelog](crates/egui-wgpu/CHANGELOG.md) +* [`egui_kittest` changelog](crates/egui_kittest/CHANGELOG.md) +* [`egui_glow` changelog](crates/egui_glow/CHANGELOG.md) +* [`ecolor` changelog](crates/ecolor/CHANGELOG.md) +* [`eframe` changelog](crates/eframe/CHANGELOG.md) 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. diff --git a/CODEOWNERS b/CODEOWNERS index 40b72a3140a..8b71b22c10e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1,2 @@ +/crates/egui_kittest @lucasmerlin /crates/egui-wgpu @Wumpf diff --git a/Cargo.lock b/Cargo.lock index 000165018c6..3837d7ec4c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.16.3" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b76d84ee70e30a4a7e39ab9018e2b17a6a09e31084176cc7c0b2dec036ba45" +checksum = "45c97bb3cc1dacbdc6d1147040fc61309590d3e1ab5efd92a8a09c7a2e07284c" dependencies = [ "enumn", "serde", @@ -30,23 +30,23 @@ dependencies = [ [[package]] name = "accesskit_atspi_common" -version = "0.9.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5393c75d4666f580f4cac0a968bc97c36076bb536a129f28210dac54ee127ed" +checksum = "03db49d2948db6875c69a1ef17816efa8e3d9f36c7cd79e467d8562a6695662b" dependencies = [ "accesskit", "accesskit_consumer", "atspi-common", "serde", "thiserror", - "zvariant", + "zvariant 4.2.0", ] [[package]] name = "accesskit_consumer" -version = "0.24.3" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a12dc159d52233c43d9fe5415969433cbdd52c3d6e0df51bda7d447427b9986" +checksum = "fa3a17950ce0d911f132387777b9b3d05eddafb59b773ccaa53fceefaeb0228e" dependencies = [ "accesskit", "immutable-chunkmap", @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "accesskit_macos" -version = "0.17.4" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc6c1ecd82053d127961ad80a8beaa6004fb851a3a5b96506d7a6bd462403f6" +checksum = "e8d94b7544775dddce398e2500a8b3cc2be3655190879071ce6a9e5610195be4" dependencies = [ "accesskit", "accesskit_consumer", @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "accesskit_unix" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be7f5cf6165be10a54b2655fa2e0e12b2509f38ed6fc43e11c31fdb7ee6230bb" +checksum = "3a88d913b144104dd825f75db1b82c63d754b01c53c2f9b7545dcdfae63bb0ed" dependencies = [ "accesskit", "accesskit_atspi_common", @@ -81,14 +81,14 @@ dependencies = [ "futures-lite", "futures-util", "serde", - "zbus", + "zbus 4.4.0", ] [[package]] name = "accesskit_windows" -version = "0.23.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974e96c347384d9133427167fb8a58c340cb0496988dacceebdc1ed27071023b" +checksum = "0aaa870a5d047338f03707706141f22c98c20e79d5403bf3c9b195549e6cdeea" dependencies = [ "accesskit", "accesskit_consumer", @@ -100,9 +100,9 @@ dependencies = [ [[package]] name = "accesskit_winit" -version = "0.22.4" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aea3522719f1c44564d03e9469a8e2f3a98b3a8a880bd66d0789c6b9c4a669dd" +checksum = "3555a67a9bb208f620cfc3746f1502d1512f0ffbdb19c6901aa90b111aa56ec5" dependencies = [ "accesskit", "accesskit_macos", @@ -258,6 +258,28 @@ dependencies = [ "libloading", ] +[[package]] +name = "ashpd" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c39d707614dbcc6bed00015539f488d8e3fe3e66ed60961efc0c90f4b380b3" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "raw-window-handle 0.6.2", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus 5.1.1", +] + [[package]] name = "async-broadcast" version = "0.7.1" @@ -336,6 +358,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + [[package]] name = "async-process" version = "2.3.0" @@ -401,18 +434,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atk-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -439,11 +460,11 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zbus", + "zbus 4.4.0", "zbus-lockstep", "zbus-lockstep-macros", - "zbus_names", - "zvariant", + "zbus_names 3.0.0", + "zvariant 4.2.0", ] [[package]] @@ -455,7 +476,7 @@ dependencies = [ "atspi-common", "atspi-proxies", "futures-lite", - "zbus", + "zbus 4.4.0", ] [[package]] @@ -466,8 +487,8 @@ checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" dependencies = [ "atspi-common", "serde", - "zbus", - "zvariant", + "zbus 4.4.0", + "zvariant 4.2.0", ] [[package]] @@ -638,16 +659,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "libc", - "system-deps", -] - [[package]] name = "calloop" version = "0.13.0" @@ -697,16 +708,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -1139,6 +1140,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dlib" version = "0.5.2" @@ -1193,7 +1205,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "glow", + "glow 0.16.0", "glutin", "glutin-winit", "home", @@ -1205,7 +1217,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "pollster", + "pollster 0.4.0", "puffin", "raw-window-handle 0.6.2", "ron", @@ -1229,6 +1241,7 @@ dependencies = [ "ahash", "backtrace", "document-features", + "egui_kittest", "emath", "epaint", "log", @@ -1345,7 +1358,7 @@ dependencies = [ "document-features", "egui", "egui-winit", - "glow", + "glow 0.16.0", "glutin", "glutin-winit", "log", @@ -1367,7 +1380,7 @@ dependencies = [ "egui_kittest", "image", "kittest", - "pollster", + "pollster 0.4.0", "wgpu", ] @@ -1632,6 +1645,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.31" @@ -1697,36 +1719,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1783,19 +1775,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - [[package]] name = "gl_generator" version = "0.14.0" @@ -1808,20 +1787,22 @@ dependencies = [ ] [[package]] -name = "glib-sys" -version = "0.18.1" +name = "glow" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" dependencies = [ - "libc", - "system-deps", + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", ] [[package]] name = "glow" -version = "0.14.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" dependencies = [ "js-sys", "slotmap", @@ -1895,17 +1876,6 @@ dependencies = [ "gl_generator", ] -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "gpu-alloc" version = "0.6.0" @@ -1945,24 +1915,6 @@ dependencies = [ "bitflags 2.6.0", ] -[[package]] -name = "gtk-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - [[package]] name = "half" version = "2.4.1" @@ -1983,12 +1935,6 @@ dependencies = [ "allocator-api2", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hello_world" version = "0.1.0" @@ -2071,14 +2017,143 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -2226,7 +2301,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kittest" version = "0.1.0" -source = "git+https://github.com/rerun-io/kittest?branch=main#1336a504aefd05f7e9aa7c9237ae44ba9e72acdd" +source = "git+https://github.com/rerun-io/kittest?branch=main#06e01f17fed36a997e1541f37b2d47e3771d7533" dependencies = [ "accesskit", "accesskit_consumer", @@ -2261,7 +2336,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2287,6 +2362,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "litrs" version = "0.4.1" @@ -2527,17 +2608,6 @@ dependencies = [ "malloc_buf", ] -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -2741,15 +2811,6 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - [[package]] name = "object" version = "0.36.5" @@ -2805,18 +2866,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "parking" version = "2.2.1" @@ -2964,6 +3013,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "pollster" version = "0.4.0" @@ -3243,21 +3298,20 @@ dependencies = [ [[package]] name = "rfd" -version = "0.13.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d8ab342bcc5436e04d3a4c1e09e17d74958bfaddf8d5fad6f85607df0f994f" +checksum = "46f6f80a9b882647d9014673ca9925d30ffc9750f2eed2b4490e189eaebd01e8" dependencies = [ - "block", - "dispatch", - "glib-sys", - "gobject-sys", - "gtk-sys", + "ashpd", + "block2", "js-sys", "log", - "objc", - "objc-foundation", - "objc_id", - "raw-window-handle 0.5.2", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "pollster 0.3.0", + "raw-window-handle 0.6.2", + "urlencoding", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -3333,9 +3387,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" dependencies = [ "log", "once_cell", @@ -3455,15 +3509,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "serial_windows" version = "0.1.0" @@ -3604,6 +3649,12 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -3646,6 +3697,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syntect" version = "5.2.0" @@ -3668,25 +3730,6 @@ dependencies = [ "yaml-rust", ] -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - [[package]] name = "tempfile" version = "3.13.0" @@ -3828,40 +3871,23 @@ dependencies = [ ] [[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] [[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "toml" -version = "0.8.19" +name = "tinytemplate" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", + "serde_json", ] [[package]] @@ -3869,9 +3895,6 @@ name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] [[package]] name = "toml_edit" @@ -3880,8 +3903,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", "winnow", ] @@ -3955,27 +3976,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -4024,15 +4030,22 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "user_attention" version = "0.1.0" @@ -4086,10 +4099,16 @@ dependencies = [ ] [[package]] -name = "version-compare" -version = "0.2.0" +name = "utf16_iter" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "version_check" @@ -4343,9 +4362,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "23.0.0" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ab52f2d3d18b70d5ab8dd270a1cff3ebe6dbe4a7d13c1cc2557138a9777fdc" +checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a" dependencies = [ "arrayvec", "cfg_aliases 0.1.1", @@ -4368,9 +4387,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "23.0.0" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0c68e7b6322a03ee5b83fcd92caeac5c2a932f6457818179f4652ad2a9c065" +checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" dependencies = [ "arrayvec", "bit-vec 0.8.0", @@ -4393,9 +4412,9 @@ dependencies = [ [[package]] name = "wgpu-hal" -version = "23.0.0" +version = "23.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de6e7266b869de56c7e3ed72a954899f71d14fec6cc81c102b7530b92947601b" +checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821" dependencies = [ "android_system_properties", "arrayvec", @@ -4405,7 +4424,7 @@ dependencies = [ "bytemuck", "cfg_aliases 0.1.1", "core-graphics-types", - "glow", + "glow 0.14.2", "glutin_wgl_sys", "gpu-alloc", "gpu-descriptor", @@ -4465,7 +4484,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -4822,6 +4841,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "x11-dl" version = "2.21.0" @@ -4914,6 +4945,30 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zbus" version = "4.4.0" @@ -4947,9 +5002,45 @@ dependencies = [ "uds_windows", "windows-sys 0.52.0", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1162094dc63b1629fcc44150bcceeaa80798cd28bcbe7fa987b65a034c258608" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-util", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow", + "xdg-home", + "zbus_macros 5.1.1", + "zbus_names 4.1.0", + "zvariant 5.1.0", ] [[package]] @@ -4959,7 +5050,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" dependencies = [ "zbus_xml", - "zvariant", + "zvariant 4.2.0", ] [[package]] @@ -4973,7 +5064,7 @@ dependencies = [ "syn", "zbus-lockstep", "zbus_xml", - "zvariant", + "zvariant 4.2.0", ] [[package]] @@ -4986,7 +5077,22 @@ dependencies = [ "proc-macro2", "quote", "syn", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_macros" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd2dcdce3e2727f7d74b7e33b5a89539b3cc31049562137faf7ae4eb86cd16d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names 4.1.0", + "zvariant 5.1.0", + "zvariant_utils 3.0.2", ] [[package]] @@ -4997,7 +5103,19 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "856b7a38811f71846fd47856ceee8bccaec8399ff53fb370247e66081ace647b" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant 5.1.0", ] [[package]] @@ -5009,8 +5127,8 @@ dependencies = [ "quick-xml 0.30.0", "serde", "static_assertions", - "zbus_names", - "zvariant", + "zbus_names 3.0.0", + "zvariant 4.2.0", ] [[package]] @@ -5034,12 +5152,55 @@ dependencies = [ "syn", ] +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zune-core" version = "0.4.12" @@ -5065,7 +5226,23 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zvariant_derive", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1200ee6ac32f1e5a312e455a949a4794855515d34f9909f4a3e082d14e1a56f" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "url", + "winnow", + "zvariant_derive 5.1.0", + "zvariant_utils 3.0.2", ] [[package]] @@ -5078,7 +5255,20 @@ dependencies = [ "proc-macro2", "quote", "syn", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_derive" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687e3b97fae6c9104fbbd36c73d27d149abf04fb874e2efbd84838763daa8916" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils 3.0.2", ] [[package]] @@ -5091,3 +5281,17 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zvariant_utils" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d1d011a38f12360e5fcccceeff5e2c42a8eb7f27f0dcba97a0862ede05c9c6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn", + "winnow", +] diff --git a/Cargo.toml b/Cargo.toml index 06c8a715757..6b50ac23a0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ [workspace.package] edition = "2021" license = "MIT OR Apache-2.0" -rust-version = "1.77" +rust-version = "1.80" version = "0.29.1" @@ -77,9 +77,9 @@ bytemuck = "1.7.2" criterion = { version = "0.5.1", default-features = false } dify = { version = "0.7", default-features = false } document-features = "0.2.10" -glow = "0.14" -glutin = "0.32.0" -glutin-winit = "0.5.0" +glow = "0.16" +glutin = { version = "0.32.0", default-features = false } +glutin-winit = { version = "0.5.0", default-features = false } home = "0.5.9" image = { version = "0.25", default-features = false } kittest = { git = "https://github.com/rerun-io/kittest", version = "0.1", branch = "main" } @@ -106,13 +106,13 @@ winit = { version = "0.30.5", default-features = false } unsafe_code = "deny" elided_lifetimes_in_paths = "warn" -future_incompatible = "warn" -nonstandard_style = "warn" -rust_2018_idioms = "warn" +future_incompatible = { level = "warn", priority = -1 } +nonstandard_style = { level = "warn", priority = -1 } +rust_2018_idioms = { level = "warn", priority = -1 } rust_2021_prelude_collisions = "warn" semicolon_in_expressions_from_macros = "warn" trivial_numeric_casts = "warn" -unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 +unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 unused_extern_crates = "warn" unused_import_braces = "warn" unused_lifetimes = "warn" @@ -145,6 +145,7 @@ disallowed_types = "warn" # See clippy.toml doc_link_with_quotes = "warn" doc_markdown = "warn" empty_enum = "warn" +empty_enum_variants_with_brackets = "warn" enum_glob_use = "warn" equatable_if_let = "warn" exit = "warn" @@ -169,6 +170,8 @@ inefficient_to_string = "warn" infinite_loop = "warn" into_iter_without_iter = "warn" invalid_upcast_comparisons = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" iter_not_returning_iterator = "warn" iter_on_empty_collections = "warn" iter_on_single_items = "warn" @@ -185,6 +188,7 @@ macro_use_imports = "warn" manual_assert = "warn" manual_clamp = "warn" manual_instant_elapsed = "warn" +manual_is_variant_and = "warn" manual_let_else = "warn" manual_ok_or = "warn" manual_string_new = "warn" @@ -202,6 +206,7 @@ mismatching_type_param_order = "warn" missing_enforced_import_renames = "warn" missing_errors_doc = "warn" missing_safety_doc = "warn" +mixed_attributes_style = "warn" mut_mut = "warn" mutex_integer = "warn" needless_borrow = "warn" @@ -211,21 +216,26 @@ needless_pass_by_ref_mut = "warn" needless_pass_by_value = "warn" negative_feature_names = "warn" nonstandard_macro_braces = "warn" +option_as_ref_cloned = "warn" option_option = "warn" path_buf_push_overwrite = "warn" print_stderr = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" +pub_underscore_fields = "warn" pub_without_shorthand = "warn" rc_mutex = "warn" readonly_write_lock = "warn" redundant_type_annotations = "warn" +ref_as_ptr = "warn" ref_option_ref = "warn" ref_patterns = "warn" rest_pat_in_fully_bound_structs = "warn" same_functions_in_if_condition = "warn" semicolon_if_nothing_returned = "warn" +single_char_pattern = "warn" single_match_else = "warn" +str_split_at_newline = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" @@ -261,12 +271,15 @@ zero_sized_map_values = "warn" # TODO(emilk): enable more of these lints: iter_over_hash_type = "allow" -let_underscore_untyped = "allow" missing_assert_message = "allow" should_panic_without_expect = "allow" too_many_lines = "allow" unwrap_used = "allow" # TODO(emilk): We really wanna warn on this one +# These are meh: +assigning_clones = "allow" # No please +let_underscore_must_use = "allow" +let_underscore_untyped = "allow" manual_range_contains = "allow" # this one is just worse imho self_named_module_files = "allow" # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 significant_drop_tightening = "allow" # Too many false positives diff --git a/README.md b/README.md index b5f00dfb8e8..247ad32d90c 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,9 @@ Still, egui can be used to create professional looking applications, like [the R * Label text selection * And more! +Check out the [3rd party egui crates wiki](https://github.com/emilk/egui/wiki/3rd-party-egui-crates) for even more +widgets and features, maintained by the community. + Light Theme: @@ -187,30 +190,8 @@ These are the official egui integrations: ### 3rd party integrations -* [`egui-ash`](https://github.com/MatchaChoco010/egui-ash) for [`ash`](https://github.com/ash-rs/ash) (a very lightweight wrapper around Vulkan) -* [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/) -* [`egui_gl_glfw`](https://github.com/mrclean71774/egui_gl_glfw) for [GLFW](https://crates.io/crates/glfw) -* [`egui_glium`](https://github.com/fayalalebrun/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium) -* [`egui-glutin-gl`](https://github.com/h3r2tic/egui-glutin-gl/) for [glutin](https://crates.io/crates/glutin) -* [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2) -* [`egui_sdl2_platform`](https://github.com/ComLarsic/egui_sdl2_platform) for [SDL2](https://crates.io/crates/sdl2) -* [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano) -* [`egui_winit_vulkano`](https://github.com/hakolao/egui_winit_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano) -* [`egui-macroquad`](https://github.com/optozorax/egui-macroquad) for [macroquad](https://github.com/not-fl3/macroquad) -* [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad) for [Miniquad](https://github.com/not-fl3/miniquad) -* [`egui_speedy2d`](https://github.com/heretik31/egui_speedy2d) for [Speedy2d](https://github.com/QuantumBadger/Speedy2D) -* [`egui-tetra2`](https://crates.io/crates/egui-tetra2) for [Tetra](https://crates.io/crates/tetra), a 2D game framework -* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash) -* [`fltk-egui`](https://crates.io/crates/fltk-egui) for [fltk-rs](https://github.com/fltk-rs/fltk-rs) -* [`ggegui`](https://github.com/NemuiSen/ggegui) for the [ggez](https://ggez.rs/) game framework -* [`godot-egui`](https://github.com/setzer22/godot-egui) for [godot-rust](https://github.com/godot-rust/godot-rust) -* [`gtk-egui-area`](https://github.com/ilya-zlobintsev/gtk-egui-area) for [gtk-rs](https://github.com/gtk-rs/gtk4-rs) -* [`nannou_egui`](https://github.com/nannou-org/nannou/tree/master/nannou_egui) for [nannou](https://nannou.cc) -* [`notan_egui`](https://github.com/Nazariglez/notan/tree/main/crates/notan_egui) for [notan](https://github.com/Nazariglez/notan) -* [`screen-13-egui`](https://github.com/attackgoat/screen-13/tree/master/contrib/screen-13-egui) for [Screen 13](https://github.com/attackgoat/screen-13) -* [`egui_skia`](https://github.com/lucasmerlin/egui_skia) for [skia](https://github.com/rust-skia/rust-skia/tree/master/skia-safe) -* [`smithay-egui`](https://github.com/Smithay/smithay-egui) for [smithay](https://github.com/Smithay/smithay/) -* [`tauri-egui`](https://github.com/tauri-apps/tauri-egui) for [tauri](https://github.com/tauri-apps/tauri) +Check the wiki to find [3rd party integrations](https://github.com/emilk/egui/wiki/3rd-party-integrations) +and [egui crates](https://github.com/emilk/egui/wiki/3rd-party-egui-crates). ### Writing your own egui integration Missing an integration for the thing you're working on? Create one, it's easy! diff --git a/clippy.toml b/clippy.toml index 05e436ac419..9e5fdd1e593 100644 --- a/clippy.toml +++ b/clippy.toml @@ -3,7 +3,7 @@ # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: -msrv = "1.77" +msrv = "1.80" allow-unwrap-in-tests = true @@ -69,9 +69,12 @@ disallowed-types = [ # Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown doc-valid-idents = [ - # You must also update the same list in the root `clippy.toml`! + # You must also update the same list in `scripts/clippy_wasm/clippy.toml`! "AccessKit", "WebGL", + "WebGL1", + "WebGL2", "WebGPU", + "VirtualBox", "..", ] diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index 80e9e0778d3..c38a8d6b9f8 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -269,3 +269,18 @@ impl Color32 { ) } } + +impl std::ops::Mul for Color32 { + type Output = Self; + + /// Fast gamma-space multiplication. + #[inline] + fn mul(self, other: Self) -> Self { + Self([ + fast_round(self[0] as f32 * other[0] as f32 / 255.0), + fast_round(self[1] as f32 * other[1] as f32 / 255.0), + fast_round(self[2] as f32 * other[2] as f32 / 255.0), + fast_round(self[3] as f32 * other[3] as f32 / 255.0), + ]) + } +} diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 8e6b8cf7bb0..87ab1d3b2c9 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -85,7 +85,7 @@ puffin = [ ] ## Enables wayland support and fixes clipboard issue. -wayland = ["egui-winit/wayland", "egui-wgpu?/wayland", "egui_glow?/wayland"] +wayland = ["egui-winit/wayland", "egui-wgpu?/wayland", "egui_glow?/wayland", "glutin?/wayland", "glutin-winit?/wayland"] ## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web. ## @@ -111,7 +111,7 @@ web_screen_reader = [ wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"] ## Enables compiling for x11. -x11 = ["egui-winit/x11", "egui-wgpu?/x11", "egui_glow?/x11"] +x11 = ["egui-winit/x11", "egui-wgpu?/x11", "egui_glow?/x11", "glutin?/x11", "glutin?/glx", "glutin-winit?/x11", "glutin-winit?/glx"] ## If set, eframe will look for the env-var `EFRAME_SCREENSHOT_TO` and write a screenshot to that location, and then quit. ## This is used to generate images for examples. @@ -154,10 +154,8 @@ egui-wgpu = { workspace = true, optional = true, features = [ ] } # if wgpu is used, use it with winit pollster = { workspace = true, optional = true } # needed for wgpu -# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps. -# this can be done at the same time we expose x11/wayland features of winit crate. -glutin = { workspace = true, optional = true } -glutin-winit = { workspace = true, optional = true } +glutin = { workspace = true, optional = true, default-features = false, features = ["egl", "wgl"] } +glutin-winit = { workspace = true, optional = true, default-features = false, features = ["egl", "wgl"] } home = { workspace = true, optional = true } puffin = { workspace = true, optional = true } wgpu = { workspace = true, optional = true, features = [ diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index f567507c4da..9f4f6dde8b1 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -439,7 +439,7 @@ pub struct WebOptions { /// Unused by webgl context as of writing. pub depth_buffer: u8, - /// Which version of WebGl context to select + /// Which version of WebGL context to select /// /// Default: [`WebGlContextOption::BestFirst`]. #[cfg(feature = "glow")] diff --git a/crates/eframe/src/native/event_loop_context.rs b/crates/eframe/src/native/event_loop_context.rs index 8f54681a869..f1262f85358 100644 --- a/crates/eframe/src/native/event_loop_context.rs +++ b/crates/eframe/src/native/event_loop_context.rs @@ -14,7 +14,7 @@ impl EventLoopGuard { cell.get().is_none(), "Attempted to set a new event loop while one is already set" ); - cell.set(Some(event_loop as *const ActiveEventLoop)); + cell.set(Some(std::ptr::from_ref::(event_loop))); }); Self } diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 476b1bdd466..51c0cadce2a 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -661,13 +661,14 @@ impl<'app> GlowWinitRunning<'app> { { for action in viewport.actions_requested.drain() { match action { - ActionRequested::Screenshot => { + ActionRequested::Screenshot(user_data) => { let screenshot = painter.read_screen_rgba(screen_size_in_pixels); egui_winit .egui_input_mut() .events .push(egui::Event::Screenshot { viewport_id, + user_data, image: screenshot.into(), }); } @@ -941,7 +942,9 @@ impl GlutinWindowContext { // Create GL display. This may probably create a window too on most platforms. Definitely on `MS windows`. Never on Android. let display_builder = glutin_winit::DisplayBuilder::new() // we might want to expose this option to users in the future. maybe using an env var or using native_options. - .with_preference(glutin_winit::ApiPreference::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150 + // + // The justification for FallbackEgl over PreferEgl is at https://github.com/emilk/egui/pull/2526#issuecomment-1400229576 . + .with_preference(glutin_winit::ApiPreference::FallbackEgl) .with_window_attributes(Some(egui_winit::create_winit_window_attributes( egui_ctx, event_loop, diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 997383f85c3..d13bed0bf41 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -643,10 +643,16 @@ impl<'app> WgpuWinitRunning<'app> { let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); - let screenshot_requested = viewport - .actions_requested - .take(&ActionRequested::Screenshot) - .is_some(); + let mut screenshot_commands = vec![]; + viewport.actions_requested.retain(|cmd| { + if let ActionRequested::Screenshot(info) = cmd { + screenshot_commands.push(info.clone()); + false + } else { + true + } + }); + let screenshot_requested = !screenshot_commands.is_empty(); let (vsync_secs, screenshot) = painter.paint_and_update_textures( viewport_id, pixels_per_point, @@ -655,19 +661,32 @@ impl<'app> WgpuWinitRunning<'app> { &textures_delta, screenshot_requested, ); - if let Some(screenshot) = screenshot { - egui_winit - .egui_input_mut() - .events - .push(egui::Event::Screenshot { - viewport_id, - image: screenshot.into(), - }); + match (screenshot_requested, screenshot) { + (false, None) => {} + (true, Some(screenshot)) => { + let screenshot = Arc::new(screenshot); + for user_data in screenshot_commands { + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Screenshot { + viewport_id, + user_data, + image: screenshot.clone(), + }); + } + } + (true, None) => { + log::error!("Bug in egui_wgpu: screenshot requested, but no screenshot was taken"); + } + (false, Some(_)) => { + log::warn!("Bug in egui_wgpu: Got screenshot without requesting it"); + } } for action in viewport.actions_requested.drain() { match action { - ActionRequested::Screenshot => { + ActionRequested::Screenshot { .. } => { // already handled above } ActionRequested::Cut => { diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 00cc8f0c182..2f8f78d0818 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -260,7 +260,7 @@ impl AppRunner { self.frame.info.cpu_usage = Some(cpu_usage_seconds); } - fn handle_platform_output(&mut self, platform_output: egui::PlatformOutput) { + fn handle_platform_output(&self, platform_output: egui::PlatformOutput) { #[cfg(feature = "web_screen_reader")] if self.egui_ctx.options(|o| o.screen_reader) { super::screen_reader::speak(&platform_output.events_description()); diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 2d5f5a4c3f0..ec487622e38 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -1,41 +1,13 @@ -use raw_window_handle::{ - DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, - RawWindowHandle, WebDisplayHandle, WebWindowHandle, WindowHandle, -}; use std::sync::Arc; + use wasm_bindgen::JsValue; use web_sys::HtmlCanvasElement; -use crate::WebOptions; use egui_wgpu::{RenderState, SurfaceErrorAction, WgpuSetup}; -use super::web_painter::WebPainter; - -struct EguiWebWindow(u32); - -#[allow(unsafe_code)] -impl HasWindowHandle for EguiWebWindow { - fn window_handle(&self) -> Result, HandleError> { - // SAFETY: there is no lifetime here. - unsafe { - Ok(WindowHandle::borrow_raw(RawWindowHandle::Web( - WebWindowHandle::new(self.0), - ))) - } - } -} +use crate::WebOptions; -#[allow(unsafe_code)] -impl HasDisplayHandle for EguiWebWindow { - fn display_handle(&self) -> Result, HandleError> { - // SAFETY: there is no lifetime here. - unsafe { - Ok(DisplayHandle::borrow_raw(RawDisplayHandle::Web( - WebDisplayHandle::new(), - ))) - } - } -} +use super::web_painter::WebPainter; pub(crate) struct WebPainterWgpu { canvas: HtmlCanvasElement, diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 46efa09bb33..6cbc371f34c 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -16,7 +16,7 @@ pub struct WebRunner { /// Have we ever panicked? panic_handler: PanicHandler, - /// If we ever panic during running, this RefCell is poisoned. + /// If we ever panic during running, this `RefCell` is poisoned. /// So before we use it, we need to check [`Self::panic_handler`]. runner: Rc>>, diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index e2c22a5cffc..29dc8d8f5c2 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -159,10 +159,14 @@ impl RenderState { ); } + let trace_path = std::env::var("WGPU_TRACE"); let (device, queue) = { crate::profile_scope!("request_device"); adapter - .request_device(&(*device_descriptor)(&adapter), None) + .request_device( + &(*device_descriptor)(&adapter), + trace_path.ok().as_ref().map(std::path::Path::new), + ) .await? }; diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index a16b93781f6..b6d49d22d4f 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -125,7 +125,7 @@ pub struct ScreenDescriptor { /// Size of the window in physical pixels. pub size_in_pixels: [u32; 2], - /// HiDPI scale factor (pixels per point). + /// High-DPI scale factor (pixels per point). pub pixels_per_point: f32, } diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index abf88518368..161773117b3 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -728,7 +728,7 @@ impl Painter { .retain(|id, _| active_viewports.contains(id)); } - #[allow(clippy::unused_self)] + #[allow(clippy::needless_pass_by_ref_mut, clippy::unused_self)] pub fn destroy(&mut self) { // TODO(emilk): something here? } diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 4d179fc2d88..2f65548757d 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -69,7 +69,7 @@ winit = { workspace = true, default-features = false } #! ### Optional dependencies # feature accesskit -accesskit_winit = { version = "0.22", optional = true } +accesskit_winit = { version = "0.23", optional = true } ## Enable this when generating docs. document-features = { workspace = true, optional = true } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index a78339d01fe..1cb2d502c5d 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1301,7 +1301,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option { - actions_requested.insert(ActionRequested::Screenshot); + ViewportCommand::Screenshot(user_data) => { + actions_requested.insert(ActionRequested::Screenshot(user_data)); } ViewportCommand::RequestCut => { actions_requested.insert(ActionRequested::Cut); diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index 487a4eac6e9..9bb1dc0f4e4 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -87,7 +87,7 @@ ahash.workspace = true nohash-hasher.workspace = true #! ### Optional dependencies -accesskit = { version = "0.16", optional = true } +accesskit = { version = "0.17.0", optional = true } backtrace = { workspace = true, optional = true } @@ -98,3 +98,7 @@ log = { workspace = true, optional = true } puffin = { workspace = true, optional = true } ron = { workspace = true, optional = true } serde = { workspace = true, optional = true, features = ["derive", "rc"] } + + +[dev-dependencies] +egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] } diff --git a/crates/egui/src/cache/cache_storage.rs b/crates/egui/src/cache/cache_storage.rs new file mode 100644 index 00000000000..d4c3c9aef15 --- /dev/null +++ b/crates/egui/src/cache/cache_storage.rs @@ -0,0 +1,69 @@ +use super::CacheTrait; + +/// A typemap of many caches, all implemented with [`CacheTrait`]. +/// +/// You can access egui's caches via [`crate::Memory::caches`], +/// found with [`crate::Context::memory_mut`]. +/// +/// ``` +/// use egui::cache::{CacheStorage, ComputerMut, FrameCache}; +/// +/// #[derive(Default)] +/// struct CharCounter {} +/// impl ComputerMut<&str, usize> for CharCounter { +/// fn compute(&mut self, s: &str) -> usize { +/// s.chars().count() +/// } +/// } +/// type CharCountCache<'a> = FrameCache; +/// +/// # let mut cache_storage = CacheStorage::default(); +/// let mut cache = cache_storage.cache::>(); +/// assert_eq!(cache.get("hello"), 5); +/// ``` +#[derive(Default)] +pub struct CacheStorage { + caches: ahash::HashMap>, +} + +impl CacheStorage { + pub fn cache(&mut self) -> &mut Cache { + self.caches + .entry(std::any::TypeId::of::()) + .or_insert_with(|| Box::::default()) + .as_any_mut() + .downcast_mut::() + .unwrap() + } + + /// Total number of cached values + fn num_values(&self) -> usize { + self.caches.values().map(|cache| cache.len()).sum() + } + + /// Call once per frame to evict cache. + pub fn update(&mut self) { + self.caches.retain(|_, cache| { + cache.update(); + cache.len() > 0 + }); + } +} + +impl Clone for CacheStorage { + fn clone(&self) -> Self { + // We return an empty cache that can be filled in again. + Self::default() + } +} + +impl std::fmt::Debug for CacheStorage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "FrameCacheStorage[{} caches with {} elements]", + self.caches.len(), + self.num_values() + ) + } +} diff --git a/crates/egui/src/cache/cache_trait.rs b/crates/egui/src/cache/cache_trait.rs new file mode 100644 index 00000000000..73cb61f3865 --- /dev/null +++ b/crates/egui/src/cache/cache_trait.rs @@ -0,0 +1,11 @@ +/// A cache, storing some value for some length of time. +#[allow(clippy::len_without_is_empty)] +pub trait CacheTrait: 'static + Send + Sync { + /// Call once per frame to evict cache. + fn update(&mut self); + + /// Number of values currently in the cache. + fn len(&self) -> usize; + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any; +} diff --git a/crates/egui/src/util/cache.rs b/crates/egui/src/cache/frame_cache.rs similarity index 51% rename from crates/egui/src/util/cache.rs rename to crates/egui/src/cache/frame_cache.rs index 14ec9a7b6a2..6c74c58dc3b 100644 --- a/crates/egui/src/util/cache.rs +++ b/crates/egui/src/cache/frame_cache.rs @@ -1,9 +1,4 @@ -//! Computing the same thing each frame can be expensive, -//! so often you want to save the result from the previous frame and reuse it. -//! -//! Enter [`FrameCache`]: it caches the results of a computation for one frame. -//! If it is still used next frame, it is not recomputed. -//! If it is not used next frame, it is evicted from the cache to save memory. +use super::CacheTrait; /// Something that does an expensive computation that we want to cache /// to save us from recomputing it each frame. @@ -74,17 +69,6 @@ impl FrameCache { } } -#[allow(clippy::len_without_is_empty)] -pub trait CacheTrait: 'static + Send + Sync { - /// Call once per frame to evict cache. - fn update(&mut self); - - /// Number of values currently in the cache. - fn len(&self) -> usize; - - fn as_any_mut(&mut self) -> &mut dyn std::any::Any; -} - impl CacheTrait for FrameCache { @@ -100,65 +84,3 @@ impl CacheTrait self } } - -/// ``` -/// use egui::util::cache::{CacheStorage, ComputerMut, FrameCache}; -/// -/// #[derive(Default)] -/// struct CharCounter {} -/// impl ComputerMut<&str, usize> for CharCounter { -/// fn compute(&mut self, s: &str) -> usize { -/// s.chars().count() -/// } -/// } -/// type CharCountCache<'a> = FrameCache; -/// -/// # let mut cache_storage = CacheStorage::default(); -/// let mut cache = cache_storage.cache::>(); -/// assert_eq!(cache.get("hello"), 5); -/// ``` -#[derive(Default)] -pub struct CacheStorage { - caches: ahash::HashMap>, -} - -impl CacheStorage { - pub fn cache(&mut self) -> &mut FrameCache { - self.caches - .entry(std::any::TypeId::of::()) - .or_insert_with(|| Box::::default()) - .as_any_mut() - .downcast_mut::() - .unwrap() - } - - /// Total number of cached values - fn num_values(&self) -> usize { - self.caches.values().map(|cache| cache.len()).sum() - } - - /// Call once per frame to evict cache. - pub fn update(&mut self) { - for cache in self.caches.values_mut() { - cache.update(); - } - } -} - -impl Clone for CacheStorage { - fn clone(&self) -> Self { - // We return an empty cache that can be filled in again. - Self::default() - } -} - -impl std::fmt::Debug for CacheStorage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "FrameCacheStorage[{} caches with {} elements]", - self.caches.len(), - self.num_values() - ) - } -} diff --git a/crates/egui/src/cache/frame_publisher.rs b/crates/egui/src/cache/frame_publisher.rs new file mode 100644 index 00000000000..0c2bc81d6c5 --- /dev/null +++ b/crates/egui/src/cache/frame_publisher.rs @@ -0,0 +1,61 @@ +use std::hash::Hash; + +use super::CacheTrait; + +/// Stores a key:value pair for the duration of this frame and the next. +pub struct FramePublisher { + generation: u32, + cache: ahash::HashMap, +} + +impl Default for FramePublisher { + fn default() -> Self { + Self::new() + } +} + +impl FramePublisher { + pub fn new() -> Self { + Self { + generation: 0, + cache: Default::default(), + } + } + + /// Publish the value. It will be available for the duration of this and the next frame. + pub fn set(&mut self, key: Key, value: Value) { + self.cache.insert(key, (self.generation, value)); + } + + /// Retrieve a value if it was published this or the previous frame. + pub fn get(&self, key: &Key) -> Option<&Value> { + self.cache.get(key).map(|(_, value)| value) + } + + /// Must be called once per frame to clear the cache. + pub fn evict_cache(&mut self) { + let current_generation = self.generation; + self.cache.retain(|_key, cached| { + cached.0 == current_generation // only keep those that were published this frame + }); + self.generation = self.generation.wrapping_add(1); + } +} + +impl CacheTrait for FramePublisher +where + Key: 'static + Eq + Hash + Send + Sync, + Value: 'static + Send + Sync, +{ + fn update(&mut self) { + self.evict_cache(); + } + + fn len(&self) -> usize { + self.cache.len() + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} diff --git a/crates/egui/src/cache/mod.rs b/crates/egui/src/cache/mod.rs new file mode 100644 index 00000000000..68469ef3907 --- /dev/null +++ b/crates/egui/src/cache/mod.rs @@ -0,0 +1,21 @@ +//! Caches for preventing the same value from being recomputed every frame. +//! +//! Computing the same thing each frame can be expensive, +//! so often you want to save the result from the previous frame and reuse it. +//! +//! Enter [`FrameCache`]: it caches the results of a computation for one frame. +//! If it is still used next frame, it is not recomputed. +//! If it is not used next frame, it is evicted from the cache to save memory. +//! +//! You can access egui's caches via [`crate::Memory::caches`], +//! found with [`crate::Context::memory_mut`]. + +mod cache_storage; +mod cache_trait; +mod frame_cache; +mod frame_publisher; + +pub use cache_storage::CacheStorage; +pub use cache_trait::CacheTrait; +pub use frame_cache::{ComputerMut, FrameCache}; +pub use frame_publisher::FramePublisher; diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index ab9a0b88d09..a2bfbe1905f 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -467,7 +467,7 @@ impl Area { id: interact_id, layer_id, rect: state.rect(), - interact_rect: state.rect(), + interact_rect: state.rect().intersect(constrain_rect), sense, enabled, }, diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index 3dd75a4458e..e68e0def1b0 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod area; pub mod collapsing_header; mod combo_box; pub mod frame; +pub mod modal; pub mod panel; pub mod popup; pub(crate) mod resize; @@ -18,6 +19,7 @@ pub use { collapsing_header::{CollapsingHeader, CollapsingResponse}, combo_box::*, frame::Frame, + modal::{Modal, ModalResponse}, panel::{CentralPanel, SidePanel, TopBottomPanel}, popup::*, resize::Resize, diff --git a/crates/egui/src/containers/modal.rs b/crates/egui/src/containers/modal.rs new file mode 100644 index 00000000000..521b9dedba4 --- /dev/null +++ b/crates/egui/src/containers/modal.rs @@ -0,0 +1,165 @@ +use crate::{ + Area, Color32, Context, Frame, Id, InnerResponse, Order, Response, Sense, Ui, UiBuilder, UiKind, +}; +use emath::{Align2, Vec2}; + +/// A modal dialog. +/// Similar to a [`crate::Window`] but centered and with a backdrop that +/// blocks input to the rest of the UI. +/// +/// You can show multiple modals on top of each other. The topmost modal will always be +/// the most recently shown one. +pub struct Modal { + pub area: Area, + pub backdrop_color: Color32, + pub frame: Option, +} + +impl Modal { + /// Create a new Modal. The id is passed to the area. + pub fn new(id: Id) -> Self { + Self { + area: Self::default_area(id), + backdrop_color: Color32::from_black_alpha(100), + frame: None, + } + } + + /// Returns an area customized for a modal. + /// Makes these changes to the default area: + /// - sense: hover + /// - anchor: center + /// - order: foreground + pub fn default_area(id: Id) -> Area { + Area::new(id) + .kind(UiKind::Modal) + .sense(Sense::hover()) + .anchor(Align2::CENTER_CENTER, Vec2::ZERO) + .order(Order::Foreground) + .interactable(true) + } + + /// Set the frame of the modal. + /// + /// Default is [`Frame::popup`]. + #[inline] + pub fn frame(mut self, frame: Frame) -> Self { + self.frame = Some(frame); + self + } + + /// Set the backdrop color of the modal. + /// + /// Default is `Color32::from_black_alpha(100)`. + #[inline] + pub fn backdrop_color(mut self, color: Color32) -> Self { + self.backdrop_color = color; + self + } + + /// Set the area of the modal. + /// + /// Default is [`Modal::default_area`]. + #[inline] + pub fn area(mut self, area: Area) -> Self { + self.area = area; + self + } + + /// Show the modal. + pub fn show(self, ctx: &Context, content: impl FnOnce(&mut Ui) -> T) -> ModalResponse { + let Self { + area, + backdrop_color, + frame, + } = self; + + let (is_top_modal, any_popup_open) = ctx.memory_mut(|mem| { + mem.set_modal_layer(area.layer()); + ( + mem.top_modal_layer() == Some(area.layer()), + mem.any_popup_open(), + ) + }); + let InnerResponse { + inner: (inner, backdrop_response), + response, + } = area.show(ctx, |ui| { + let bg_rect = ui.ctx().screen_rect(); + let bg_sense = Sense { + click: true, + drag: true, + focusable: false, + }; + let mut backdrop = ui.new_child(UiBuilder::new().sense(bg_sense).max_rect(bg_rect)); + backdrop.set_min_size(bg_rect.size()); + ui.painter().rect_filled(bg_rect, 0.0, backdrop_color); + let backdrop_response = backdrop.response(); + + let frame = frame.unwrap_or_else(|| Frame::popup(ui.style())); + + // We need the extra scope with the sense since frame can't have a sense and since we + // need to prevent the clicks from passing through to the backdrop. + let inner = ui + .scope_builder( + UiBuilder::new().sense(Sense { + click: true, + drag: true, + focusable: false, + }), + |ui| frame.show(ui, content).inner, + ) + .inner; + + (inner, backdrop_response) + }); + + ModalResponse { + response, + backdrop_response, + inner, + is_top_modal, + any_popup_open, + } + } +} + +/// The response of a modal dialog. +pub struct ModalResponse { + /// The response of the modal contents + pub response: Response, + + /// The response of the modal backdrop. + /// + /// A click on this means the user clicked outside the modal, + /// in which case you might want to close the modal. + pub backdrop_response: Response, + + /// The inner response from the content closure + pub inner: T, + + /// Is this the topmost modal? + pub is_top_modal: bool, + + /// Is there any popup open? + /// We need to check this before the modal contents are shown, so we can know if any popup + /// was open when checking if the escape key was clicked. + pub any_popup_open: bool, +} + +impl ModalResponse { + /// Should the modal be closed? + /// Returns true if: + /// - the backdrop was clicked + /// - this is the topmost modal, no popup is open and the escape key was pressed + pub fn should_close(&self) -> bool { + let ctx = &self.response.ctx; + + // this is a closure so that `Esc` is consumed only if the modal is topmost + let escape_clicked = + || ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape)); + + self.backdrop_response.clicked() + || (self.is_top_modal && !self.any_popup_open && escape_clicked()) + } +} diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index 56e3c44b150..a90e615aca7 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -87,17 +87,22 @@ pub fn show_tooltip_at_pointer( // Add a small exclusion zone around the pointer to avoid tooltips // covering what we're hovering over. - let mut exclusion_rect = Rect::from_center_size(pointer_pos, Vec2::splat(24.0)); + let mut pointer_rect = Rect::from_center_size(pointer_pos, Vec2::splat(24.0)); // Keep the left edge of the tooltip in line with the cursor: - exclusion_rect.min.x = pointer_pos.x; + pointer_rect.min.x = pointer_pos.x; + + // Transform global coords to layer coords: + if let Some(transform) = ctx.memory(|m| m.layer_transforms.get(&parent_layer).copied()) { + pointer_rect = transform.inverse() * pointer_rect; + } show_tooltip_at_dyn( ctx, parent_layer, widget_id, allow_placing_below, - &exclusion_rect, + &pointer_rect, Box::new(add_contents), ) }) @@ -155,6 +160,7 @@ fn show_tooltip_at_dyn<'c, R>( widget_rect: &Rect, add_contents: Box R + 'c>, ) -> R { + // Transform layer coords to global coords: let mut widget_rect = *widget_rect; if let Some(transform) = ctx.memory(|m| m.layer_transforms.get(&parent_layer).copied()) { widget_rect = transform * widget_rect; diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 458bf11229d..84f687783aa 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -205,7 +205,7 @@ struct Prepared { } impl Resize { - fn begin(&mut self, ui: &mut Ui) -> Prepared { + fn begin(&self, ui: &mut Ui) -> Prepared { let position = ui.available_rect_before_wrap().min; let id = self.id.unwrap_or_else(|| { let id_salt = self.id_salt.unwrap_or_else(|| Id::new("resize")); @@ -295,7 +295,7 @@ impl Resize { } } - pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { + pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R { let mut prepared = self.begin(ui); let ret = add_contents(&mut prepared.content_ui); self.end(ui, prepared); diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 3b3925c9dcd..3c14a02e5e1 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -39,7 +39,7 @@ pub struct State { scroll_start_offset_from_top_left: [Option; 2], /// Is the scroll sticky. This is true while scroll handle is in the end position - /// and remains that way until the user moves the scroll_handle. Once unstuck (false) + /// and remains that way until the user moves the `scroll_handle`. Once unstuck (false) /// it remains false until the scroll touches the end position, which reenables stickiness. scroll_stuck_to_end: Vec2b, @@ -499,6 +499,11 @@ struct Prepared { scrolling_enabled: bool, stick_to_end: Vec2b, + + /// If there was a scroll target before the [`ScrollArea`] was added this frame, it's + /// not for us to handle so we save it and restore it after this [`ScrollArea`] is done. + saved_scroll_target: [Option; 2], + animated: bool, } @@ -693,6 +698,10 @@ impl ScrollArea { } } + let saved_scroll_target = content_ui + .ctx() + .pass_state_mut(|state| std::mem::take(&mut state.scroll_target)); + Prepared { id, state, @@ -707,6 +716,7 @@ impl ScrollArea { viewport, scrolling_enabled, stick_to_end, + saved_scroll_target, animated, } } @@ -820,6 +830,7 @@ impl Prepared { viewport: _, scrolling_enabled, stick_to_end, + saved_scroll_target, animated, } = self; @@ -853,7 +864,7 @@ impl Prepared { let (start, end) = (range.min, range.max); let clip_start = clip_rect.min[d]; let clip_end = clip_rect.max[d]; - let mut spacing = ui.spacing().item_spacing[d]; + let mut spacing = content_ui.spacing().item_spacing[d]; let delta_update = if let Some(align) = align { let center_factor = align.to_factor(); @@ -902,6 +913,15 @@ impl Prepared { } } + // Restore scroll target meant for ScrollAreas up the stack (if any) + ui.ctx().pass_state_mut(|state| { + for d in 0..2 { + if saved_scroll_target[d].is_some() { + state.scroll_target[d] = saved_scroll_target[d].clone(); + }; + } + }); + let inner_rect = { // At this point this is the available size for the inner rect. let mut inner_size = inner_rect.size(); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 331430c7865..aa449849e32 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -552,13 +552,13 @@ impl ContextImpl { crate::profile_scope!("accesskit"); use crate::pass_state::AccessKitPassState; let id = crate::accesskit_root_id(); - let mut builder = accesskit::NodeBuilder::new(accesskit::Role::Window); + let mut root_node = accesskit::Node::new(accesskit::Role::Window); let pixels_per_point = viewport.input.pixels_per_point(); - builder.set_transform(accesskit::Affine::scale(pixels_per_point.into())); - let mut node_builders = IdMap::default(); - node_builders.insert(id, builder); + root_node.set_transform(accesskit::Affine::scale(pixels_per_point.into())); + let mut nodes = IdMap::default(); + nodes.insert(id, root_node); viewport.this_pass.accesskit_state = Some(AccessKitPassState { - node_builders, + nodes, parent_stack: vec![id], }); } @@ -640,9 +640,9 @@ impl ContextImpl { } #[cfg(feature = "accesskit")] - fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::NodeBuilder { + fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::Node { let state = self.viewport().this_pass.accesskit_state.as_mut().unwrap(); - let builders = &mut state.node_builders; + let builders = &mut state.nodes; if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) { entry.insert(Default::default()); let parent_id = state.parent_stack.last().unwrap(); @@ -1160,6 +1160,9 @@ impl Context { /// same widget, then `allow_focus` should only be true once (like in [`Ui::new`] (true) and [`Ui::remember_min_rect`] (false)). #[allow(clippy::too_many_arguments)] pub(crate) fn create_widget(&self, w: WidgetRect, allow_focus: bool) -> Response { + let interested_in_focus = + w.enabled && w.sense.focusable && self.memory(|mem| mem.allows_interaction(w.layer_id)); + // Remember this widget self.write(|ctx| { let viewport = ctx.viewport(); @@ -1169,12 +1172,12 @@ impl Context { // but also to know when we have reached the widget we are checking for cover. viewport.this_pass.widgets.insert(w.layer_id, w); - if allow_focus && w.sense.focusable { - ctx.memory.interested_in_focus(w.id); + if allow_focus && interested_in_focus { + ctx.memory.interested_in_focus(w.id, w.layer_id); } }); - if allow_focus && (!w.enabled || !w.sense.focusable || !w.layer_id.allow_interaction()) { + if allow_focus && !interested_in_focus { // Not interested or allowed input: self.memory_mut(|mem| mem.surrender_focus(w.id)); } @@ -1279,7 +1282,7 @@ impl Context { #[cfg(feature = "accesskit")] if enabled && sense.click - && input.has_accesskit_action_request(id, accesskit::Action::Default) + && input.has_accesskit_action_request(id, accesskit::Action::Click) { res.fake_primary_click = true; } @@ -2359,9 +2362,9 @@ impl ContextImpl { let root_id = crate::accesskit_root_id().accesskit_id(); let nodes = { state - .node_builders + .nodes .into_iter() - .map(|(id, builder)| (id.accesskit_id(), builder.build())) + .map(|(id, node)| (id.accesskit_id(), node)) .collect() }; let focus_id = self @@ -3270,7 +3273,7 @@ impl Context { pub fn accesskit_node_builder( &self, id: Id, - writer: impl FnOnce(&mut accesskit::NodeBuilder) -> R, + writer: impl FnOnce(&mut accesskit::Node) -> R, ) -> Option { self.write(|ctx| { ctx.viewport() @@ -3452,15 +3455,23 @@ impl Context { return Err(load::LoadError::NoImageLoaders); } + let mut format = None; + // Try most recently added loaders first (hence `.rev()`) for loader in image_loaders.iter().rev() { match loader.load(self, uri, size_hint) { Err(load::LoadError::NotSupported) => continue, + Err(load::LoadError::FormatNotSupported { detected_format }) => { + format = format.or(detected_format); + continue; + } result => return result, } } - Err(load::LoadError::NoMatchingImageLoader) + Err(load::LoadError::NoMatchingImageLoader { + detected_format: format, + }) } /// Try loading the texture from the given uri using any available texture loaders. diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index a1b9783280e..7987ea61225 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -529,6 +529,10 @@ pub enum Event { /// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`]. Screenshot { viewport_id: crate::ViewportId, + + /// Whatever was passed to [`crate::ViewportCommand::Screenshot`]. + user_data: crate::UserData, + image: std::sync::Arc, }, } diff --git a/crates/egui/src/data/key.rs b/crates/egui/src/data/key.rs index c43d1c5685d..a075b025a2f 100644 --- a/crates/egui/src/data/key.rs +++ b/crates/egui/src/data/key.rs @@ -55,7 +55,7 @@ pub enum Key { // `]` CloseBracket, - /// \`, also known as "backquote" or "grave" + /// Also known as "backquote" or "grave" Backtick, /// `-` diff --git a/crates/egui/src/data/mod.rs b/crates/egui/src/data/mod.rs index bfe1e8a327d..f6f267dd06a 100644 --- a/crates/egui/src/data/mod.rs +++ b/crates/egui/src/data/mod.rs @@ -3,5 +3,7 @@ pub mod input; mod key; pub mod output; +mod user_data; pub use key::Key; +pub use user_data::UserData; diff --git a/crates/egui/src/data/user_data.rs b/crates/egui/src/data/user_data.rs new file mode 100644 index 00000000000..20bf5e1a123 --- /dev/null +++ b/crates/egui/src/data/user_data.rs @@ -0,0 +1,74 @@ +use std::{any::Any, sync::Arc}; + +/// A wrapper around `dyn Any`, used for passing custom user data +/// to [`crate::ViewportCommand::Screenshot`]. +#[derive(Clone, Debug, Default)] +pub struct UserData { + /// A user value given to the screenshot command, + /// that will be returned in [`crate::Event::Screenshot`]. + pub data: Option>, +} + +impl UserData { + /// You can also use [`Self::default`]. + pub fn new(user_info: impl Any + Send + Sync) -> Self { + Self { + data: Some(Arc::new(user_info)), + } + } +} + +impl PartialEq for UserData { + fn eq(&self, other: &Self) -> bool { + match (&self.data, &other.data) { + (Some(a), Some(b)) => Arc::ptr_eq(a, b), + (None, None) => true, + _ => false, + } + } +} + +impl Eq for UserData {} + +impl std::hash::Hash for UserData { + fn hash(&self, state: &mut H) { + self.data.as_ref().map(Arc::as_ptr).hash(state); + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for UserData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_none() // can't serialize an `Any` + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for UserData { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct UserDataVisitor; + + impl<'de> serde::de::Visitor<'de> for UserDataVisitor { + type Value = UserData; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a None value") + } + + fn visit_none(self) -> Result + where + E: serde::de::Error, + { + Ok(UserData::default()) + } + } + + deserializer.deserialize_option(UserDataVisitor) + } +} diff --git a/crates/egui/src/drag_and_drop.rs b/crates/egui/src/drag_and_drop.rs index 13da9d314c1..e1997800834 100644 --- a/crates/egui/src/drag_and_drop.rs +++ b/crates/egui/src/drag_and_drop.rs @@ -23,27 +23,42 @@ pub struct DragAndDrop { impl DragAndDrop { pub(crate) fn register(ctx: &Context) { - ctx.on_end_pass("debug_text", std::sync::Arc::new(Self::end_pass)); + ctx.on_begin_pass("drag_and_drop_begin_pass", Arc::new(Self::begin_pass)); + ctx.on_end_pass("drag_and_drop_end_pass", Arc::new(Self::end_pass)); } - fn end_pass(ctx: &Context) { - let abort_dnd = - ctx.input(|i| i.pointer.any_released() || i.key_pressed(crate::Key::Escape)); - - let mut is_dragging = false; + /// Interrupt drag-and-drop if the user presses the escape key. + /// + /// This needs to happen at frame start so we can properly capture the escape key. + fn begin_pass(ctx: &Context) { + let has_any_payload = Self::has_any_payload(ctx); - ctx.data_mut(|data| { - let state = data.get_temp_mut_or_default::(Id::NULL); + if has_any_payload { + let abort_dnd_due_to_escape_key = + ctx.input_mut(|i| i.consume_key(crate::Modifiers::NONE, crate::Key::Escape)); - if abort_dnd { - state.payload = None; + if abort_dnd_due_to_escape_key { + Self::clear_payload(ctx); } + } + } - is_dragging = state.payload.is_some(); - }); + /// Interrupt drag-and-drop if the user releases the mouse button. + /// + /// This is a catch-all safety net in case user code doesn't capture the drag payload itself. + /// This must happen at end-of-frame such that we don't shadow the mouse release event from user + /// code. + fn end_pass(ctx: &Context) { + let has_any_payload = Self::has_any_payload(ctx); + + if has_any_payload { + let abort_dnd_due_to_mouse_release = ctx.input_mut(|i| i.pointer.any_released()); - if is_dragging { - ctx.set_cursor_icon(CursorIcon::Grabbing); + if abort_dnd_due_to_mouse_release { + Self::clear_payload(ctx); + } else { + ctx.set_cursor_icon(CursorIcon::Grabbing); + } } } diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 3c4986fcad0..0342f6f5227 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -227,7 +227,7 @@ impl GridLayout { self.col += 1; } - fn paint_row(&mut self, cursor: &Rect, painter: &Painter) { + fn paint_row(&self, cursor: &Rect, painter: &Painter) { // handle row color painting based on color-picker function let Some(color_picker) = self.color_picker.as_ref() else { return; @@ -450,7 +450,7 @@ impl Grid { ui.allocate_new_ui(ui_builder, |ui| { ui.horizontal(|ui| { let is_color = color_picker.is_some(); - let mut grid = GridLayout { + let grid = GridLayout { num_columns, color_picker, min_cell_size: vec2(min_col_width, min_row_height), diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 7f743ee709a..99c166cf443 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -889,9 +889,9 @@ impl Default for PointerState { press_start_time: None, has_moved_too_much_for_a_click: false, started_decidedly_dragging: false, - last_click_time: std::f64::NEG_INFINITY, - last_last_click_time: std::f64::NEG_INFINITY, - last_move_time: std::f64::NEG_INFINITY, + last_click_time: f64::NEG_INFINITY, + last_last_click_time: f64::NEG_INFINITY, + last_move_time: f64::NEG_INFINITY, pointer_events: vec![], input_options: Default::default(), } diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 9105ece2591..7966e12ad2a 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -11,9 +11,6 @@ pub enum Order { /// Painted behind all floating windows Background, - /// Special layer between panels and windows - PanelResizeLine, - /// Normal moveable windows that you reorder by click Middle, @@ -30,10 +27,9 @@ pub enum Order { } impl Order { - const COUNT: usize = 6; + const COUNT: usize = 5; const ALL: [Self; Self::COUNT] = [ Self::Background, - Self::PanelResizeLine, Self::Middle, Self::Foreground, Self::Tooltip, @@ -44,12 +40,9 @@ impl Order { #[inline(always)] pub fn allow_interaction(&self) -> bool { match self { - Self::Background - | Self::PanelResizeLine - | Self::Middle - | Self::Foreground - | Self::Tooltip - | Self::Debug => true, + Self::Background | Self::Middle | Self::Foreground | Self::Tooltip | Self::Debug => { + true + } } } @@ -57,7 +50,6 @@ impl Order { pub fn short_debug_format(&self) -> &'static str { match self { Self::Background => "backg", - Self::PanelResizeLine => "panel", Self::Middle => "middl", Self::Foreground => "foreg", Self::Tooltip => "toolt", @@ -95,6 +87,7 @@ impl LayerId { } #[inline(always)] + #[deprecated = "Use `Memory::allows_interaction` instead"] pub fn allow_interaction(&self) -> bool { self.order.allow_interaction() } diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index 32fd0d03ac4..0c9bb494133 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -2,7 +2,7 @@ use crate::{ emath::{pos2, vec2, Align2, NumExt, Pos2, Rect, Vec2}, Align, }; -use std::f32::INFINITY; +const INFINITY: f32 = f32::INFINITY; // ---------------------------------------------------------------------------- diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 79784c984a7..8c70ca5a812 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -3,7 +3,7 @@ //! Try the live web demo: . Read more about egui at . //! //! `egui` is in heavy development, with each new version having breaking changes. -//! You need to have rust 1.77.0 or later to use `egui`. +//! You need to have rust 1.80.0 or later to use `egui`. //! //! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template) //! which uses [`eframe`](https://docs.rs/eframe). @@ -393,6 +393,7 @@ #![allow(clippy::manual_range_contains)] mod animation_manager; +pub mod cache; pub mod containers; mod context; mod data; @@ -471,7 +472,7 @@ pub use self::{ output::{ self, CursorIcon, FullOutput, OpenUrl, PlatformOutput, UserAttentionType, WidgetInfo, }, - Key, + Key, UserData, }, drag_and_drop::DragAndDrop, epaint::text::TextWrapMode, diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index b6711de3c52..26950eb2add 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -77,16 +77,19 @@ pub enum LoadError { /// Programmer error: There are no image loaders installed. NoImageLoaders, - /// A specific loader does not support this scheme, protocol or image format. + /// A specific loader does not support this scheme or protocol. NotSupported, + /// A specific loader does not support the format of the image. + FormatNotSupported { detected_format: Option }, + /// Programmer error: Failed to find the bytes for this image because /// there was no [`BytesLoader`] supporting the scheme. NoMatchingBytesLoader, /// Programmer error: Failed to parse the bytes as an image because - /// there was no [`ImageLoader`] supporting the scheme. - NoMatchingImageLoader, + /// there was no [`ImageLoader`] supporting the format. + NoMatchingImageLoader { detected_format: Option }, /// Programmer error: no matching [`TextureLoader`]. /// Because of the [`DefaultTextureLoader`], this error should never happen. @@ -96,6 +99,20 @@ pub enum LoadError { Loading(String), } +impl LoadError { + /// Returns the (approximate) size of the error message in bytes. + pub fn byte_size(&self) -> usize { + match self { + Self::FormatNotSupported { detected_format } + | Self::NoMatchingImageLoader { detected_format } => { + detected_format.as_ref().map_or(0, |s| s.len()) + } + Self::Loading(message) => message.len(), + _ => std::mem::size_of::(), + } + } +} + impl Display for LoadError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -105,12 +122,15 @@ impl Display for LoadError { Self::NoMatchingBytesLoader => f.write_str("No matching BytesLoader. Either you need to call Context::include_bytes, or install some more bytes loaders, e.g. using egui_extras."), - Self::NoMatchingImageLoader => f.write_str("No matching ImageLoader. Either you need to call Context::include_bytes, or install some more bytes loaders, e.g. using egui_extras."), + Self::NoMatchingImageLoader { detected_format: None } => f.write_str("No matching ImageLoader. Either no ImageLoader is installed or the image is corrupted / has an unsupported format."), + Self::NoMatchingImageLoader { detected_format: Some(detected_format) } => write!(f, "No matching ImageLoader for format: {detected_format:?}. Make sure you enabled the necessary features on the image crate."), Self::NoMatchingTextureLoader => f.write_str("No matching TextureLoader. Did you remove the default one?"), Self::NotSupported => f.write_str("Image scheme or URI not supported by this loader"), + Self::FormatNotSupported { detected_format } => write!(f, "Image format not supported by this loader: {detected_format:?}"), + Self::Loading(message) => f.write_str(message), } } diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index 75183bdd15b..5e94e6c0915 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -54,7 +54,7 @@ pub struct Memory { /// so as not to lock the UI thread. /// /// ``` - /// use egui::util::cache::{ComputerMut, FrameCache}; + /// use egui::cache::{ComputerMut, FrameCache}; /// /// #[derive(Default)] /// struct CharCounter {} @@ -72,7 +72,7 @@ pub struct Memory { /// }); /// ``` #[cfg_attr(feature = "persistence", serde(skip))] - pub caches: crate::util::cache::CacheStorage, + pub caches: crate::cache::CacheStorage, // ------------------------------------------ /// new fonts that will be applied at the start of the next frame @@ -513,6 +513,12 @@ pub(crate) struct Focus { /// Set when looking for widget with navigational keys like arrows, tab, shift+tab. focus_direction: FocusDirection, + /// The top-most modal layer from the previous frame. + top_modal_layer: Option, + + /// The top-most modal layer from the current frame. + top_modal_layer_current_frame: Option, + /// A cache of widget IDs that are interested in focus with their corresponding rectangles. focus_widgets_cache: IdMap, } @@ -623,6 +629,8 @@ impl Focus { self.focused_widget = None; } } + + self.top_modal_layer = self.top_modal_layer_current_frame.take(); } pub(crate) fn had_focus_last_frame(&self, id: Id) -> bool { @@ -676,6 +684,14 @@ impl Focus { self.last_interested = Some(id); } + fn set_modal_layer(&mut self, layer_id: LayerId) { + self.top_modal_layer_current_frame = Some(layer_id); + } + + pub(crate) fn top_modal_layer(&self) -> Option { + self.top_modal_layer + } + fn reset_focus(&mut self) { self.focus_direction = FocusDirection::None; } @@ -720,7 +736,7 @@ impl Focus { let current_rect = self.focus_widgets_cache.get(¤t_focused.id)?; - let mut best_score = std::f32::INFINITY; + let mut best_score = f32::INFINITY; let mut best_id = None; for (candidate_id, candidate_rect) in &self.focus_widgets_cache { @@ -802,7 +818,15 @@ impl Memory { /// Top-most layer at the given position. pub fn layer_id_at(&self, pos: Pos2) -> Option { - self.areas().layer_id_at(pos, &self.layer_transforms) + self.areas() + .layer_id_at(pos, &self.layer_transforms) + .and_then(|layer_id| { + if self.is_above_modal_layer(layer_id) { + Some(layer_id) + } else { + self.top_modal_layer() + } + }) } /// An iterator over all layers. Back-to-front, top is last. @@ -877,6 +901,30 @@ impl Memory { } } + /// Returns true if + /// - this layer is the top-most modal layer or above it + /// - there is no modal layer + pub fn is_above_modal_layer(&self, layer_id: LayerId) -> bool { + if let Some(modal_layer) = self.focus().and_then(|f| f.top_modal_layer) { + matches!( + self.areas().compare_order(layer_id, modal_layer), + std::cmp::Ordering::Equal | std::cmp::Ordering::Greater + ) + } else { + true + } + } + + /// Does this layer allow interaction? + /// Returns true if + /// - the layer is not behind a modal layer + /// - the [`Order`] allows interaction + pub fn allows_interaction(&self, layer_id: LayerId) -> bool { + let is_above_modal_layer = self.is_above_modal_layer(layer_id); + let ordering_allows_interaction = layer_id.order.allow_interaction(); + is_above_modal_layer && ordering_allows_interaction + } + /// Register this widget as being interested in getting keyboard focus. /// This will allow the user to select it with tab and shift-tab. /// This is normally done automatically when handling interactions, @@ -884,11 +932,36 @@ impl Memory { /// e.g. before deciding which type of underlying widget to use, /// as in the [`crate::DragValue`] widget, so a widget can be focused /// and rendered correctly in a single frame. + /// + /// Pass in the `layer_id` of the layer that the widget is in. #[inline(always)] - pub fn interested_in_focus(&mut self, id: Id) { + pub fn interested_in_focus(&mut self, id: Id, layer_id: LayerId) { + if !self.allows_interaction(layer_id) { + return; + } self.focus_mut().interested_in_focus(id); } + /// Limit focus to widgets on the given layer and above. + /// If this is called multiple times per frame, the top layer wins. + pub fn set_modal_layer(&mut self, layer_id: LayerId) { + if let Some(current) = self.focus().and_then(|f| f.top_modal_layer_current_frame) { + if matches!( + self.areas().compare_order(layer_id, current), + std::cmp::Ordering::Less + ) { + return; + } + } + + self.focus_mut().set_modal_layer(layer_id); + } + + /// Get the top modal layer (from the previous frame). + pub fn top_modal_layer(&self) -> Option { + self.focus()?.top_modal_layer() + } + /// Stop editing the active [`TextEdit`](crate::TextEdit) (if any). #[inline(always)] pub fn stop_text_input(&mut self) { @@ -1037,6 +1110,9 @@ impl Memory { // ---------------------------------------------------------------------------- +/// Map containing the index of each layer in the order list, for quick lookups. +type OrderMap = HashMap; + /// Keeps track of [`Area`](crate::containers::area::Area)s, which are free-floating [`Ui`](crate::Ui)s. /// These [`Area`](crate::containers::area::Area)s can be in any [`Order`]. #[derive(Clone, Debug, Default)] @@ -1048,6 +1124,9 @@ pub struct Areas { /// Back-to-front, top is last. order: Vec, + /// Actual order of the layers, pre-calculated each frame. + order_map: OrderMap, + visible_last_frame: ahash::HashSet, visible_current_frame: ahash::HashSet, @@ -1079,12 +1158,28 @@ impl Areas { } /// For each layer, which [`Self::order`] is it in? - pub(crate) fn order_map(&self) -> HashMap { - self.order + pub(crate) fn order_map(&self) -> &OrderMap { + &self.order_map + } + + /// Compare the order of two layers, based on the order list from last frame. + /// May return [`std::cmp::Ordering::Equal`] if the layers are not in the order list. + pub(crate) fn compare_order(&self, a: LayerId, b: LayerId) -> std::cmp::Ordering { + if let (Some(a), Some(b)) = (self.order_map.get(&a), self.order_map.get(&b)) { + a.cmp(b) + } else { + a.order.cmp(&b.order) + } + } + + /// Calculates the order map. + fn calculate_order_map(&mut self) { + self.order_map = self + .order .iter() .enumerate() .map(|(i, id)| (*id, i)) - .collect() + .collect(); } pub(crate) fn set_state(&mut self, layer_id: LayerId, state: area::AreaState) { @@ -1209,6 +1304,7 @@ impl Areas { }; order.splice(parent_pos..=parent_pos, moved_layers); } + self.calculate_order_map(); } } diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index 5b8ae77df37..232450e3862 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -344,6 +344,12 @@ impl Painter { }) } + /// Paints a line connecting the points. + /// NOTE: all coordinates are screen coordinates! + pub fn line(&self, points: Vec, stroke: impl Into) -> ShapeIdx { + self.add(Shape::line(points, stroke)) + } + /// Paints a horizontal line. pub fn hline(&self, x: impl Into, y: f32, stroke: impl Into) -> ShapeIdx { self.add(Shape::hline(x, y, stroke.into())) diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index bbeaca9b3c6..da42e0932ae 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -70,7 +70,7 @@ impl ScrollTarget { #[cfg(feature = "accesskit")] #[derive(Clone)] pub struct AccessKitPassState { - pub node_builders: IdMap, + pub nodes: IdMap, pub parent_stack: Vec, } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 9051d3a2d7f..f49383c70d1 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -988,7 +988,7 @@ impl Response { } #[cfg(feature = "accesskit")] - pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::NodeBuilder) { + pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::Node) { if !self.enabled { builder.set_disabled(); } @@ -1001,15 +1001,15 @@ impl Response { if self.sense.focusable { builder.add_action(accesskit::Action::Focus); } - if self.sense.click && builder.default_action_verb().is_none() { - builder.set_default_action_verb(accesskit::DefaultActionVerb::Click); + if self.sense.click { + builder.add_action(accesskit::Action::Click); } } #[cfg(feature = "accesskit")] fn fill_accesskit_node_from_widget_info( &self, - builder: &mut accesskit::NodeBuilder, + builder: &mut accesskit::Node, info: crate::WidgetInfo, ) { use crate::WidgetType; @@ -1039,7 +1039,11 @@ impl Response { builder.set_disabled(); } if let Some(label) = info.label { - builder.set_name(label); + if matches!(builder.role(), Role::Label) { + builder.set_value(label); + } else { + builder.set_label(label); + } } if let Some(value) = info.current_text_value { builder.set_value(value); diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 0f6c9633ebd..14b5aecd6ee 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -288,7 +288,7 @@ pub struct Style { /// If true and scrolling is enabled for only one direction, allow horizontal scrolling without pressing shift pub always_scroll_the_only_direction: bool, - /// The animation that should be used when scrolling a [`crate::ScrollArea`] using e.g. [Ui::scroll_to_rect]. + /// The animation that should be used when scrolling a [`crate::ScrollArea`] using e.g. [`Ui::scroll_to_rect`]. pub scroll_animation: ScrollAnimation, } diff --git a/crates/egui/src/text_selection/accesskit_text.rs b/crates/egui/src/text_selection/accesskit_text.rs index f56ab9eddb1..d0c3869038d 100644 --- a/crates/egui/src/text_selection/accesskit_text.rs +++ b/crates/egui/src/text_selection/accesskit_text.rs @@ -29,8 +29,6 @@ pub fn update_accesskit_for_text_widget( }); } - builder.set_default_action_verb(accesskit::DefaultActionVerb::Focus); - builder.set_role(role); parent_id @@ -44,7 +42,7 @@ pub fn update_accesskit_for_text_widget( for (row_index, row) in galley.rows.iter().enumerate() { let row_id = parent_id.with(row_index); ctx.accesskit_node_builder(row_id, |builder| { - builder.set_role(accesskit::Role::InlineTextBox); + builder.set_role(accesskit::Role::TextRun); let rect = row.rect.translate(galley_pos.to_vec2()); builder.set_bounds(accesskit::Rect { x0: rect.min.x.into(), diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index d86f9dc56c2..2a31d1e1270 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -96,8 +96,19 @@ pub fn paint_text_selection( pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { let stroke = visuals.text_cursor.stroke; - let top = cursor_rect.center_top(); - let bottom = cursor_rect.center_bottom(); + // Ensure the cursor is aligned to the pixel grid for whole number widths. + // See https://github.com/emilk/egui/issues/5164 + let (top, bottom) = if (stroke.width as usize) % 2 == 0 { + ( + painter.round_pos_to_pixels(cursor_rect.center_top()), + painter.round_pos_to_pixels(cursor_rect.center_bottom()), + ) + } else { + ( + painter.round_pos_to_pixel_center(cursor_rect.center_top()), + painter.round_pos_to_pixel_center(cursor_rect.center_bottom()), + ) + }; painter.line_segment([top, bottom], (stroke.width, stroke.color)); @@ -121,14 +132,14 @@ pub fn paint_text_cursor( ui: &Ui, painter: &Painter, primary_cursor_rect: Rect, - time_since_last_edit: f64, + time_since_last_interaction: f64, ) { if ui.visuals().text_cursor.blink { let on_duration = ui.visuals().text_cursor.on_duration; let off_duration = ui.visuals().text_cursor.off_duration; let total_duration = on_duration + off_duration; - let time_in_cycle = (time_since_last_edit % (total_duration as f64)) as f32; + let time_in_cycle = (time_since_last_interaction % (total_duration as f64)) as f32; let wake_in = if time_in_cycle < on_duration { // Cursor is visible diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 7aae3f2fbeb..7aa131bec7a 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -24,6 +24,9 @@ pub enum UiKind { /// A bottom [`crate::TopBottomPanel`]. BottomPanel, + /// A modal [`crate::Modal`]. + Modal, + /// A [`crate::Frame`]. Frame, @@ -82,6 +85,7 @@ impl UiKind { Self::Window | Self::Menu + | Self::Modal | Self::Popup | Self::Tooltip | Self::Picker @@ -228,6 +232,12 @@ impl UiStack { self.kind().map_or(false, |kind| kind.is_panel()) } + /// Is this [`crate::Ui`] an [`crate::Area`]? + #[inline] + pub fn is_area_ui(&self) -> bool { + self.kind().map_or(false, |kind| kind.is_area()) + } + /// Is this a root [`crate::Ui`], i.e. created with [`crate::Ui::new()`]? #[inline] pub fn is_root_ui(&self) -> bool { diff --git a/crates/egui/src/util/mod.rs b/crates/egui/src/util/mod.rs index 55e93eb0400..de62b961822 100644 --- a/crates/egui/src/util/mod.rs +++ b/crates/egui/src/util/mod.rs @@ -1,6 +1,5 @@ //! Miscellaneous tools used by the rest of egui. -pub mod cache; pub(crate) mod fixed_cache; pub mod id_type_map; pub mod undoer; @@ -9,3 +8,7 @@ pub use id_type_map::IdTypeMap; pub use epaint::emath::History; pub use epaint::util::{hash, hash_with}; + +/// Deprecated alias for [`crate::cache`]. +#[deprecated = "Use egui::cache instead"] +pub use crate::cache; diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 31cbc623e39..d8b26429c77 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -1058,8 +1058,8 @@ pub enum ViewportCommand { /// Take a screenshot. /// - /// The results are returned in `crate::Event::Screenshot`. - Screenshot, + /// The results are returned in [`crate::Event::Screenshot`]. + Screenshot(crate::UserData), /// Request cut of the current selection /// @@ -1100,6 +1100,8 @@ impl ViewportCommand { } } +// ---------------------------------------------------------------------------- + /// Describes a viewport, i.e. a native window. /// /// This is returned by [`crate::Context::run`] on each frame, and should be applied diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 28a578ee694..e4355b49e8d 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -37,6 +37,7 @@ pub struct Button<'a> { min_size: Vec2, rounding: Option, selected: bool, + image_tint_follows_text_color: bool, } impl<'a> Button<'a> { @@ -70,6 +71,7 @@ impl<'a> Button<'a> { min_size: Vec2::ZERO, rounding: None, selected: false, + image_tint_follows_text_color: false, } } @@ -156,6 +158,18 @@ impl<'a> Button<'a> { self } + /// If true, the tint of the image is multiplied by the widget text color. + /// + /// This makes sense for images that are white, that should have the same color as the text color. + /// This will also make the icon color depend on hover state. + /// + /// Default: `false`. + #[inline] + pub fn image_tint_follows_text_color(mut self, image_tint_follows_text_color: bool) -> Self { + self.image_tint_follows_text_color = image_tint_follows_text_color; + self + } + /// Show some text on the right side of the button, in weak color. /// /// Designed for menu buttons, for setting a keyboard shortcut text (e.g. `Ctrl+S`). @@ -190,6 +204,7 @@ impl Widget for Button<'_> { min_size, rounding, selected, + image_tint_follows_text_color, } = self; let frame = frame.unwrap_or_else(|| ui.visuals().button_frame); @@ -319,12 +334,16 @@ impl Widget for Button<'_> { let image_rect = Rect::from_min_size(image_pos, image_size); cursor_x += image_size.x; let tlr = image.load_for_size(ui.ctx(), image_size); + let mut image_options = image.image_options().clone(); + if image_tint_follows_text_color { + image_options.tint = image_options.tint * visuals.text_color(); + } widgets::image::paint_texture_load_result( ui, &tlr, image_rect, image.show_loading_spinner, - image.image_options(), + &image_options, ); response = widgets::image::texture_load_result_response( &image.source(ui.ctx()), diff --git a/crates/egui/src/widgets/color_picker.rs b/crates/egui/src/widgets/color_picker.rs index a00d6f9ac3b..9a605905968 100644 --- a/crates/egui/src/widgets/color_picker.rs +++ b/crates/egui/src/widgets/color_picker.rs @@ -165,8 +165,11 @@ fn color_slider_1d(ui: &mut Ui, value: &mut f32, color_at: impl Fn(f32) -> Color /// * `x_value` - X axis, either saturation or value (0.0-1.0). /// * `y_value` - Y axis, either saturation or value (0.0-1.0). /// * `color_at` - A function that dictates how the mix of saturation and value will be displayed in the 2d slider. -/// E.g.: `|x_value, y_value| HsvaGamma { h: 1.0, s: x_value, v: y_value, a: 1.0 }.into()` displays the colors as follows: top-left: white \[s: 0.0, v: 1.0], top-right: fully saturated color \[s: 1.0, v: 1.0], bottom-right: black \[s: 0.0, v: 1.0]. /// +/// e.g.: `|x_value, y_value| HsvaGamma { h: 1.0, s: x_value, v: y_value, a: 1.0 }.into()` displays the colors as follows: +/// * top-left: white `[s: 0.0, v: 1.0]` +/// * top-right: fully saturated color `[s: 1.0, v: 1.0]` +/// * bottom-right: black `[s: 0.0, v: 1.0].` fn color_slider_2d( ui: &mut Ui, x_value: &mut f32, diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index feae91ade1e..a5b8c25b2f8 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -452,7 +452,7 @@ impl<'a> Widget for DragValue<'a> { // in button mode for just one frame. This is important for // screen readers. let is_kb_editing = ui.memory_mut(|mem| { - mem.interested_in_focus(id); + mem.interested_in_focus(id, ui.layer_id()); mem.has_focus(id) }); @@ -686,7 +686,7 @@ impl<'a> Widget for DragValue<'a> { } // The name field is set to the current value by the button, // but we don't want it set that way on this widget type. - builder.clear_name(); + builder.clear_label(); // Always expose the value as a string. This makes the widget // more stable to accessibility users as it switches // between edit and button modes. This is particularly important diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 3c2bb973f88..7dc3a5cd568 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -1030,7 +1030,7 @@ impl<'a> Widget for Slider<'a> { // Logarithmic sliders are allowed to include zero and infinity, // even though mathematically it doesn't make sense. -use std::f64::INFINITY; +const INFINITY: f64 = f64::INFINITY; /// When the user asks for an infinitely large range (e.g. logarithmic from zero), /// give a scale that this many orders of magnitude in size. diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 5ce5749b97d..012d8c8f0d5 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use emath::Rect; use epaint::text::{cursor::CCursor, Galley, LayoutJob}; use crate::{ @@ -602,6 +603,8 @@ impl<'t> TextEdit<'t> { if did_interact || response.clicked() { ui.memory_mut(|mem| mem.request_focus(response.id)); + + state.last_interaction_time = ui.ctx().input(|i| i.time); } } } @@ -720,6 +723,16 @@ impl<'t> TextEdit<'t> { } } + // Allocate additional space if edits were made this frame that changed the size. This is important so that, + // if there's a ScrollArea, it can properly scroll to the cursor. + let extra_size = galley.size() - rect.size(); + if extra_size.x > 0.0 || extra_size.y > 0.0 { + ui.allocate_rect( + Rect::from_min_size(outer_rect.max, extra_size), + Sense::hover(), + ); + } + painter.galley(galley_pos, galley.clone(), text_color); if has_focus { @@ -727,16 +740,15 @@ impl<'t> TextEdit<'t> { let primary_cursor_rect = cursor_rect(galley_pos, &galley, &cursor_range.primary, row_height); - let is_fully_visible = ui.clip_rect().contains_rect(rect); // TODO(emilk): remove this HACK workaround for https://github.com/emilk/egui/issues/1531 - if (response.changed || selection_changed) && !is_fully_visible { + if response.changed || selection_changed { // Scroll to keep primary cursor in view: - ui.scroll_to_rect(primary_cursor_rect, None); + ui.scroll_to_rect(primary_cursor_rect + margin, None); } if text.is_mutable() && interactive { let now = ui.ctx().input(|i| i.time); if response.changed || selection_changed { - state.last_edit_time = now; + state.last_interaction_time = now; } // Only show (and blink) cursor if the egui viewport has focus. @@ -749,7 +761,7 @@ impl<'t> TextEdit<'t> { ui, &painter, primary_cursor_rect, - now - state.last_edit_time, + now - state.last_interaction_time, ); } diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index e73664a1293..c10a88274ef 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -53,10 +53,10 @@ pub struct TextEditState { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) singleline_offset: f32, - /// When did the user last press a key? + /// When did the user last press a key or click on the `TextEdit`. /// Used to pause the cursor animation when typing. #[cfg_attr(feature = "serde", serde(skip))] - pub(crate) last_edit_time: f64, + pub(crate) last_interaction_time: f64, } impl TextEditState { @@ -89,6 +89,7 @@ impl TextEditState { self.undoer.lock().clone() } + #[allow(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability pub fn set_undoer(&mut self, undoer: TextEditUndoer) { *self.undoer.lock() = undoer; } diff --git a/crates/egui/tests/accesskit.rs b/crates/egui/tests/accesskit.rs index 1354198d47d..83efeab3f37 100644 --- a/crates/egui/tests/accesskit.rs +++ b/crates/egui/tests/accesskit.rs @@ -46,7 +46,7 @@ fn button_node() { .find(|(_, node)| node.role() == Role::Button) .expect("Button should exist in the accesskit output"); - assert_eq!(button.name(), Some(button_text)); + assert_eq!(button.label(), Some(button_text)); assert!(!button.is_disabled()); } @@ -72,7 +72,7 @@ fn disabled_button_node() { .find(|(_, node)| node.role() == Role::Button) .expect("Button should exist in the accesskit output"); - assert_eq!(button.name(), Some(button_text)); + assert_eq!(button.label(), Some(button_text)); assert!(button.is_disabled()); } @@ -97,7 +97,7 @@ fn toggle_button_node() { .find(|(_, node)| node.role() == Role::Button) .expect("Toggle button should exist in the accesskit output"); - assert_eq!(toggle.name(), Some(button_text)); + assert_eq!(toggle.label(), Some(button_text)); assert!(!toggle.is_disabled()); } @@ -165,14 +165,14 @@ fn accesskit_output_single_egui_frame(run_ui: impl FnMut(&Context)) -> TreeUpdat } #[track_caller] -fn assert_button_exists(tree: &TreeUpdate, name: &str, parent: NodeId) { +fn assert_button_exists(tree: &TreeUpdate, label: &str, parent: NodeId) { let (node_id, _) = tree .nodes .iter() .find(|(_, node)| { - !node.is_hidden() && node.role() == Role::Button && node.name() == Some(name) + !node.is_hidden() && node.role() == Role::Button && node.label() == Some(label) }) - .expect("No visible button with that name exists."); + .expect("No visible button with that label exists."); assert_parent_child(tree, parent, *node_id); } @@ -183,7 +183,7 @@ fn assert_window_exists(tree: &TreeUpdate, title: &str, parent: NodeId) -> NodeI .nodes .iter() .find(|(_, node)| { - !node.is_hidden() && node.role() == Role::Window && node.name() == Some(title) + !node.is_hidden() && node.role() == Role::Window && node.label() == Some(title) }) .expect("No visible window with that title exists."); diff --git a/crates/egui/tests/regression_tests.rs b/crates/egui/tests/regression_tests.rs new file mode 100644 index 00000000000..f54cf1ffcf6 --- /dev/null +++ b/crates/egui/tests/regression_tests.rs @@ -0,0 +1,30 @@ +use egui::Button; +use egui_kittest::kittest::Queryable; +use egui_kittest::Harness; + +#[test] +pub fn focus_should_skip_over_disabled_buttons() { + let mut harness = Harness::new_ui(|ui| { + ui.add(Button::new("Button 1")); + ui.add_enabled(false, Button::new("Button Disabled")); + ui.add(Button::new("Button 3")); + }); + + harness.press_key(egui::Key::Tab); + harness.run(); + + let button_1 = harness.get_by_label("Button 1"); + assert!(button_1.is_focused()); + + harness.press_key(egui::Key::Tab); + harness.run(); + + let button_3 = harness.get_by_label("Button 3"); + assert!(button_3.is_focused()); + + harness.press_key(egui::Key::Tab); + harness.run(); + + let button_1 = harness.get_by_label("Button 1"); + assert!(button_1.is_focused()); +} diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 23ef8599dba..a66b6a7587f 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -27,7 +27,12 @@ web_app = ["http", "persistence"] http = ["ehttp", "image", "poll-promise", "egui_extras/image"] image_viewer = ["image", "egui_extras/all_loaders", "rfd"] -persistence = ["eframe/persistence", "egui/persistence", "serde", "egui_extras/serde"] +persistence = [ + "eframe/persistence", + "egui_extras/serde", + "egui/persistence", + "serde", +] puffin = ["eframe/puffin", "dep:puffin", "dep:puffin_http"] serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"] syntect = ["egui_demo_lib/syntect"] @@ -74,7 +79,7 @@ env_logger = { version = "0.10", default-features = false, features = [ "auto-color", "humantime", ] } -rfd = { version = "0.13", optional = true } +rfd = { version = "0.15", optional = true } # web: [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/egui_demo_app/src/frame_history.rs b/crates/egui_demo_app/src/frame_history.rs index 535d6d9f95f..a6a3fbeeb3f 100644 --- a/crates/egui_demo_app/src/frame_history.rs +++ b/crates/egui_demo_app/src/frame_history.rs @@ -32,7 +32,7 @@ impl FrameHistory { 1.0 / self.frame_times.mean_time_interval().unwrap_or_default() } - pub fn ui(&mut self, ui: &mut egui::Ui) { + pub fn ui(&self, ui: &mut egui::Ui) { ui.label(format!( "Mean CPU usage: {:.2} ms / frame", 1e3 * self.mean_frame_time() diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index bd313fb0c08..cbed988fa68 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -111,11 +111,19 @@ impl Anchor { Self::Rendering, ] } + + #[cfg(target_arch = "wasm32")] + fn from_str_case_insensitive(anchor: &str) -> Option { + let anchor = anchor.to_lowercase(); + Self::all().into_iter().find(|x| x.to_string() == anchor) + } } impl std::fmt::Display for Anchor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self:?}") + let mut name = format!("{self:?}"); + name.make_ascii_lowercase(); + f.write_str(&name) } } @@ -263,11 +271,15 @@ impl eframe::App for WrapApp { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { #[cfg(target_arch = "wasm32")] - if let Some(anchor) = frame.info().web_info.location.hash.strip_prefix('#') { - let anchor = Anchor::all().into_iter().find(|x| x.to_string() == anchor); - if let Some(v) = anchor { - self.state.selected_anchor = v; - } + if let Some(anchor) = frame + .info() + .web_info + .location + .hash + .strip_prefix('#') + .and_then(Anchor::from_str_case_insensitive) + { + self.state.selected_anchor = anchor; } #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 790e7289953..5b57c675b5a 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -33,6 +33,7 @@ impl Default for Demos { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -409,7 +410,7 @@ mod tests { let window = harness.node().children().next().unwrap(); // TODO(lucasmerlin): Windows should probably have a label? - //let window = harness.get_by_name(name); + //let window = harness.get_by_label(name); let size = window.raw_bounds().expect("window bounds").size(); harness.set_size(Vec2::new(size.width as f32, size.height as f32)); @@ -425,7 +426,7 @@ mod tests { let result = harness.try_wgpu_snapshot_options(&format!("demos/{name}"), &options); if let Err(err) = result { - errors.push(err); + errors.push(err.to_string()); } } diff --git a/crates/egui_demo_lib/src/demo/mod.rs b/crates/egui_demo_lib/src/demo/mod.rs index 828bdd896a3..8c9034868e4 100644 --- a/crates/egui_demo_lib/src/demo/mod.rs +++ b/crates/egui_demo_lib/src/demo/mod.rs @@ -17,6 +17,7 @@ pub mod frame_demo; pub mod highlighting; pub mod interactive_container; pub mod misc_demo_window; +pub mod modals; pub mod multi_touch; pub mod paint_bezier; pub mod painting; diff --git a/crates/egui_demo_lib/src/demo/modals.rs b/crates/egui_demo_lib/src/demo/modals.rs new file mode 100644 index 00000000000..989101b4d75 --- /dev/null +++ b/crates/egui_demo_lib/src/demo/modals.rs @@ -0,0 +1,287 @@ +use egui::{ComboBox, Context, Id, Modal, ProgressBar, Ui, Widget, Window}; + +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct Modals { + user_modal_open: bool, + save_modal_open: bool, + save_progress: Option, + + role: &'static str, + name: String, +} + +impl Default for Modals { + fn default() -> Self { + Self { + user_modal_open: false, + save_modal_open: false, + save_progress: None, + role: Self::ROLES[0], + name: "John Doe".to_owned(), + } + } +} + +impl Modals { + const ROLES: [&'static str; 2] = ["user", "admin"]; +} + +impl crate::Demo for Modals { + fn name(&self) -> &'static str { + "🗖 Modals" + } + + fn show(&mut self, ctx: &Context, open: &mut bool) { + use crate::View as _; + Window::new(self.name()) + .open(open) + .vscroll(false) + .resizable(false) + .show(ctx, |ui| self.ui(ui)); + } +} + +impl crate::View for Modals { + fn ui(&mut self, ui: &mut Ui) { + let Self { + user_modal_open, + save_modal_open, + save_progress, + role, + name, + } = self; + + ui.horizontal(|ui| { + if ui.button("Open User Modal").clicked() { + *user_modal_open = true; + } + + if ui.button("Open Save Modal").clicked() { + *save_modal_open = true; + } + }); + + ui.label("Click one of the buttons to open a modal."); + ui.label("Modals have a backdrop and prevent interaction with the rest of the UI."); + ui.label( + "You can show modals on top of each other and close the topmost modal with \ + escape or by clicking outside the modal.", + ); + + if *user_modal_open { + let modal = Modal::new(Id::new("Modal A")).show(ui.ctx(), |ui| { + ui.set_width(250.0); + + ui.heading("Edit User"); + + ui.label("Name:"); + ui.text_edit_singleline(name); + + ComboBox::new("role", "Role") + .selected_text(*role) + .show_ui(ui, |ui| { + for r in Self::ROLES { + ui.selectable_value(role, r, r); + } + }); + + ui.separator(); + + egui::Sides::new().show( + ui, + |_ui| {}, + |ui| { + if ui.button("Save").clicked() { + *save_modal_open = true; + } + if ui.button("Cancel").clicked() { + *user_modal_open = false; + } + }, + ); + }); + + if modal.should_close() { + *user_modal_open = false; + } + } + + if *save_modal_open { + let modal = Modal::new(Id::new("Modal B")).show(ui.ctx(), |ui| { + ui.set_width(200.0); + ui.heading("Save? Are you sure?"); + + ui.add_space(32.0); + + egui::Sides::new().show( + ui, + |_ui| {}, + |ui| { + if ui.button("Yes Please").clicked() { + *save_progress = Some(0.0); + } + + if ui.button("No Thanks").clicked() { + *save_modal_open = false; + } + }, + ); + }); + + if modal.should_close() { + *save_modal_open = false; + } + } + + if let Some(progress) = *save_progress { + Modal::new(Id::new("Modal C")).show(ui.ctx(), |ui| { + ui.set_width(70.0); + ui.heading("Saving…"); + + ProgressBar::new(progress).ui(ui); + + if progress >= 1.0 { + *save_progress = None; + *save_modal_open = false; + *user_modal_open = false; + } else { + *save_progress = Some(progress + 0.003); + ui.ctx().request_repaint(); + } + }); + } + + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file!()); + }); + } +} + +#[cfg(test)] +mod tests { + use crate::demo::modals::Modals; + use crate::Demo; + use egui::accesskit::Role; + use egui::Key; + use egui_kittest::kittest::Queryable; + use egui_kittest::Harness; + + #[test] + fn clicking_escape_when_popup_open_should_not_close_modal() { + let initial_state = Modals { + user_modal_open: true, + ..Modals::default() + }; + + let mut harness = Harness::new_state( + |ctx, modals| { + modals.show(ctx, &mut true); + }, + initial_state, + ); + + harness.get_by_role(Role::ComboBox).click(); + + harness.run(); + assert!(harness.ctx.memory(|mem| mem.any_popup_open())); + assert!(harness.state().user_modal_open); + + harness.press_key(Key::Escape); + harness.run(); + assert!(!harness.ctx.memory(|mem| mem.any_popup_open())); + assert!(harness.state().user_modal_open); + } + + #[test] + fn escape_should_close_top_modal() { + let initial_state = Modals { + user_modal_open: true, + save_modal_open: true, + ..Modals::default() + }; + + let mut harness = Harness::new_state( + |ctx, modals| { + modals.show(ctx, &mut true); + }, + initial_state, + ); + + assert!(harness.state().user_modal_open); + assert!(harness.state().save_modal_open); + + harness.press_key(Key::Escape); + harness.run(); + + assert!(harness.state().user_modal_open); + assert!(!harness.state().save_modal_open); + } + + #[test] + fn should_match_snapshot() { + let initial_state = Modals { + user_modal_open: true, + ..Modals::default() + }; + + let mut harness = Harness::new_state( + |ctx, modals| { + modals.show(ctx, &mut true); + }, + initial_state, + ); + + let mut results = Vec::new(); + + harness.run(); + results.push(harness.try_wgpu_snapshot("modals_1")); + + harness.get_by_label("Save").click(); + // TODO(lucasmerlin): Remove these extra runs once run checks for repaint requests + harness.run(); + harness.run(); + harness.run(); + results.push(harness.try_wgpu_snapshot("modals_2")); + + harness.get_by_label("Yes Please").click(); + // TODO(lucasmerlin): Remove these extra runs once run checks for repaint requests + harness.run(); + harness.run(); + harness.run(); + results.push(harness.try_wgpu_snapshot("modals_3")); + + for result in results { + result.unwrap(); + } + } + + // This tests whether the backdrop actually prevents interaction with lower layers. + #[test] + fn backdrop_should_prevent_focusing_lower_area() { + let initial_state = Modals { + save_modal_open: true, + save_progress: Some(0.0), + ..Modals::default() + }; + + let mut harness = Harness::new_state( + |ctx, modals| { + modals.show(ctx, &mut true); + }, + initial_state, + ); + + // TODO(lucasmerlin): Remove these extra runs once run checks for repaint requests + harness.run(); + harness.run(); + harness.run(); + + harness.get_by_label("Yes Please").simulate_click(); + + harness.run(); + + // This snapshots should show the progress bar modal on top of the save modal. + harness.wgpu_snapshot("modals_backdrop_should_prevent_focusing_lower_area"); + } +} diff --git a/crates/egui_demo_lib/src/demo/pan_zoom.rs b/crates/egui_demo_lib/src/demo/pan_zoom.rs index 6367946faef..f8411a740c0 100644 --- a/crates/egui_demo_lib/src/demo/pan_zoom.rs +++ b/crates/egui_demo_lib/src/demo/pan_zoom.rs @@ -73,20 +73,29 @@ impl crate::View for PanZoom { for (i, (pos, callback)) in [ ( egui::Pos2::new(0.0, 0.0), - Box::new(|ui: &mut egui::Ui, _: &mut Self| ui.button("top left!")) - as Box egui::Response>, + Box::new(|ui: &mut egui::Ui, _: &mut Self| { + ui.button("top left").on_hover_text("Normal tooltip") + }) as Box egui::Response>, ), ( egui::Pos2::new(0.0, 120.0), - Box::new(|ui: &mut egui::Ui, _| ui.button("bottom left?")), + Box::new(|ui: &mut egui::Ui, _| { + ui.button("bottom left").on_hover_text("Normal tooltip") + }), ), ( egui::Pos2::new(120.0, 120.0), - Box::new(|ui: &mut egui::Ui, _| ui.button("right bottom :D")), + Box::new(|ui: &mut egui::Ui, _| { + ui.button("right bottom") + .on_hover_text_at_pointer("Tooltip at pointer") + }), ), ( egui::Pos2::new(120.0, 0.0), - Box::new(|ui: &mut egui::Ui, _| ui.button("right top ):")), + Box::new(|ui: &mut egui::Ui, _| { + ui.button("right top") + .on_hover_text_at_pointer("Tooltip at pointer") + }), ), ( egui::Pos2::new(60.0, 60.0), diff --git a/crates/egui_demo_lib/src/demo/sliders.rs b/crates/egui_demo_lib/src/demo/sliders.rs index d15d9aa3100..ef8bdb0cd11 100644 --- a/crates/egui_demo_lib/src/demo/sliders.rs +++ b/crates/egui_demo_lib/src/demo/sliders.rs @@ -1,5 +1,4 @@ use egui::{style::HandleShape, Slider, SliderClamping, SliderOrientation, Ui}; -use std::f64::INFINITY; /// Showcase sliders #[derive(PartialEq)] @@ -77,7 +76,7 @@ impl crate::View for Sliders { let (type_min, type_max) = if *integer { ((i32::MIN as f64), (i32::MAX as f64)) } else if *logarithmic { - (-INFINITY, INFINITY) + (-f64::INFINITY, f64::INFINITY) } else { (-1e5, 1e5) // linear sliders make little sense with huge numbers }; diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs index 75d8135dff5..ed3ebe7f90f 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs @@ -13,7 +13,7 @@ pub enum Item<'a> { // TODO(emilk): add Style here so empty heading still uses up the right amount of space. Newline, - /// + /// Text Text(Style, &'a str), /// title, url diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index 093b2c6a33b..162fc51a1df 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b8d4f004ee11ea68ae0f30657601b6e51403fcc3ca91fa5b8cdcb58585d8d40d -size 78318 +oid sha256:01aaa4ef1a167a94fa1e5163550aabe4fa5e9f3a012b26170fe3088a6ca32d94 +size 81064 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Modals.png b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png new file mode 100644 index 00000000000..274b4b57686 --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/demos/Modals.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026723cb5d89b32386a849328c34420ee9e3ae1f97cbf6fa3c4543141123549e +size 32890 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png b/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png index 7ba225feae8..384840b7101 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Pan Zoom.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79ce1dbf7627579d4e10de6494e34d8fd9685506d7b35cb3c9148f90f8c01366 -size 25144 +oid sha256:ccfda16ef7cdf94f7fbbd2c0f8df6f6de7904969e2a66337920c32608a6f9f05 +size 25357 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_1.png b/crates/egui_demo_lib/tests/snapshots/modals_1.png new file mode 100644 index 00000000000..461bef728bc --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/modals_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8348ff582e11fdc9baf008b5434f81f8d77b834479cb3765c87d1f4fd695e30f +size 48212 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png new file mode 100644 index 00000000000..0f1273f41df --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23482b77cbd817c66421a630e409ac3d8c5d24de00aa91e476e8d42b607c24b1 +size 48104 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png new file mode 100644 index 00000000000..ba8cca6228b --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d94aa33d72c32f6f1aafab92c9753dc07bc5224c701003ac7fe8a01ae8c701a +size 44011 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png new file mode 100644 index 00000000000..14d6fb9cbfc --- /dev/null +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1a5d265470c36e64340ccceea4ade464b3c4a1177d60630b02ae8287934748f +size 44026 diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index f6301aec823..1d2f6afa480 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -28,7 +28,7 @@ pub struct RetainedImage { } impl RetainedImage { - pub fn from_color_image(debug_name: impl Into, image: ColorImage) -> Self { + pub fn from_color_image(debug_name: impl Into, image: egui::ColorImage) -> Self { Self { debug_name: debug_name.into(), size: image.size, @@ -54,7 +54,7 @@ impl RetainedImage { ) -> Result { Ok(Self::from_color_image( debug_name, - load_image_bytes(image_bytes)?, + load_image_bytes(image_bytes).map_err(|err| err.to_string())?, )) } @@ -154,7 +154,7 @@ impl RetainedImage { self.texture .lock() .get_or_insert_with(|| { - let image: &mut ColorImage = &mut self.image.lock(); + let image: &mut egui::ColorImage = &mut self.image.lock(); let image = std::mem::take(image); ctx.load_texture(&self.debug_name, image, self.options) }) @@ -190,8 +190,6 @@ impl RetainedImage { // ---------------------------------------------------------------------------- -use egui::ColorImage; - /// Load a (non-svg) image. /// /// Requires the "image" feature. You must also opt-in to the image formats you need @@ -200,9 +198,19 @@ use egui::ColorImage; /// # Errors /// On invalid image or unsupported image format. #[cfg(feature = "image")] -pub fn load_image_bytes(image_bytes: &[u8]) -> Result { +pub fn load_image_bytes(image_bytes: &[u8]) -> Result { crate::profile_function!(); - let image = image::load_from_memory(image_bytes).map_err(|err| err.to_string())?; + let image = image::load_from_memory(image_bytes).map_err(|err| match err { + image::ImageError::Unsupported(err) => match err.kind() { + image::error::UnsupportedErrorKind::Format(format) => { + egui::load::LoadError::FormatNotSupported { + detected_format: Some(format.to_string()), + } + } + _ => egui::load::LoadError::Loading(err.to_string()), + }, + err => egui::load::LoadError::Loading(err.to_string()), + })?; let size = [image.width() as _, image.height() as _]; let image_buffer = image.to_rgba8(); let pixels = image_buffer.as_flat_samples(); diff --git a/crates/egui_extras/src/loaders/image_loader.rs b/crates/egui_extras/src/loaders/image_loader.rs index 8c2c497058a..088ef5e4587 100644 --- a/crates/egui_extras/src/loaders/image_loader.rs +++ b/crates/egui_extras/src/loaders/image_loader.rs @@ -7,7 +7,7 @@ use egui::{ use image::ImageFormat; use std::{mem::size_of, path::Path, sync::Arc}; -type Entry = Result, String>; +type Entry = Result, LoadError>; #[derive(Default)] pub struct ImageCrateLoader { @@ -31,9 +31,14 @@ fn is_supported_uri(uri: &str) -> bool { .any(|format_ext| ext == *format_ext) } -fn is_unsupported_mime(mime: &str) -> bool { +fn is_supported_mime(mime: &str) -> bool { + // This is the default mime type for binary files, so this might actually be a valid image, + // let's relay on image's format guessing + if mime == "application/octet-stream" { + return true; + } // Uses only the enabled image crate features - !ImageFormat::all() + ImageFormat::all() .filter(ImageFormat::reading_enabled) .map(|fmt| fmt.to_mime_type()) .any(|format_mime| mime == format_mime) @@ -46,12 +51,12 @@ impl ImageLoader for ImageCrateLoader { fn load(&self, ctx: &egui::Context, uri: &str, _: SizeHint) -> ImageLoadResult { // three stages of guessing if we support loading the image: - // 1. URI extension + // 1. URI extension (only done for files) // 2. Mime from `BytesPoll::Ready` - // 3. image::guess_format + // 3. image::guess_format (used internally by image::load_from_memory) // (1) - if !is_supported_uri(uri) { + if uri.starts_with("file://") && !is_supported_uri(uri) { return Err(LoadError::NotSupported); } @@ -59,26 +64,26 @@ impl ImageLoader for ImageCrateLoader { if let Some(entry) = cache.get(uri).cloned() { match entry { Ok(image) => Ok(ImagePoll::Ready { image }), - Err(err) => Err(LoadError::Loading(err)), + Err(err) => Err(err), } } else { match ctx.try_load_bytes(uri) { Ok(BytesPoll::Ready { bytes, mime, .. }) => { - // (2 and 3) - if mime.as_deref().is_some_and(is_unsupported_mime) - || image::guess_format(&bytes).is_err() - { - return Err(LoadError::NotSupported); + // (2) + if let Some(mime) = mime { + if !is_supported_mime(&mime) { + return Err(LoadError::FormatNotSupported { + detected_format: Some(mime), + }); + } } + // (3) log::trace!("started loading {uri:?}"); let result = crate::image::load_image_bytes(&bytes).map(Arc::new); log::trace!("finished loading {uri:?}"); cache.insert(uri.into(), result.clone()); - match result { - Ok(image) => Ok(ImagePoll::Ready { image }), - Err(err) => Err(LoadError::Loading(err)), - } + result.map(|image| ImagePoll::Ready { image }) } Ok(BytesPoll::Pending { size }) => Ok(ImagePoll::Pending { size }), Err(err) => Err(err), @@ -100,7 +105,7 @@ impl ImageLoader for ImageCrateLoader { .values() .map(|result| match result { Ok(image) => image.pixels.len() * size_of::(), - Err(err) => err.len(), + Err(err) => err.byte_size(), }) .sum() } diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 293fbde007f..9275d345b5b 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -33,9 +33,7 @@ pub fn highlight( // performing it at a separate thread (ctx, ctx.style()) can be used and when ui is available // (ui.ctx(), ui.style()) can be used - impl egui::util::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> - for Highlighter - { + impl egui::cache::ComputerMut<(&egui::FontId, &CodeTheme, &str, &str), LayoutJob> for Highlighter { fn compute( &mut self, (font_id, theme, code, lang): (&egui::FontId, &CodeTheme, &str, &str), @@ -44,7 +42,7 @@ pub fn highlight( } } - type HighlightCache = egui::util::cache::FrameCache; + type HighlightCache = egui::cache::FrameCache; let font_id = style .override_font_id diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 2be2e25bb77..ec88990305b 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -1227,7 +1227,7 @@ impl<'a> TableBody<'a> { // Capture the hover information for the just created row. This is used in the next render // to ensure that the entire row is highlighted. - fn capture_hover_state(&mut self, response: &Option, row_index: usize) { + fn capture_hover_state(&self, response: &Option, row_index: usize) { let is_row_hovered = response.as_ref().map_or(false, |r| r.hovered()); if is_row_hovered { self.layout diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 094d537dafc..2bb184c833b 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -78,8 +78,8 @@ wasm-bindgen.workspace = true [dev-dependencies] -glutin.workspace = true # examples/pure_glow -glutin-winit.workspace = true +glutin = { workspace = true, default-features = true } # examples/pure_glow +glutin-winit = { workspace = true, default-features = true } [[example]] name = "pure_glow" diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index f8f6145c8a0..86d86bedf7c 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -620,7 +620,7 @@ impl Painter { h as _, src_format, glow::UNSIGNED_BYTE, - glow::PixelUnpackData::Slice(data), + glow::PixelUnpackData::Slice(Some(data)), ); check_for_gl_error!(&self.gl, "tex_sub_image_2d"); } else { @@ -635,7 +635,7 @@ impl Painter { border, src_format, glow::UNSIGNED_BYTE, - Some(data), + glow::PixelUnpackData::Slice(Some(data)), ); check_for_gl_error!(&self.gl, "tex_image_2d"); } @@ -686,7 +686,7 @@ impl Painter { h as _, glow::RGBA, glow::UNSIGNED_BYTE, - glow::PixelPackData::Slice(&mut pixels), + glow::PixelPackData::Slice(Some(&mut pixels)), ); } let mut flipped = Vec::with_capacity((w * h * 4) as usize); @@ -711,7 +711,7 @@ impl Painter { h as _, glow::RGB, glow::UNSIGNED_BYTE, - glow::PixelPackData::Slice(&mut pixels), + glow::PixelPackData::Slice(Some(&mut pixels)), ); } pixels diff --git a/crates/egui_kittest/CHANGELOG.md b/crates/egui_kittest/CHANGELOG.md new file mode 100644 index 00000000000..8f9a8aa2837 --- /dev/null +++ b/crates/egui_kittest/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog for egui_kittest +All notable changes to the `egui_kittest` crate will be noted in this file. + + +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. + + diff --git a/crates/egui_kittest/README.md b/crates/egui_kittest/README.md index db07f7e8d99..2ffda785c05 100644 --- a/crates/egui_kittest/README.md +++ b/crates/egui_kittest/README.md @@ -20,13 +20,13 @@ fn main() { let mut harness = Harness::builder().with_size(egui::Vec2::new(200.0, 100.0)).build(app); - let checkbox = harness.get_by_name("Check me!"); + let checkbox = harness.get_by_label("Check me!"); assert_eq!(checkbox.toggled(), Some(Toggled::False)); checkbox.click(); harness.run(); - let checkbox = harness.get_by_name("Check me!"); + let checkbox = harness.get_by_label("Check me!"); assert_eq!(checkbox.toggled(), Some(Toggled::True)); // You can even render the ui and do image snapshot tests diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index 45ad00e83a1..65c4dfbf0f6 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -56,7 +56,7 @@ impl HarnessBuilder { /// }); /// }, checked); /// - /// harness.get_by_name("Check me!").click(); + /// harness.get_by_label("Check me!").click(); /// harness.run(); /// /// assert_eq!(*harness.state(), true); @@ -85,7 +85,7 @@ impl HarnessBuilder { /// ui.checkbox(checked, "Check me!"); /// }, checked); /// - /// harness.get_by_name("Check me!").click(); + /// harness.get_by_label("Check me!").click(); /// harness.run(); /// /// assert_eq!(*harness.state(), true); diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 8e410d84d11..03648aa16fe 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -119,7 +119,7 @@ impl<'a, State> Harness<'a, State> { /// }); /// }, checked); /// - /// harness.get_by_name("Check me!").click(); + /// harness.get_by_label("Check me!").click(); /// harness.run(); /// /// assert_eq!(*harness.state(), true); @@ -144,7 +144,7 @@ impl<'a, State> Harness<'a, State> { /// ui.checkbox(checked, "Check me!"); /// }, checked); /// - /// harness.get_by_name("Check me!").click(); + /// harness.get_by_label("Check me!").click(); /// harness.run(); /// /// assert_eq!(*harness.state(), true); @@ -249,6 +249,25 @@ impl<'a, State> Harness<'a, State> { pub fn state_mut(&mut self) -> &mut State { &mut self.state } + + /// Press a key. + /// This will create a key down event and a key up event. + pub fn press_key(&mut self, key: egui::Key) { + self.input.events.push(egui::Event::Key { + key, + pressed: true, + modifiers: Default::default(), + repeat: false, + physical_key: None, + }); + self.input.events.push(egui::Event::Key { + key, + pressed: false, + modifiers: Default::default(), + repeat: false, + physical_key: None, + }); + } } /// Utilities for stateless harnesses. diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 40e02027b4b..5be6c275419 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -53,6 +53,9 @@ impl SnapshotOptions { pub enum SnapshotError { /// Image did not match snapshot Diff { + /// Name of the test + name: String, + /// Count of pixels that were different diff: i32, @@ -72,6 +75,9 @@ pub enum SnapshotError { /// The size of the image did not match the snapshot SizeMismatch { + /// Name of the test + name: String, + /// Expected size expected: (u32, u32), @@ -89,32 +95,43 @@ pub enum SnapshotError { }, } +const HOW_TO_UPDATE_SCREENSHOTS: &str = + "Run `UPDATE_SNAPSHOTS=1 cargo test` to update the snapshots."; + impl Display for SnapshotError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Diff { diff, diff_path } => { + Self::Diff { + name, + diff, + diff_path, + } => { write!( f, - "Image did not match snapshot. Diff: {diff}, {diff_path:?}" + "'{name}' Image did not match snapshot. Diff: {diff}, {diff_path:?}. {HOW_TO_UPDATE_SCREENSHOTS}" ) } Self::OpenSnapshot { path, err } => match err { ImageError::IoError(io) => match io.kind() { ErrorKind::NotFound => { - write!(f, "Missing snapshot: {path:?}") + write!(f, "Missing snapshot: {path:?}. {HOW_TO_UPDATE_SCREENSHOTS}") } err => { - write!(f, "Error reading snapshot: {err:?}\nAt: {path:?}") + write!(f, "Error reading snapshot: {err:?}\nAt: {path:?}. {HOW_TO_UPDATE_SCREENSHOTS}") } }, err => { - write!(f, "Error decoding snapshot: {err:?}\nAt: {path:?}") + write!(f, "Error decoding snapshot: {err:?}\nAt: {path:?}. Make sure git-lfs is setup correctly. Read the instructions here: https://github.com/emilk/egui/blob/master/CONTRIBUTING.md#making-a-pr") } }, - Self::SizeMismatch { expected, actual } => { + Self::SizeMismatch { + name, + expected, + actual, + } => { write!( f, - "Image size did not match snapshot. Expected: {expected:?}, Actual: {actual:?}" + "'{name}' Image size did not match snapshot. Expected: {expected:?}, Actual: {actual:?}. {HOW_TO_UPDATE_SCREENSHOTS}" ) } Self::WriteSnapshot { path, err } => { @@ -194,6 +211,7 @@ pub fn try_image_snapshot_options( if previous.dimensions() != current.dimensions() { maybe_update_snapshot(&path, current)?; return Err(SnapshotError::SizeMismatch { + name: name.to_owned(), expected: previous.dimensions(), actual: current.dimensions(), }); @@ -217,13 +235,16 @@ pub fn try_image_snapshot_options( err, })?; maybe_update_snapshot(&path, current)?; - return Err(SnapshotError::Diff { diff, diff_path }); + Err(SnapshotError::Diff { + name: name.to_owned(), + diff, + diff_path, + }) } else { // Delete old diff if it exists std::fs::remove_file(diff_path).ok(); + Ok(()) } - - Ok(()) } /// Image snapshot test. diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index d2cda112af7..4c3001fa72b 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -60,7 +60,7 @@ impl TestRenderer { } /// Render the [`Harness`] and return the resulting image. - pub fn render(&mut self, harness: &Harness<'_, State>) -> RgbaImage { + pub fn render(&self, harness: &Harness<'_, State>) -> RgbaImage { // We need to create a new renderer each time we render, since the renderer stores // textures related to the Harnesses' egui Context. // Calling the renderer from different Harnesses would cause problems if we store the renderer. diff --git a/crates/emath/src/numeric.rs b/crates/emath/src/numeric.rs index 03d00077129..9a7814b23d2 100644 --- a/crates/emath/src/numeric.rs +++ b/crates/emath/src/numeric.rs @@ -18,8 +18,8 @@ macro_rules! impl_numeric_float { ($t: ident) => { impl Numeric for $t { const INTEGRAL: bool = false; - const MIN: Self = std::$t::MIN; - const MAX: Self = std::$t::MAX; + const MIN: Self = $t::MIN; + const MAX: Self = $t::MAX; #[inline(always)] fn to_f64(self) -> f64 { @@ -44,8 +44,8 @@ macro_rules! impl_numeric_integer { ($t: ident) => { impl Numeric for $t { const INTEGRAL: bool = true; - const MIN: Self = std::$t::MIN; - const MAX: Self = std::$t::MAX; + const MIN: Self = $t::MIN; + const MAX: Self = $t::MAX; #[inline(always)] fn to_f64(self) -> f64 { diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 6c0677ad55e..8b655bd722f 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -1,4 +1,3 @@ -use std::f32::INFINITY; use std::fmt; use crate::{lerp, pos2, vec2, Div, Mul, Pos2, Rangef, Rot2, Vec2}; @@ -33,8 +32,8 @@ pub struct Rect { impl Rect { /// Infinite rectangle that contains every point. pub const EVERYTHING: Self = Self { - min: pos2(-INFINITY, -INFINITY), - max: pos2(INFINITY, INFINITY), + min: pos2(-f32::INFINITY, -f32::INFINITY), + max: pos2(f32::INFINITY, f32::INFINITY), }; /// The inverse of [`Self::EVERYTHING`]: stretches from positive infinity to negative infinity. @@ -53,8 +52,8 @@ impl Rect { /// assert_eq!(rect, Rect::from_min_max(pos2(0.0, 1.0), pos2(2.0, 3.0))) /// ``` pub const NOTHING: Self = Self { - min: pos2(INFINITY, INFINITY), - max: pos2(-INFINITY, -INFINITY), + min: pos2(f32::INFINITY, f32::INFINITY), + max: pos2(-f32::INFINITY, -f32::INFINITY), }; /// An invalid [`Rect`] filled with [`f32::NAN`]. @@ -650,6 +649,8 @@ impl Rect { /// /// A ray that starts inside the rect will return `true`. pub fn intersects_ray(&self, o: Pos2, d: Vec2) -> bool { + debug_assert!(d.is_normalized(), "expected normalized direction"); + let mut tmin = -f32::INFINITY; let mut tmax = f32::INFINITY; @@ -671,6 +672,32 @@ impl Rect { 0.0 <= tmax && tmin <= tmax } + + /// Where does a ray from the center intersect the rectangle? + /// + /// `d` is the direction of the ray and assumed to be normalized. + pub fn intersects_ray_from_center(&self, d: Vec2) -> Pos2 { + debug_assert!(d.is_normalized(), "expected normalized direction"); + + let mut tmin = f32::NEG_INFINITY; + let mut tmax = f32::INFINITY; + + for i in 0..2 { + let inv_d = 1.0 / -d[i]; + let mut t0 = (self.min[i] - self.center()[i]) * inv_d; + let mut t1 = (self.max[i] - self.center()[i]) * inv_d; + + if inv_d < 0.0 { + std::mem::swap(&mut t0, &mut t1); + } + + tmin = tmin.max(t0); + tmax = tmax.min(t1); + } + + let t = tmax.min(tmin); + self.center() + t * -d + } } impl fmt::Debug for Rect { @@ -793,4 +820,57 @@ mod tests { println!("Leftward ray from right:"); assert!(rect.intersects_ray(pos2(4.0, 2.0), Vec2::LEFT)); } + + #[test] + fn test_ray_from_center_intersection() { + let rect = Rect::from_min_max(pos2(1.0, 1.0), pos2(3.0, 3.0)); + + assert_eq!( + rect.intersects_ray_from_center(Vec2::RIGHT), + pos2(3.0, 2.0), + "rightward ray" + ); + + assert_eq!( + rect.intersects_ray_from_center(Vec2::UP), + pos2(2.0, 1.0), + "upward ray" + ); + + assert_eq!( + rect.intersects_ray_from_center(Vec2::LEFT), + pos2(1.0, 2.0), + "leftward ray" + ); + + assert_eq!( + rect.intersects_ray_from_center(Vec2::DOWN), + pos2(2.0, 3.0), + "downward ray" + ); + + assert_eq!( + rect.intersects_ray_from_center((Vec2::LEFT + Vec2::DOWN).normalized()), + pos2(1.0, 3.0), + "bottom-left corner ray" + ); + + assert_eq!( + rect.intersects_ray_from_center((Vec2::LEFT + Vec2::UP).normalized()), + pos2(1.0, 1.0), + "top-left corner ray" + ); + + assert_eq!( + rect.intersects_ray_from_center((Vec2::RIGHT + Vec2::DOWN).normalized()), + pos2(3.0, 3.0), + "bottom-right corner ray" + ); + + assert_eq!( + rect.intersects_ray_from_center((Vec2::RIGHT + Vec2::UP).normalized()), + pos2(3.0, 1.0), + "top-right corner ray" + ); + } } diff --git a/crates/emath/src/smart_aim.rs b/crates/emath/src/smart_aim.rs index 88b807cf80b..72094706995 100644 --- a/crates/emath/src/smart_aim.rs +++ b/crates/emath/src/smart_aim.rs @@ -138,7 +138,9 @@ fn test_aim() { assert_eq!(best_in_range_f64(99.999, 100.000), 100.0); assert_eq!(best_in_range_f64(10.001, 100.001), 100.0); - use std::f64::{INFINITY, NAN, NEG_INFINITY}; + const NAN: f64 = f64::NAN; + const INFINITY: f64 = f64::INFINITY; + const NEG_INFINITY: f64 = f64::NEG_INFINITY; assert!(best_in_range_f64(NAN, NAN).is_nan()); assert_eq!(best_in_range_f64(NAN, 1.2), 1.2); assert_eq!(best_in_range_f64(NAN, INFINITY), INFINITY); diff --git a/crates/emath/src/vec2.rs b/crates/emath/src/vec2.rs index 03e0715c20a..9a173348b03 100644 --- a/crates/emath/src/vec2.rs +++ b/crates/emath/src/vec2.rs @@ -176,6 +176,12 @@ impl Vec2 { } } + /// Checks if `self` has length `1.0` up to a precision of `1e-6`. + #[inline(always)] + pub fn is_normalized(self) -> bool { + (self.length_sq() - 1.0).abs() < 2e-6 + } + /// Rotates the vector by 90°, i.e positive X to positive Y /// (clockwise in egui coordinates). #[inline(always)] @@ -497,8 +503,10 @@ impl fmt::Display for Vec2 { } } -#[test] -fn test_vec2() { +#[cfg(test)] +mod test { + use super::*; + macro_rules! almost_eq { ($left: expr, $right: expr) => { let left = $left; @@ -506,32 +514,58 @@ fn test_vec2() { assert!((left - right).abs() < 1e-6, "{} != {}", left, right); }; } - use std::f32::consts::TAU; - assert_eq!(Vec2::ZERO.angle(), 0.0); - assert_eq!(Vec2::angled(0.0).angle(), 0.0); - assert_eq!(Vec2::angled(1.0).angle(), 1.0); - assert_eq!(Vec2::X.angle(), 0.0); - assert_eq!(Vec2::Y.angle(), 0.25 * TAU); + #[test] + fn test_vec2() { + use std::f32::consts::TAU; + + assert_eq!(Vec2::ZERO.angle(), 0.0); + assert_eq!(Vec2::angled(0.0).angle(), 0.0); + assert_eq!(Vec2::angled(1.0).angle(), 1.0); + assert_eq!(Vec2::X.angle(), 0.0); + assert_eq!(Vec2::Y.angle(), 0.25 * TAU); + + assert_eq!(Vec2::RIGHT.angle(), 0.0); + assert_eq!(Vec2::DOWN.angle(), 0.25 * TAU); + almost_eq!(Vec2::LEFT.angle(), 0.50 * TAU); + assert_eq!(Vec2::UP.angle(), -0.25 * TAU); - assert_eq!(Vec2::RIGHT.angle(), 0.0); - assert_eq!(Vec2::DOWN.angle(), 0.25 * TAU); - almost_eq!(Vec2::LEFT.angle(), 0.50 * TAU); - assert_eq!(Vec2::UP.angle(), -0.25 * TAU); + let mut assignment = vec2(1.0, 2.0); + assignment += vec2(3.0, 4.0); + assert_eq!(assignment, vec2(4.0, 6.0)); - let mut assignment = vec2(1.0, 2.0); - assignment += vec2(3.0, 4.0); - assert_eq!(assignment, vec2(4.0, 6.0)); + let mut assignment = vec2(4.0, 6.0); + assignment -= vec2(1.0, 2.0); + assert_eq!(assignment, vec2(3.0, 4.0)); - let mut assignment = vec2(4.0, 6.0); - assignment -= vec2(1.0, 2.0); - assert_eq!(assignment, vec2(3.0, 4.0)); + let mut assignment = vec2(1.0, 2.0); + assignment *= 2.0; + assert_eq!(assignment, vec2(2.0, 4.0)); - let mut assignment = vec2(1.0, 2.0); - assignment *= 2.0; - assert_eq!(assignment, vec2(2.0, 4.0)); + let mut assignment = vec2(2.0, 4.0); + assignment /= 2.0; + assert_eq!(assignment, vec2(1.0, 2.0)); + } + + #[test] + fn test_vec2_normalized() { + fn generate_spiral(n: usize, start: Vec2, end: Vec2) -> impl Iterator { + let angle_step = 2.0 * std::f32::consts::PI / n as f32; + let radius_step = (end.length() - start.length()) / n as f32; + + (0..n).map(move |i| { + let angle = i as f32 * angle_step; + let radius = start.length() + i as f32 * radius_step; + let x = radius * angle.cos(); + let y = radius * angle.sin(); + vec2(x, y) + }) + } - let mut assignment = vec2(2.0, 4.0); - assignment /= 2.0; - assert_eq!(assignment, vec2(1.0, 2.0)); + for v in generate_spiral(40, Vec2::splat(0.1), Vec2::splat(2.0)) { + let vn = v.normalized(); + almost_eq!(vn.length(), 1.0); + assert!(vn.is_normalized()); + } + } } diff --git a/crates/epaint/src/bezier.rs b/crates/epaint/src/bezier.rs index 05fff89298b..cab0d29f918 100644 --- a/crates/epaint/src/bezier.rs +++ b/crates/epaint/src/bezier.rs @@ -207,17 +207,21 @@ impl CubicBezierShape { /// B.x = (P3.x - 3 * P2.x + 3 * P1.x - P0.x) * t^3 + (3 * P2.x - 6 * P1.x + 3 * P0.x) * t^2 + (3 * P1.x - 3 * P0.x) * t + P0.x /// B.y = (P3.y - 3 * P2.y + 3 * P1.y - P0.y) * t^3 + (3 * P2.y - 6 * P1.y + 3 * P0.y) * t^2 + (3 * P1.y - 3 * P0.y) * t + P0.y /// Combine the above three equations and iliminate B.x and B.y, we get: + /// ```text /// t^3 * ( (P3.x - 3*P2.x + 3*P1.x - P0.x) * (P3.y - P0.y) - (P3.y - 3*P2.y + 3*P1.y - P0.y) * (P3.x - P0.x)) /// + t^2 * ( (3 * P2.x - 6 * P1.x + 3 * P0.x) * (P3.y - P0.y) - (3 * P2.y - 6 * P1.y + 3 * P0.y) * (P3.x - P0.x)) /// + t^1 * ( (3 * P1.x - 3 * P0.x) * (P3.y - P0.y) - (3 * P1.y - 3 * P0.y) * (P3.x - P0.x)) /// + (P0.x * (P3.y - P0.y) - P0.y * (P3.x - P0.x)) + P0.x * (P0.y - P3.y) + P0.y * (P3.x - P0.x) /// = 0 - /// or a * t^3 + b * t^2 + c * t + d = 0 + /// ``` + /// or `a * t^3 + b * t^2 + c * t + d = 0` /// /// let x = t - b / (3 * a), then we have: + /// ```text /// x^3 + p * x + q = 0, where: /// p = (3.0 * a * c - b^2) / (3.0 * a^2) /// q = (2.0 * b^3 - 9.0 * a * b * c + 27.0 * a^2 * d) / (27.0 * a^3) + /// ``` /// /// when p > 0, there will be one real root, two complex roots /// when p = 0, there will be two real roots, when p=q=0, there will be three real roots but all 0. diff --git a/crates/epaint/src/mutex.rs b/crates/epaint/src/mutex.rs index 157701c2be0..bd984a1d0ec 100644 --- a/crates/epaint/src/mutex.rs +++ b/crates/epaint/src/mutex.rs @@ -75,7 +75,7 @@ mod mutex_impl { // Detect if we are recursively taking out a lock on this mutex. // use a pointer to the inner data as an id for this lock - let ptr = (&self.0 as *const parking_lot::Mutex<_>).cast::<()>(); + let ptr = std::ptr::from_ref::>(&self.0).cast::<()>(); // Store it in thread local storage while we have a lock guard taken out HELD_LOCKS_TLS.with(|held_locks| { diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 17826e6afb1..64dd827148c 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -623,10 +623,10 @@ pub struct Glyph { /// The row/line height of this font. pub font_height: f32, - /// The ascent of the sub-font within the font ("FontImpl"). + /// The ascent of the sub-font within the font (`FontImpl`). pub font_impl_ascent: f32, - /// The row/line height of the sub-font within the font ("FontImpl"). + /// The row/line height of the sub-font within the font (`FontImpl`). pub font_impl_height: f32, /// Position and size of the glyph in the font texture, in texels. diff --git a/crates/epaint/src/texture_handle.rs b/crates/epaint/src/texture_handle.rs index f4142d91510..1f640a171de 100644 --- a/crates/epaint/src/texture_handle.rs +++ b/crates/epaint/src/texture_handle.rs @@ -66,6 +66,7 @@ impl TextureHandle { } /// Assign a new image to an existing texture. + #[allow(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability pub fn set(&mut self, image: impl Into, options: TextureOptions) { self.tex_mngr .write() @@ -73,6 +74,7 @@ impl TextureHandle { } /// Assign a new image to a subregion of the whole texture. + #[allow(clippy::needless_pass_by_ref_mut)] // Intentionally hide interiority of mutability pub fn set_partial( &mut self, pos: [usize; 2], diff --git a/deny.toml b/deny.toml index 2f8ac05198b..ede37dea405 100644 --- a/deny.toml +++ b/deny.toml @@ -51,6 +51,7 @@ skip = [ { name = "cfg_aliases" }, # old version via wgpu { name = "event-listener" }, # TODO(emilk): rustls pulls in two versions of this 😭 { name = "futures-lite" }, # old version via accesskit_unix and zbus + { name = "glow" }, # old version via wgpu { name = "memoffset" }, # tiny dependency { name = "ndk-sys" }, # old version via wgpu, winit uses newer version { name = "quick-xml" }, # old version via wayland-scanner @@ -84,6 +85,7 @@ allow = [ "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux + "Unicode-3.0", # https://www.unicode.org/license.txt "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) ] diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 4f31cc6d7cc..62e8bc29c14 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 7bbea3b07de..1ddec02f5f8 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index d7cecf07312..5214bdc1f5f 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 54d037d15ed..241b893401d 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["tami5 "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/custom_keypad/Cargo.toml b/examples/custom_keypad/Cargo.toml index 088e7a3fc41..73c6b0e7aa8 100644 --- a/examples/custom_keypad/Cargo.toml +++ b/examples/custom_keypad/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Varphone Wong "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/custom_style/Cargo.toml b/examples/custom_style/Cargo.toml index cd4f4063bee..f87ce0bf82a 100644 --- a/examples/custom_style/Cargo.toml +++ b/examples/custom_style/Cargo.toml @@ -3,7 +3,7 @@ name = "custom_style" version = "0.1.0" license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index 5e4941e3df8..4a53ee48745 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 7adb8e1b8a9..1a9f86c40c0 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] @@ -20,4 +20,4 @@ env_logger = { version = "0.10", default-features = false, features = [ "auto-color", "humantime", ] } -rfd = "0.13" +rfd = "0.15" diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index e5acf4d89bb..8816b425598 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 07cdd4858c1..b3e00b20897 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Maxim Osipenko "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] @@ -29,6 +29,4 @@ env_logger = { version = "0.10", default-features = false, features = [ ] } # This is normally enabled by eframe/default, which is not being used here # because of accesskit, as mentioned above -winit = { workspace = true, features = [ - "default" -] } +winit = { workspace = true, features = ["default"] } diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index 169504409f7..7197c60337b 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index c564f4c1590..f1b4f97ddf2 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jan Procházka "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index c2cd90ce575..1af09849319 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Jose Palazon "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 089d0840142..1644d6a72a5 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index 8f200bd628d..3389de5c928 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index cc4733594dd..5963ea3ad71 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 88b57f20e7a..1dd0bbf5076 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -45,7 +45,7 @@ impl eframe::App for MyApp { if ui.button("save to 'top_left.png'").clicked() { self.save_to_file = true; - ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot(Default::default())); } ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| { @@ -58,9 +58,13 @@ impl eframe::App for MyApp { } else { ctx.set_theme(egui::Theme::Light); }; - ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd( + egui::ViewportCommand::Screenshot(Default::default()), + ); } else if ui.button("take screenshot!").clicked() { - ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd( + egui::ViewportCommand::Screenshot(Default::default()), + ); } }); }); diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index 069d7184481..1f6d5ea431e 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index a9fd5aa0373..25aa473486c 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["TicClick "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/rust-toolchain b/rust-toolchain index ed934c15761..38e5e90f3ac 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,6 +5,6 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.77.0" +channel = "1.80.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/check.sh b/scripts/check.sh index 5f316d57a69..7db1a6aa3e2 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -9,7 +9,7 @@ set -x # Checks all tests, lints etc. # Basically does what the CI does. -cargo +1.77.0 install --quiet typos-cli +cargo +1.80.0 install --quiet typos-cli export RUSTFLAGS="-D warnings" export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454 diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index f0e91004a81..f06033c71b9 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -6,7 +6,7 @@ # ----------------------------------------------------------------------------- # Section identical to the root clippy.toml: -msrv = "1.77" +msrv = "1.80" allow-unwrap-in-tests = true @@ -47,6 +47,9 @@ doc-valid-idents = [ # You must also update the same list in the root `clippy.toml`! "AccessKit", "WebGL", + "WebGL1", + "WebGL2", "WebGPU", + "VirtualBox", "..", ] diff --git a/tests/test_egui_extras_compilation/Cargo.toml b/tests/test_egui_extras_compilation/Cargo.toml index bccea8a3216..f1d67f4bb8b 100644 --- a/tests/test_egui_extras_compilation/Cargo.toml +++ b/tests/test_egui_extras_compilation/Cargo.toml @@ -3,14 +3,17 @@ name = "test_egui_extras_compilation" version = "0.1.0" license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] workspace = true [package.metadata.cargo-machete] -ignored = ["eframe", "egui_extras"] # We don't use them, just check that things compile +ignored = [ + "eframe", + "egui_extras", +] # We don't use them, just check that things compile [dependencies] eframe = { workspace = true, features = ["default", "persistence"] } diff --git a/tests/test_inline_glow_paint/Cargo.toml b/tests/test_inline_glow_paint/Cargo.toml index 975f777d72b..bcda5b3ddea 100644 --- a/tests/test_inline_glow_paint/Cargo.toml +++ b/tests/test_inline_glow_paint/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/tests/test_size_pass/Cargo.toml b/tests/test_size_pass/Cargo.toml index 21d645cef5e..e3819a107e9 100644 --- a/tests/test_size_pass/Cargo.toml +++ b/tests/test_size_pass/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Emil Ernerfeldt "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/tests/test_ui_stack/Cargo.toml b/tests/test_ui_stack/Cargo.toml index e87845bc043..12ba9961be7 100644 --- a/tests/test_ui_stack/Cargo.toml +++ b/tests/test_ui_stack/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["Antoine Beyeler "] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints] diff --git a/tests/test_viewports/Cargo.toml b/tests/test_viewports/Cargo.toml index 6edac0bd5e1..cb877a7107a 100644 --- a/tests/test_viewports/Cargo.toml +++ b/tests/test_viewports/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["konkitoman"] license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.77" +rust-version = "1.80" publish = false [lints]