diff --git a/.typos.toml b/.typos.toml index 46d137138ea..6d856495178 100644 --- a/.typos.toml +++ b/.typos.toml @@ -3,8 +3,7 @@ # run: typos [default.extend-words] -nknown = "nknown" # part of @55nknown username -Prefence = "Prefence" # typo in glutin_winit API +nknown = "nknown" # part of @55nknown username [files] extend-exclude = ["web_demo/egui_demo_app.js"] # auto-generated diff --git a/.vscode/settings.json b/.vscode/settings.json index dbe8e6629dc..8ec1e4aa67f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -32,4 +32,5 @@ "--all-targets", "--all-features", ], + "rust-analyzer.showUnlinkedFileNotification": false, } diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000000..1c8d123d025 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +/crates/egui_plot @Bromeon @EmbersArc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8d3897457d4..9565c41dbaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing guidelines +# Contribution Guidelines ## Introduction @@ -6,6 +6,8 @@ / Emil +## How to contribute to egui +You want to contribute to egui, but don't know how? First of all: thank you! I created a special issue just for that: . But make sure you still read this file first :) ## Discussion @@ -25,7 +27,7 @@ If you are filing a bug, please provide a way to reproduce it. ## Making a PR -First file an issue (or find an existing one) and announce that you plan to work on something. That way we will avoid having several people doing double work. Please ask for feedback before you start working on something non-trivial! +For small things, just go ahead an open a PR. For bigger things, please file an issue first (or find an existing one) and announce that you plan to work on something. That way we will avoid having several people doing double work, and you might get useful feedback on the issue before you start working. Browse through [`ARCHITECTURE.md`](ARCHITECTURE.md) to get a sense of how all pieces connects. @@ -34,76 +36,115 @@ You can test your code locally by running `./scripts/check.sh`. When you have something that works, open a draft PR. You may get some helpful feedback early! When you feel the PR is ready to go, do a self-review of the code, and then open it for review. -Please keep pull requests small and focused. - Don't worry about having many small commits in the PR - they will be squashed to one commit once merged. -Do not include the `.js` and `.wasm` build artifacts generated for building for web. -`git` is not great at storing large files like these, so we only commit a new web demo after a new egui release. +Please keep pull requests small and focused. The smaller it is, the more likely it is to get merged. + +## PR review + +Most PR reviews are done by me, Emil, but I very much appreciate any help I can get reviewing PRs! +It is very easy to add complexity to a project, but remember that each line of code added is code that needs to be maintained in perpituity, so we have a high bar on what get merged! + +When reviewing, we look for: +* The PR title and description should be helpful +* Breaking changes are documented in the PR description +* The code should be readable +* The code should have helpful docstrings +* The code should follow the [Code Style](CONTRIBUTING.md#code-style) + +Note that each new egui release have some breaking changes, so we don't mind having a few of those in a PR. Of course, we still try to avoid them if we can, and if we can't we try to first deprecate old code using the `#[deprecated]` attribute. ## Creating an integration for egui -If you make an integration for `egui` for some engine or renderer, please share it with the world! -I will add a link to it from the `egui` README.md so others can easily find it. +See for how to write your own egui integration. -Read the section on integrations at . +If you make an integration for `egui` for some engine or renderer, please share it with the world! +Make a PR to add it as a link to [`README.md`](README.md#integrations) so others can easily find it. ## Testing the web viewer -* Install some tools with `scripts/setup_web.sh` * Build with `scripts/build_demo_web.sh` * Host with `scripts/start_server.sh` * Open -## Code Conventions -Conventions unless otherwise specified: - -* angles are in radians and clock-wise -* `Vec2::X` is right and `Vec2::Y` is down. -* `Pos2::ZERO` is left top. - +## Code Style While using an immediate mode gui is simple, implementing one is a lot more tricky. There are many subtle corner-case you need to think through. The `egui` source code is a bit messy, partially because it is still evolving. -* Read some code before writing your own. -* Follow the `egui` code style. -* Add blank lines around all `fn`, `struct`, `enum`, etc. -* `// Comment like this.` and not `//like this`. -* Use `TODO` instead of `FIXME`. -* Add your github handle to the `TODO`:s you write, e.g: `TODO(emilk): clean this up`. -* Write idiomatic rust. -* Avoid `unsafe`. -* Avoid code that can cause panics. -* Use good names for everything. -* Add docstrings to types, `struct` fields and all `pub fn`. -* Add some example code (doc-tests). -* Before making a function longer, consider adding a helper function. -* If you are only using it in one function, put the `use` statement in that function. This improves locality, making it easier to read and move the code. -* When importing a `trait` to use it's trait methods, do this: `use Trait as _;`. That lets the reader know why you imported it, even though it seems unused. -* Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/). -* Break the above rules when it makes sense. +* Read some code before writing your own +* Leave the code cleaner than how you found it +* Write idiomatic rust +* Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/) +* Add blank lines around all `fn`, `struct`, `enum`, etc +* `// Comment like this.` and not `//like this` +* Use `TODO` instead of `FIXME` +* Add your github handle to the `TODO`:s you write, e.g: `TODO(emilk): clean this up` +* Avoid `unsafe` +* Avoid `unwrap` and any other code that can cause panics +* Use good names for everything +* Add docstrings to types, `struct` fields and all `pub fn` +* Add some example code (doc-tests) +* Before making a function longer, consider adding a helper function +* If you are only using it in one function, put the `use` statement in that function. This improves locality, making it easier to read and move the code +* When importing a `trait` to use it's trait methods, do this: `use Trait as _;`. That lets the reader know why you imported it, even though it seems unused +* Avoid double negatives +* Flip `if !condition {} else {}` +* Sets of things should be lexicographically sorted (e.g. crate dependencies in `Cargo.toml`) +* Break the above rules when it makes sense ### Good: ``` rust /// The name of the thing. -fn name(&self) -> &str { +pub fn name(&self) -> &str { &self.name } fn foo(&self) { - // TODO(emilk): implement + // TODO(emilk): this can be optimized } ``` ### Bad: ``` rust -//some function -fn get_name(&self) -> &str { +//gets the name +pub fn get_name(&self) -> &str { &self.name } fn foo(&self) { - //FIXME: implement + //FIXME: this can be optimized } ``` + +### Coordinate system +The left-top corner of the screen is `(0.0, 0.0)`, +with `Vec2::X` increasing to the right and `Vec2::Y` increasing downwards. + +`egui` uses logical _points_ as its coordinate system. +Those related to physical _pixels_ by the `pixels_per_point` scale factor. +For example, a high-dpi screeen can have `pixels_per_point = 2.0`, +meaning there are two physical screen pixels for each logical point. + +Angles are in radians, and are measured clockwise from the X-axis, which has angle=0. + + +### Avoid `unwrap`, `expect` etc. +The code should never panic or crash, which means that any instance of `unwrap` or `expect` is a potential time-bomb. Even if you structured your code to make them impossible, any reader will have to read the code very carefully to prove to themselves that an `unwrap` won't panic. Often you can instead rewrite your code so as to avoid it. The same goes for indexing into a slice (which will panic on out-of-bounds) - it is often preferable to use `.get()`. + +For instance: + +``` rust +let first = if vec.is_empty() { + return; +} else { + vec[0] +}; +``` +can be better written as: + +``` rust +let Some(first) = vec.first() else { + return; +}; +``` diff --git a/Cargo.lock b/Cargo.lock index 4de3c27ab52..5bf86d724ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "accesskit" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0cc53b7e5d8f45ebe687178cf91af0f45fdba6e78fedf94f0269c5be5b9f296" +checksum = "ca8410747ed85a17c4a1e9ed3f5a74d3e7bdcc876cf9a18ff40ae21d645997b2" dependencies = [ "enumn", "serde", @@ -30,30 +30,30 @@ dependencies = [ [[package]] name = "accesskit_consumer" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39dfcfd32eb0c1b525daaf4b02adcd2fa529c22cd713491e15bf002a01a714f5" +checksum = "8c17cca53c09fbd7288667b22a201274b9becaa27f0b91bf52a526db95de45e6" dependencies = [ "accesskit", ] [[package]] name = "accesskit_macos" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89c7e8406319ac3149d7b59983637984f0864bbf738319b1c443976268b6426c" +checksum = "cd3b6ae1eabbfbced10e840fd3fce8a93ae84f174b3e4ba892ab7bcb42e477a7" dependencies = [ "accesskit", "accesskit_consumer", - "objc2", + "objc2 0.3.0-beta.3.patch-leaks.3", "once_cell", ] [[package]] name = "accesskit_unix" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0c84552a7995c981d5f22e2d4b24ba9a55718bb12fba883506d6d7344acaf1" +checksum = "6c8c9b4467d77cacfbc93cee9aa8e7822f6d527c774efdca5f8b3a5280c34847" dependencies = [ "accesskit", "accesskit_consumer", @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "accesskit_windows" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314d4a797fc82d182b04f4f0665a368924fb556ad9557fccd2d39d38dc8c1c1b" +checksum = "afcae27ec0974fc7c3b0b318783be89fd1b2e66dd702179fe600166a38ff4a0b" dependencies = [ "accesskit", "accesskit_consumer", @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "accesskit_winit" -version = "0.15.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88e39fcec2e10971e188730b7a76bab60647dacc973d4591855ebebcadfaa738" +checksum = "5284218aca17d9e150164428a0ebc7b955f70e3a9a78b4c20894513aabf98a67" dependencies = [ "accesskit", "accesskit_macos", @@ -115,6 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", + "getrandom", "once_cell", "serde", "version_check", @@ -138,13 +139,15 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "android-activity" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c77a0045eda8b888c76ea473c2b0515ba6f471d318f8927c5c72240937035a6" +checksum = "052ad56e336bcc615a214bffbeca6c181ee9550acec193f0327e0b103b033a4d" dependencies = [ "android-properties", - "bitflags 1.3.2", + "bitflags 2.4.0", "cc", + "cesu8", + "jni", "jni-sys", "libc", "log", @@ -152,6 +155,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", + "thiserror", ] [[package]] @@ -181,6 +185,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + [[package]] name = "anyhow" version = "1.0.75" @@ -189,9 +199,9 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arboard" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac57f2b058a76363e357c056e4f74f1945bf734d37b8b3ef49066c4787dde0fc" +checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08" dependencies = [ "clipboard-win", "log", @@ -201,7 +211,7 @@ dependencies = [ "parking_lot", "thiserror", "winapi", - "x11rb", + "x11rb 0.12.0", ] [[package]] @@ -216,6 +226,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5f312b0a56c5cdf967c0aeb67f6289603354951683bc97ddc595ab974ba9aa" + [[package]] name = "ash" version = "0.37.3+1.3.251" @@ -285,7 +301,7 @@ dependencies = [ "futures-lite", "log", "parking", - "polling", + "polling 2.8.0", "rustix 0.37.25", "slab", "socket2", @@ -320,7 +336,7 @@ dependencies = [ "cfg-if", "event-listener 3.0.0", "futures-lite", - "rustix 0.38.14", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -437,17 +453,6 @@ dependencies = [ "zbus", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -469,12 +474,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.4" @@ -541,7 +540,16 @@ version = "0.1.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa55741ee90902547802152aaf3f8e5248aab7e21468089560d4c8840561146" dependencies = [ - "objc-sys", + "objc-sys 0.2.0-beta.2", +] + +[[package]] +name = "block-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd7cf50912cddc06dc5ea7c08c5e81c1b2c842a70d19def1848d54c586fed92" +dependencies = [ + "objc-sys 0.3.1", ] [[package]] @@ -550,8 +558,18 @@ version = "0.2.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dd9e63c1744f755c2f60332b88de39d341e5e86239014ad839bd71c106dec42" dependencies = [ - "block-sys", - "objc2-encode", + "block-sys 0.1.0-beta.1", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "block2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +dependencies = [ + "block-sys 0.2.0", + "objc2 0.4.1", ] [[package]] @@ -620,16 +638,28 @@ dependencies = [ [[package]] name = "calloop" -version = "0.10.6" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" +checksum = "7b50b5a44d59a98c55a9eeb518f39bf7499ba19fd98ee7d22618687f3f10adbf" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "log", - "nix 0.25.1", - "slotmap", + "polling 3.3.0", + "rustix 0.38.21", + "slab", "thiserror", - "vec_map", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +dependencies = [ + "calloop", + "rustix 0.38.21", + "wayland-backend", + "wayland-client", ] [[package]] @@ -734,25 +764,29 @@ checksum = "7a0e87cdf78571d9fbeff16861c37a006cd718d2433dc6d5b80beaae367d899a" [[package]] name = "clap" -version = "3.2.25" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ - "bitflags 1.3.2", - "clap_lex", - "indexmap 1.9.3", - "textwrap", + "clap_builder", ] [[package]] -name = "clap_lex" -version = "0.2.4" +name = "clap_builder" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ - "os_str_bytes", + "anstyle", + "clap_lex", ] +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + [[package]] name = "clipboard-win" version = "4.5.0" @@ -766,16 +800,16 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", "core-foundation", "core-graphics", - "foreign-types 0.3.2", + "foreign-types", "libc", "objc", ] @@ -867,14 +901,14 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" dependencies = [ "bitflags 1.3.2", "core-foundation", "core-graphics-types", - "foreign-types 0.3.2", + "foreign-types", "libc", ] @@ -909,19 +943,19 @@ dependencies = [ [[package]] name = "criterion" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", - "atty", "cast", "ciborium", "clap", "criterion-plot", + "is-terminal", "itertools", - "lazy_static", "num-traits", + "once_cell", "oorandom", "regex", "serde", @@ -970,6 +1004,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + [[package]] name = "custom_3d_glow" version = "0.1.0" @@ -1015,15 +1055,18 @@ dependencies = [ [[package]] name = "data-url" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +dependencies = [ + "powerfmt", +] [[package]] name = "derivative" @@ -1099,7 +1142,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "ecolor" -version = "0.24.1" +version = "0.24.2" dependencies = [ "bytemuck", "cint", @@ -1110,7 +1153,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.24.1" +version = "0.24.2" dependencies = [ "bytemuck", "cocoa", @@ -1120,7 +1163,7 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "glow 0.12.3", + "glow", "glutin", "glutin-winit", "image", @@ -1131,7 +1174,7 @@ dependencies = [ "percent-encoding", "pollster", "puffin", - "raw-window-handle", + "raw-window-handle 0.5.2", "ron", "serde", "static_assertions", @@ -1146,7 +1189,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.24.1" +version = "0.24.2" dependencies = [ "accesskit", "ahash", @@ -1162,7 +1205,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.24.1" +version = "0.24.2" dependencies = [ "bytemuck", "document-features", @@ -1178,7 +1221,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.24.1" +version = "0.24.2" dependencies = [ "accesskit_winit", "arboard", @@ -1186,7 +1229,7 @@ dependencies = [ "egui", "log", "puffin", - "raw-window-handle", + "raw-window-handle 0.5.2", "serde", "smithay-clipboard", "web-time", @@ -1196,7 +1239,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.24.1" +version = "0.24.2" dependencies = [ "bytemuck", "chrono", @@ -1220,7 +1263,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.24.1" +version = "0.24.2" dependencies = [ "chrono", "criterion", @@ -1235,7 +1278,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.24.1" +version = "0.24.2" dependencies = [ "chrono", "document-features", @@ -1249,32 +1292,30 @@ dependencies = [ "resvg", "serde", "syntect", - "tiny-skia", - "usvg", ] [[package]] name = "egui_glow" -version = "0.24.1" +version = "0.24.2" dependencies = [ "bytemuck", "document-features", "egui", "egui-winit", - "glow 0.12.3", + "glow", "glutin", "glutin-winit", "log", - "memoffset 0.7.1", + "memoffset", "puffin", - "raw-window-handle", + "raw-window-handle 0.5.2", "wasm-bindgen", "web-sys", ] [[package]] name = "egui_plot" -version = "0.24.1" +version = "0.24.2" dependencies = [ "document-features", "egui", @@ -1303,7 +1344,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "emath" -version = "0.24.1" +version = "0.24.2" dependencies = [ "bytemuck", "document-features", @@ -1373,13 +1414,12 @@ dependencies = [ "humantime", "is-terminal", "log", - "regex", "termcolor", ] [[package]] name = "epaint" -version = "0.24.1" +version = "0.24.2" dependencies = [ "ab_glyph", "ahash", @@ -1526,15 +1566,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - [[package]] name = "foreign-types" version = "0.5.0" @@ -1542,7 +1573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -1556,12 +1587,6 @@ dependencies = [ "syn 2.0.37", ] -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1674,14 +1699,24 @@ dependencies = [ [[package]] name = "gethostname" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" dependencies = [ "libc", "winapi", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -1735,18 +1770,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "glow" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "glow" version = "0.13.0" @@ -1761,68 +1784,60 @@ dependencies = [ [[package]] name = "glutin" -version = "0.30.10" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc93b03242719b8ad39fb26ed2b01737144ce7bd4bfc7adadcef806596760fe" +checksum = "eca18d477e18c996c1fd1a50e04c6a745b67e2d512c7fb51f2757d9486a0e3ee" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "cfg_aliases", "cgl", "core-foundation", "dispatch", "glutin_egl_sys", "glutin_glx_sys", - "glutin_wgl_sys 0.4.0", - "libloading 0.7.4", - "objc2", + "glutin_wgl_sys", + "icrate", + "libloading 0.8.0", + "objc2 0.4.1", "once_cell", - "raw-window-handle", - "wayland-sys 0.30.1", - "windows-sys 0.45.0", + "raw-window-handle 0.5.2", + "wayland-sys", + "windows-sys 0.48.0", "x11-dl", ] [[package]] name = "glutin-winit" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629a873fc04062830bfe8f97c03773bcd7b371e23bcc465d0a61448cd1588fa4" +checksum = "1ebcdfba24f73b8412c5181e56f092b5eff16671c514ce896b258a0a64bd7735" dependencies = [ "cfg_aliases", "glutin", - "raw-window-handle", + "raw-window-handle 0.5.2", "winit", ] [[package]] name = "glutin_egl_sys" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af784eb26c5a68ec85391268e074f0aa618c096eadb5d6330b0911cf34fe57c5" +checksum = "77cc5623f5309ef433c3dd4ca1223195347fe62c413da8e2fdd0eb76db2d9bcd" dependencies = [ "gl_generator", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "glutin_glx_sys" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b53cb5fe568964aa066a3ba91eac5ecbac869fb0842cd0dc9e412434f1a1494" +checksum = "a165fd686c10dcc2d45380b35796e577eacfd43d4660ee741ec8ebe2201b3b4f" dependencies = [ "gl_generator", "x11-dl", ] -[[package]] -name = "glutin_wgl_sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef89398e90033fc6bc65e9bd42fd29bbbfd483bda5b56dc5562f455550618165" -dependencies = [ - "gl_generator", -] - [[package]] name = "glutin_wgl_sys" version = "0.5.0" @@ -1884,7 +1899,7 @@ checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ "bitflags 2.4.0", "gpu-descriptor-types", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] @@ -1922,15 +1937,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", "allocator-api2", @@ -1982,15 +1991,6 @@ dependencies = [ "env_logger", ] -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.3" @@ -2047,6 +2047,17 @@ dependencies = [ "cc", ] +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +dependencies = [ + "block2 0.3.0", + "dispatch", + "objc2 0.4.1", +] + [[package]] name = "idna" version = "0.4.0" @@ -2084,28 +2095,18 @@ dependencies = [ [[package]] name = "imagesize" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df19da1e92fbfec043ca97d622955381b1f3ee72a180ec999912df31b1ccd951" - -[[package]] -name = "indexmap" -version = "1.9.3" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] +checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] @@ -2115,9 +2116,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", ] [[package]] @@ -2126,7 +2124,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi", "libc", "windows-sys 0.48.0", ] @@ -2137,8 +2135,8 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.3", - "rustix 0.38.14", + "hermit-abi", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -2230,24 +2228,18 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kurbo" -version = "0.8.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" dependencies = [ "arrayvec", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" -version = "0.2.148" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libloading" @@ -2292,9 +2284,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "litrs" @@ -2341,22 +2333,13 @@ checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375" dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.7.1" @@ -2375,7 +2358,7 @@ dependencies = [ "bitflags 2.4.0", "block", "core-graphics-types", - "foreign-types 0.5.0", + "foreign-types", "log", "objc", "paste", @@ -2419,18 +2402,6 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" -[[package]] -name = "mio" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", -] - [[package]] name = "multiple_viewports" version = "0.1.0" @@ -2441,15 +2412,15 @@ dependencies = [ [[package]] name = "naga" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61d829abac9f5230a85d8cc83ec0879b4c09790208ae25b5ea031ef84562e071" +checksum = "ae585df4b6514cf8842ac0f1ab4992edc975892704835b549cf818dc0191249e" dependencies = [ "bit-set", "bitflags 2.4.0", "codespan-reporting", "hexf-parse", - "indexmap 2.0.0", + "indexmap", "log", "num-traits", "rustc-hash", @@ -2470,15 +2441,17 @@ dependencies = [ [[package]] name = "ndk" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "jni-sys", + "log", "ndk-sys", "num_enum", - "raw-window-handle", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.0", "thiserror", ] @@ -2490,38 +2463,13 @@ checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] name = "ndk-sys" -version = "0.4.1+23.1.7779620" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ "jni-sys", ] -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - -[[package]] -name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - [[package]] name = "nix" version = "0.26.4" @@ -2531,7 +2479,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.7.1", + "memoffset", ] [[package]] @@ -2582,23 +2530,23 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.5.11" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.5.11" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", ] [[package]] @@ -2628,15 +2576,31 @@ version = "0.2.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" +[[package]] +name = "objc-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e1d07c6eab1ce8b6382b8e3c7246fe117ff3f8b34be065f5ebace6749fe845" + [[package]] name = "objc2" version = "0.3.0-beta.3.patch-leaks.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" dependencies = [ - "block2", - "objc-sys", - "objc2-encode", + "block2 0.2.0-alpha.6", + "objc-sys 0.2.0-beta.2", + "objc2-encode 2.0.0-pre.2", +] + +[[package]] +name = "objc2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" +dependencies = [ + "objc-sys 0.3.1", + "objc2-encode 3.0.0", ] [[package]] @@ -2645,9 +2609,15 @@ version = "2.0.0-pre.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abfcac41015b00a120608fdaa6938c44cb983fee294351cc4bac7638b4e50512" dependencies = [ - "objc-sys", + "objc-sys 0.2.0-beta.2", ] +[[package]] +name = "objc2-encode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + [[package]] name = "objc_exception" version = "0.1.2" @@ -2706,12 +2676,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "os_str_bytes" -version = "6.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" - [[package]] name = "owned_ttf_parser" version = "0.19.0" @@ -2811,14 +2775,14 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plist" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06" +checksum = "e5699cc8a63d1aa2b1ee8e12b9ad70ac790d65788cd36101fa37f87ea46c4cef" dependencies = [ - "base64 0.21.4", - "indexmap 1.9.3", + "base64", + "indexmap", "line-wrap", - "quick-xml", + "quick-xml 0.31.0", "serde", "time", ] @@ -2862,12 +2826,32 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "polling" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.21", + "tracing", + "windows-sys 0.48.0", +] + [[package]] name = "pollster" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2945,9 +2929,18 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] @@ -3003,6 +2996,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "raw-window-handle" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" + [[package]] name = "rctree" version = "0.5.0" @@ -3075,9 +3074,9 @@ checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" [[package]] name = "resvg" -version = "0.28.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c115863f2d3621999cf187e318bc92b16402dfeff6a48c74df700d77381394c1" +checksum = "cadccb3d99a9efb8e5e00c16fbb732cbe400db2ec7fc004697ee7d97d86cf1f4" dependencies = [ "log", "pico-args", @@ -3105,7 +3104,7 @@ dependencies = [ "objc", "objc-foundation", "objc_id", - "raw-window-handle", + "raw-window-handle 0.5.2", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -3142,7 +3141,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64 0.21.4", + "base64", "bitflags 2.4.0", "serde", "serde_derive", @@ -3150,12 +3149,9 @@ dependencies = [ [[package]] name = "roxmltree" -version = "0.15.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9de9831a129b122e7e61f242db509fa9d0838008bf0b29bb0624669edfe48a" -dependencies = [ - "xmlparser", -] +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" [[package]] name = "rustc-demangle" @@ -3185,14 +3181,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.7", + "linux-raw-sys 0.4.11", "windows-sys 0.48.0", ] @@ -3283,9 +3279,9 @@ dependencies = [ [[package]] name = "sctk-adwaita" -version = "0.5.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" +checksum = "82b2eaf3a5b264a521b988b2e73042e742df700c4f962cde845d1541adb46550" dependencies = [ "ab_glyph", "log", @@ -3420,31 +3416,47 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smithay-client-toolkit" -version = "0.16.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" +checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", "calloop", - "dlib", - "lazy_static", + "calloop-wayland-source", + "cursor-icon", + "libc", "log", "memmap2", - "nix 0.24.3", - "pkg-config", + "rustix 0.38.21", + "thiserror", + "wayland-backend", "wayland-client", + "wayland-csd-frame", "wayland-cursor", "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", ] [[package]] name = "smithay-clipboard" -version = "0.6.6" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" +checksum = "0bb62b280ce5a5cba847669933a0948d00904cf83845c944eae96a4738cea1a6" dependencies = [ + "libc", "smithay-client-toolkit", - "wayland-client", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +dependencies = [ + "serde", ] [[package]] @@ -3505,10 +3517,11 @@ dependencies = [ [[package]] name = "svgtypes" -version = "0.8.2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22975e8a2bac6a76bb54f898a6b18764633b00e780330f0b689f65afb3975564" +checksum = "6e44e288cd960318917cbd540340968b90becc8bc81f171345d706e7a89d9d70" dependencies = [ + "kurbo", "siphasher", ] @@ -3583,7 +3596,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall 0.3.5", - "rustix 0.38.14", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -3612,12 +3625,6 @@ dependencies = [ "env_logger", ] -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thiserror" version = "1.0.49" @@ -3640,12 +3647,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -3668,23 +3676,24 @@ dependencies = [ [[package]] name = "tiny-skia" -version = "0.8.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +checksum = "b6a067b809476893fce6a254cf285850ff69c847e6cfbade6a20b655b6c7e80d" dependencies = [ "arrayref", "arrayvec", "bytemuck", "cfg-if", + "log", "png", "tiny-skia-path", ] [[package]] name = "tiny-skia-path" -version = "0.8.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +checksum = "5de35e8a90052baaaf61f171680ac2f8e925a1e43ea9d2e3a00514772250e541" dependencies = [ "arrayref", "bytemuck", @@ -3743,7 +3752,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -3843,6 +3852,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.11" @@ -3873,7 +3888,7 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5ccd538d4a604753ebc2f17cd9946e89b77bf87f6a8e2309667c6f2e87855e3" dependencies = [ - "base64 0.21.4", + "base64", "flate2", "log", "once_cell", @@ -3904,29 +3919,47 @@ dependencies = [ [[package]] name = "usvg" -version = "0.28.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b5b7c2b30845b3348c067ca3d09e20cc6e327c288f0ca4c48698712abf432e9" +checksum = "38b0a51b72ab80ca511d126b77feeeb4fb1e972764653e61feac30adc161a756" +dependencies = [ + "base64", + "log", + "pico-args", + "usvg-parser", + "usvg-tree", + "xmlwriter", +] + +[[package]] +name = "usvg-parser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd4e3c291f45d152929a31f0f6c819245e2921bfd01e7bd91201a9af39a2bdc" dependencies = [ - "base64 0.13.1", "data-url", "flate2", "imagesize", "kurbo", "log", - "rctree", "roxmltree", "simplecss", "siphasher", - "strict-num", "svgtypes", + "usvg-tree", ] [[package]] -name = "vec_map" -version = "0.8.2" +name = "usvg-tree" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "8ee3d202ebdb97a6215604b8f5b4d6ef9024efd623cf2e373a6416ba976ec7d3" +dependencies = [ + "rctree", + "strict-num", + "svgtypes", + "tiny-skia-path", +] [[package]] name = "version-compare" @@ -4029,87 +4062,111 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] -name = "wayland-client" -version = "0.29.5" +name = "wayland-backend" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +checksum = "19152ddd73f45f024ed4534d9ca2594e0ef252c1847695255dae47f34df9fbe4" dependencies = [ - "bitflags 1.3.2", + "cc", "downcast-rs", - "libc", - "nix 0.24.3", + "nix", "scoped-tls", - "wayland-commons", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca7d52347346f5473bf2f56705f360e8440873052e575e55890c4fa57843ed3" +dependencies = [ + "bitflags 2.4.0", + "nix", + "wayland-backend", "wayland-scanner", - "wayland-sys 0.29.5", ] [[package]] -name = "wayland-commons" -version = "0.29.5" +name = "wayland-csd-frame" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "nix 0.24.3", - "once_cell", - "smallvec", - "wayland-sys 0.29.5", + "bitflags 2.4.0", + "cursor-icon", + "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.29.5" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +checksum = "a44aa20ae986659d6c77d64d808a046996a932aa763913864dc40c359ef7ad5b" dependencies = [ - "nix 0.24.3", + "nix", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.29.5" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "e253d7107ba913923dc253967f35e8561a3c65f914543e46843c88ddd729e21c" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.0", + "wayland-backend", "wayland-client", - "wayland-commons", "wayland-scanner", ] [[package]] -name = "wayland-scanner" -version = "0.29.5" +name = "wayland-protocols-plasma" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "proc-macro2", - "quote", - "xml-rs", + "bitflags 2.4.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", ] [[package]] -name = "wayland-sys" -version = "0.29.5" +name = "wayland-protocols-wlr" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "dlib", - "lazy_static", - "pkg-config", + "bitflags 2.4.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c" +dependencies = [ + "proc-macro2", + "quick-xml 0.30.0", + "quote", ] [[package]] name = "wayland-sys" -version = "0.30.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" +checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" dependencies = [ "dlib", - "lazy_static", "log", + "once_cell", "pkg-config", ] @@ -4146,7 +4203,7 @@ dependencies = [ "log", "ndk-context", "objc", - "raw-window-handle", + "raw-window-handle 0.5.2", "url", "web-sys", ] @@ -4171,7 +4228,7 @@ dependencies = [ "naga", "parking_lot", "profiling", - "raw-window-handle", + "raw-window-handle 0.5.2", "smallvec", "static_assertions", "wasm-bindgen", @@ -4196,7 +4253,7 @@ dependencies = [ "naga", "parking_lot", "profiling", - "raw-window-handle", + "raw-window-handle 0.5.2", "rustc-hash", "smallvec", "thiserror", @@ -4219,8 +4276,8 @@ dependencies = [ "block", "core-graphics-types", "d3d12", - "glow 0.13.0", - "glutin_wgl_sys 0.5.0", + "glow", + "glutin_wgl_sys", "gpu-alloc", "gpu-allocator", "gpu-descriptor", @@ -4237,7 +4294,7 @@ dependencies = [ "parking_lot", "profiling", "range-alloc", - "raw-window-handle", + "raw-window-handle 0.5.2", "renderdoc-sys", "rustc-hash", "smallvec", @@ -4500,37 +4557,51 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winit" -version = "0.28.7" +version = "0.29.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94" +checksum = "7fd430cd4560ee9c48885a4ef473b609a56796e37b1e18222abee146143f7457" dependencies = [ + "ahash", "android-activity", - "bitflags 1.3.2", + "atomic-waker", + "bitflags 2.4.0", + "bytemuck", + "calloop", "cfg_aliases", "core-foundation", "core-graphics", - "dispatch", - "instant", + "cursor-icon", + "icrate", + "js-sys", "libc", "log", - "mio", + "memmap2", "ndk", - "objc2", + "ndk-sys", + "objc2 0.4.1", "once_cell", "orbclient", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.5.2", + "raw-window-handle 0.6.0", "redox_syscall 0.3.5", + "rustix 0.38.21", "sctk-adwaita", "smithay-client-toolkit", + "smol_str", + "unicode-segmentation", "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", "wayland-client", - "wayland-commons", "wayland-protocols", - "wayland-scanner", + "wayland-protocols-plasma", "web-sys", - "windows-sys 0.45.0", + "web-time", + "windows-sys 0.48.0", "x11-dl", + "x11rb 0.13.0", + "xkbcommon-dl", ] [[package]] @@ -4555,26 +4626,47 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" +checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a" dependencies = [ - "gethostname", - "nix 0.24.3", + "gethostname 0.3.0", + "nix", "winapi", "winapi-wsapoll", - "x11rb-protocol", + "x11rb-protocol 0.12.0", +] + +[[package]] +name = "x11rb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +dependencies = [ + "as-raw-xcb-connection", + "gethostname 0.4.3", + "libc", + "libloading 0.8.0", + "once_cell", + "rustix 0.38.21", + "x11rb-protocol 0.13.0", ] [[package]] name = "x11rb-protocol" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" +checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc" dependencies = [ - "nix 0.24.3", + "nix", ] +[[package]] +name = "x11rb-protocol" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + [[package]] name = "xcursor" version = "0.3.4" @@ -4590,10 +4682,29 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" dependencies = [ - "nix 0.26.4", + "nix", "winapi", ] +[[package]] +name = "xkbcommon-dl" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" +dependencies = [ + "bitflags 2.4.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" + [[package]] name = "xml-rs" version = "0.8.19" @@ -4601,10 +4712,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" [[package]] -name = "xmlparser" -version = "0.13.5" +name = "xmlwriter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "yaml-rust" @@ -4639,7 +4750,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix 0.26.4", + "nix", "once_cell", "ordered-stream", "rand", @@ -4683,18 +4794,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.21" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686b7e407015242119c33dab17b8f61ba6843534de936d94368856528eae4dcc" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.21" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020f3dfe25dfc38dfea49ce62d5d45ecdd7f0d8a724fa63eb36b6eba4ec76806" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index bbf9fb19b03..641073f4d79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ members = [ edition = "2021" license = "MIT OR Apache-2.0" rust-version = "1.72" -version = "0.24.1" +version = "0.24.2" [profile.release] @@ -48,6 +48,8 @@ opt-level = 2 [workspace.dependencies] +criterion = { version = "0.5.1", default-features = false } +glow = "0.13" puffin = "0.18" raw-window-handle = "0.5.0" thiserror = "1.0.37" diff --git a/README.md b/README.md index 59d63e16579..814c3bc23d7 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,7 @@ These are the official egui integrations: ### 3rd party integrations * [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/). +* [`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_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) 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). diff --git a/crates/ecolor/src/color32.rs b/crates/ecolor/src/color32.rs index 56b96589256..f3cf3fe6c95 100644 --- a/crates/ecolor/src/color32.rs +++ b/crates/ecolor/src/color32.rs @@ -7,6 +7,8 @@ use crate::{gamma_u8_from_linear_f32, linear_f32_from_gamma_u8, linear_f32_from_ /// /// Internally this uses 0-255 gamma space `sRGBA` color with premultiplied alpha. /// Alpha channel is in linear space. +/// +/// The special value of alpha=0 means the color is to be treated as an additive color. #[repr(C)] #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -61,7 +63,16 @@ impl Color32 { pub const DEBUG_COLOR: Color32 = Color32::from_rgba_premultiplied(0, 200, 0, 128); /// An ugly color that is planned to be replaced before making it to the screen. - pub const TEMPORARY_COLOR: Color32 = Color32::from_rgb(64, 254, 0); + /// + /// This is an invalid color, in that it does not correspond to a valid multiplied color, + /// nor to an additive color. + /// + /// This is used as a special color key, + /// i.e. often taken to mean "no color". + pub const PLACEHOLDER: Color32 = Color32::from_rgba_premultiplied(64, 254, 0, 128); + + #[deprecated = "Renamed to PLACEHOLDER"] + pub const TEMPORARY_COLOR: Color32 = Self::PLACEHOLDER; #[inline] pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { diff --git a/crates/ecolor/src/lib.rs b/crates/ecolor/src/lib.rs index 06bf55396b2..431e51d85fc 100644 --- a/crates/ecolor/src/lib.rs +++ b/crates/ecolor/src/lib.rs @@ -12,8 +12,6 @@ #[cfg(feature = "cint")] mod cint_impl; -#[cfg(feature = "cint")] -pub use cint_impl::*; mod color32; pub use color32::*; diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 68c0bfd7bd8..09b98abdf5b 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -21,6 +21,7 @@ include = [ [package.metadata.docs.rs] all-features = true +rustc-args = ["--cfg=web_sys_unstable_apis"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [lib] @@ -116,7 +117,7 @@ thiserror.workspace = true document-features = { version = "0.2", optional = true } egui_glow = { version = "0.24.1", path = "../egui_glow", optional = true, default-features = false } -glow = { version = "0.12", optional = true } +glow = { workspace = true, optional = true } ron = { version = "0.8", optional = true, features = ["integer128"] } serde = { version = "1", optional = true, features = ["derive"] } @@ -131,7 +132,7 @@ image = { version = "0.24", default-features = false, features = [ "png", ] } # Needed for app icon raw-window-handle.workspace = true -winit = { version = "0.28.1", default-features = false } +winit = { version = "0.29.4", default-features = false, features = ["rwh_05"] } # optional native: directories-next = { version = "2", optional = true } @@ -142,14 +143,14 @@ pollster = { version = "0.3", 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 = { version = "0.30", optional = true } -glutin-winit = { version = "0.3.0", optional = true } +glutin = { version = "0.31", optional = true } +glutin-winit = { version = "0.4", optional = true } puffin = { workspace = true, optional = true } wgpu = { workspace = true, optional = true } # mac: [target.'cfg(any(target_os = "macos"))'.dependencies] -cocoa = "0.24.1" # Stuck on old version until we update to winit 0.29 +cocoa = "0.25.0" objc = "0.2.7" # windows: diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 982eb54814d..8f96b7962eb 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -64,7 +64,7 @@ pub struct CreationContext<'s> { /// /// Only available when compiling with the `glow` feature and using [`Renderer::Glow`]. #[cfg(feature = "glow")] - pub gl: Option>, + pub gl: Option>, /// The underlying WGPU render state. /// @@ -217,7 +217,7 @@ pub enum HardwareAcceleration { /// Options controlling the behavior of a native window. /// -/// Addintional windows can be opened using (egui viewports)[`egui::viewport`]. +/// Additional windows can be opened using (egui viewports)[`egui::viewport`]. /// /// Set the window title and size using [`Self::viewport`]. /// @@ -298,7 +298,7 @@ pub struct NativeOptions { /// /// This feature was introduced in . /// - /// When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used. + /// When `true`, [`winit::platform::run_on_demand::EventLoopExtRunOnDemand`] is used. /// When `false`, [`winit::event_loop::EventLoop::run`] is used. pub run_and_return: bool, @@ -526,16 +526,23 @@ pub enum Renderer { #[cfg(any(feature = "glow", feature = "wgpu"))] impl Default for Renderer { fn default() -> Self { + #[cfg(not(feature = "glow"))] + #[cfg(not(feature = "wgpu"))] + compile_error!("eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"); + #[cfg(feature = "glow")] + #[cfg(not(feature = "wgpu"))] return Self::Glow; #[cfg(not(feature = "glow"))] #[cfg(feature = "wgpu")] return Self::Wgpu; - #[cfg(not(feature = "glow"))] - #[cfg(not(feature = "wgpu"))] - compile_error!("eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"); + // By default, only the `glow` feature is enabled, so if the user added `wgpu` to the feature list + // they probably wanted to use wgpu: + #[cfg(feature = "glow")] + #[cfg(feature = "wgpu")] + return Self::Wgpu; } } @@ -574,7 +581,7 @@ impl std::str::FromStr for Renderer { /// Represents the surroundings of your app. /// /// It provides methods to inspect the surroundings (are we on the web?), -/// allocate textures, and change settings (e.g. window size). +/// access to persistent storage, and access to the rendering backend. pub struct Frame { /// Information about the integration. pub(crate) info: IntegrationInfo, @@ -584,7 +591,7 @@ pub struct Frame { /// A reference to the underlying [`glow`] (OpenGL) context. #[cfg(feature = "glow")] - pub(crate) gl: Option>, + pub(crate) gl: Option>, /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. #[cfg(feature = "wgpu")] @@ -656,7 +663,7 @@ impl Frame { /// To get a [`glow`] context you need to compile with the `glow` feature flag, /// and run eframe using [`Renderer::Glow`]. #[cfg(feature = "glow")] - pub fn gl(&self) -> Option<&std::rc::Rc> { + pub fn gl(&self) -> Option<&std::sync::Arc> { self.gl.as_ref() } diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index fd6437e305e..d8b5bd1fdf9 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -9,6 +9,21 @@ //! In short, you implement [`App`] (especially [`App::update`]) and then //! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`. //! +//! ## Compiling for web +//! To get copy-paste working on web, you need to compile with +//! `export RUSTFLAGS=--cfg=web_sys_unstable_apis`. +//! +//! You need to install the `wasm32` target with `rustup target add wasm32-unknown-unknown`. +//! +//! Build the `.wasm` using `cargo build --target wasm32-unknown-unknown` +//! and then use [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) to generate the JavaScript glue code. +//! +//! See the [`eframe_template` repository](https://github.com/emilk/eframe_template/) for more. +//! +//! ## Simplified usage +//! If your app is only for native, and you don't need advanced features like state persistence, +//! then you can use the simpler function [`run_simple_native`]. +//! //! ## Usage, native: //! ``` no_run //! use eframe::egui; @@ -114,10 +129,6 @@ //! } //! ``` //! -//! ## Simplified usage -//! If your app is only for native, and you don't need advanced features like state persistence, -//! then you can use the simpler function [`run_simple_native`]. -//! //! ## Feature flags #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] //! @@ -325,6 +336,11 @@ pub enum Error { #[error("winit error: {0}")] Winit(#[from] winit::error::OsError), + /// An error from [`winit::event_loop::EventLoop`]. + #[cfg(not(target_arch = "wasm32"))] + #[error("winit EventLoopError: {0}")] + WinitEventLoop(#[from] winit::error::EventLoopError), + /// An error from [`glutin`] when using [`glow`]. #[cfg(all(feature = "glow", not(target_arch = "wasm32")))] #[error("glutin error: {0}")] diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index efd8135cce5..e80785b5d37 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -152,7 +152,7 @@ impl EpiIntegration { app_name: &str, native_options: &crate::NativeOptions, storage: Option>, - #[cfg(feature = "glow")] gl: Option>, + #[cfg(feature = "glow")] gl: Option>, #[cfg(feature = "wgpu")] wgpu_render_state: Option, ) -> Self { let frame = epi::Frame { @@ -231,7 +231,7 @@ impl EpiIntegration { &mut self, window: &winit::window::Window, egui_winit: &mut egui_winit::State, - event: &winit::event::WindowEvent<'_>, + event: &winit::event::WindowEvent, ) -> EventResponse { crate::profile_function!(egui_winit::short_window_event_description(event)); @@ -255,8 +255,7 @@ impl EpiIntegration { _ => {} } - egui_winit.update_pixels_per_point(&self.egui_ctx, window); - egui_winit.on_window_event(&self.egui_ctx, event) + egui_winit.on_window_event(window, event) } pub fn pre_update(&mut self) { diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 6bf7336af85..922039bf307 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -1,8 +1,19 @@ +//! Note that this file contains code very similar to [`wgpu_integration`]. +//! When making changes to one you often also want to apply it to the other. +//! +//! This is also very complex code, and not very pretty. +//! There is a bunch of improvements we could do, +//! like removing a bunch of `unwraps`. + +#![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e + use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant}; use glutin::{ + config::GlConfig, + context::NotCurrentGlContext, display::GetGlDisplay, - prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext}, + prelude::{GlDisplay, PossiblyCurrentGlContext}, surface::GlSurface, }; use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _}; @@ -112,6 +123,8 @@ struct Viewport { /// None for immediate viewports. viewport_ui_cb: Option>, + // These three live and die together. + // TODO(emilk): clump them together into one struct! gl_surface: Option>, window: Option>, egui_winit: Option, @@ -160,17 +173,17 @@ impl GlowWinitApp { }; // Creates the window - must come before we create our glow context - glutin_window_context.on_resume(event_loop)?; + glutin_window_context.initialize_window(ViewportId::ROOT, event_loop)?; - if let Some(viewport) = glutin_window_context.viewports.get(&ViewportId::ROOT) { - if let Some(window) = &viewport.window { - epi_integration::apply_window_settings(window, window_settings); - } + { + let viewport = &glutin_window_context.viewports[&ViewportId::ROOT]; + let window = viewport.window.as_ref().unwrap(); // Can't fail - we just called `initialize_all_viewports` + epi_integration::apply_window_settings(window, window_settings); } let gl = unsafe { crate::profile_scope!("glow::Context::from_loader_function"); - Rc::new(glow::Context::from_loader_function(|s| { + Arc::new(glow::Context::from_loader_function(|s| { let s = std::ffi::CString::new(s) .expect("failed to construct C string from string for gl proc address"); @@ -390,9 +403,13 @@ impl WinitApp for GlowWinitApp { } } - fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult { + fn run_ui_and_paint( + &mut self, + event_loop: &EventLoopWindowTarget, + window_id: WindowId, + ) -> EventResult { if let Some(running) = &mut self.running { - running.run_ui_and_paint(window_id) + running.run_ui_and_paint(event_loop, window_id) } else { EventResult::Wait } @@ -401,29 +418,27 @@ impl WinitApp for GlowWinitApp { fn on_event( &mut self, event_loop: &EventLoopWindowTarget, - event: &winit::event::Event<'_, UserEvent>, + event: &winit::event::Event, ) -> Result { crate::profile_function!(winit_integration::short_event_description(event)); Ok(match event { winit::event::Event::Resumed => { + log::debug!("Event::Resumed"); + let running = if let Some(running) = &mut self.running { - // not the first resume event. create whatever you need. - running.glutin.borrow_mut().on_resume(event_loop)?; + // Not the first resume event. Create all outstanding windows. + running + .glutin + .borrow_mut() + .initialize_all_windows(event_loop); running } else { - // first resume event. - // we can actually move this outside of event loop. - // and just run the on_resume fn of gl_window + // First resume event. Created our root window etc. self.init_run_state(event_loop)? }; - let window_id = running - .glutin - .borrow() - .window_from_viewport - .get(&ViewportId::ROOT) - .copied(); - EventResult::RepaintNow(window_id.unwrap()) + let window_id = running.glutin.borrow().window_from_viewport[&ViewportId::ROOT]; + EventResult::RepaintNow(window_id) } winit::event::Event::Suspended => { @@ -433,15 +448,6 @@ impl WinitApp for GlowWinitApp { EventResult::Wait } - winit::event::Event::MainEventsCleared => { - if let Some(running) = &self.running { - if let Err(err) = running.glutin.borrow_mut().on_resume(event_loop) { - log::warn!("on_resume failed {err}"); - } - } - EventResult::Wait - } - winit::event::Event::WindowEvent { event, window_id } => { if let Some(running) = &mut self.running { running.on_window_event(*window_id, event) @@ -477,7 +483,11 @@ impl WinitApp for GlowWinitApp { } impl GlowWinitRunning { - fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult { + fn run_ui_and_paint( + &mut self, + event_loop: &EventLoopWindowTarget, + window_id: WindowId, + ) -> EventResult { crate::profile_function!(); let Some(viewport_id) = self @@ -517,7 +527,6 @@ impl GlowWinitRunning { egui_winit::update_viewport_info(&mut viewport.info, &egui_ctx, window); let egui_winit = viewport.egui_winit.as_mut().unwrap(); - egui_winit.update_pixels_per_point(&egui_ctx, window); let mut raw_input = egui_winit.take_egui_input(window); let viewport_ui_cb = viewport.viewport_ui_cb.clone(); @@ -533,12 +542,16 @@ impl GlowWinitRunning { (raw_input, viewport_ui_cb) }; - { + let clear_color = self + .app + .clear_color(&self.integration.egui_ctx.style().visuals); + + let has_many_viewports = self.glutin.borrow().viewports.len() > 1; + let clear_before_update = !has_many_viewports; // HACK: for some reason, an early clear doesn't "take" on Mac with multiple viewports. + + if clear_before_update { // clear before we call update, so users can paint between clear-color and egui windows: - let clear_color = self - .app - .clear_color(&self.integration.egui_ctx.style().visuals); let mut glutin = self.glutin.borrow_mut(); let GlutinWindowContext { viewports, @@ -600,7 +613,7 @@ impl GlowWinitRunning { let egui_winit = viewport.egui_winit.as_mut().unwrap(); integration.post_update(); - egui_winit.handle_platform_output(window, &integration.egui_ctx, platform_output); + egui_winit.handle_platform_output(window, platform_output); let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point); @@ -609,6 +622,10 @@ impl GlowWinitRunning { let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); + if !clear_before_update { + painter.clear(screen_size_in_pixels, clear_color); + } + painter.paint_and_update_textures( screen_size_in_pixels, pixels_per_point, @@ -659,7 +676,7 @@ impl GlowWinitRunning { std::thread::sleep(std::time::Duration::from_millis(10)); } - glutin.handle_viewport_output(&integration.egui_ctx, viewport_output); + glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output); if integration.should_close() { EventResult::Exit @@ -671,7 +688,7 @@ impl GlowWinitRunning { fn on_window_event( &mut self, window_id: WindowId, - event: &winit::event::WindowEvent<'_>, + event: &winit::event::WindowEvent, ) -> EventResult { crate::profile_function!(egui_winit::short_window_event_description(event)); @@ -710,13 +727,6 @@ impl GlowWinitRunning { } } - winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - if let Some(viewport_id) = viewport_id { - repaint_asap = true; - glutin.resize(viewport_id, **new_inner_size); - } - } - winit::event::WindowEvent::CloseRequested => { if viewport_id == Some(ViewportId::ROOT) && self.integration.should_close() { log::debug!( @@ -761,7 +771,11 @@ impl GlowWinitRunning { { event_response = self.integration.on_window_event(window, egui_winit, event); } + } else { + log::trace!("Ignoring event: no viewport for {viewport_id:?}"); } + } else { + log::trace!("Ignoring event: no viewport_id"); } if event_response.repaint { @@ -848,7 +862,7 @@ 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::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150 + .with_preference(glutin_winit::ApiPreference::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150 .with_window_builder(Some(egui_winit::create_winit_window_builder( egui_ctx, event_loop, @@ -961,37 +975,29 @@ impl GlutinWindowContext { focused_viewport: Some(ViewportId::ROOT), }; - slf.on_resume(event_loop)?; + slf.initialize_window(ViewportId::ROOT, event_loop)?; Ok(slf) } - /// This will be run after `new`. on android, it might be called multiple times over the course of the app's lifetime. - /// roughly, - /// 1. check if window already exists. otherwise, create one now. - /// 2. create attributes for surface creation. - /// 3. create surface. - /// 4. make surface and context current. + /// Create a surface, window, and winit integration for all viewports lacking any of that. /// - /// we presently assume that we will - fn on_resume(&mut self, event_loop: &EventLoopWindowTarget) -> Result<()> { + /// Errors will be logged. + fn initialize_all_windows(&mut self, event_loop: &EventLoopWindowTarget) { crate::profile_function!(); - let viewports: Vec = self - .viewports - .iter() - .filter(|(_, viewport)| viewport.gl_surface.is_none()) - .map(|(id, _)| *id) - .collect(); + let viewports: Vec = self.viewports.keys().copied().collect(); for viewport_id in viewports { - self.init_viewport(viewport_id, event_loop)?; + if let Err(err) = self.initialize_window(viewport_id, event_loop) { + log::error!("Failed to initialize a window for viewport {viewport_id:?}: {err}"); + } } - Ok(()) } + /// Create a surface, window, and winit integration for the viewport, if missing. #[allow(unsafe_code)] - pub(crate) fn init_viewport( + pub(crate) fn initialize_window( &mut self, viewport_id: ViewportId, event_loop: &EventLoopWindowTarget, @@ -1006,12 +1012,16 @@ impl GlutinWindowContext { let window = if let Some(window) = &mut viewport.window { window } else { - log::trace!("Window doesn't exist yet. Creating one now with finalize_window"); + log::debug!("Creating a window for viewport {viewport_id:?}"); let window_builder = egui_winit::create_winit_window_builder( &self.egui_ctx, event_loop, viewport.builder.clone(), ); + if window_builder.transparent() && self.gl_config.supports_transparency() == Some(false) + { + log::error!("Cannot create transparent window: the GL config does not support it"); + } let window = glutin_winit::finalize_window(event_loop, window_builder, &self.gl_config)?; egui_winit::apply_viewport_builder_to_window( @@ -1024,7 +1034,20 @@ impl GlutinWindowContext { viewport.window.insert(Rc::new(window)) }; - { + viewport.egui_winit.get_or_insert_with(|| { + log::debug!("Initializing egui_winit for viewport {viewport_id:?}"); + egui_winit::State::new( + self.egui_ctx.clone(), + viewport_id, + event_loop, + Some(window.scale_factor() as f32), + self.max_texture_side, + ) + }); + + if viewport.gl_surface.is_none() { + log::debug!("Creating a gl_surface for viewport {viewport_id:?}"); + // surface attributes let (width_px, height_px): (u32, u32) = window.inner_size().into(); let width_px = std::num::NonZeroU32::new(width_px.at_least(1)).unwrap(); @@ -1064,23 +1087,14 @@ impl GlutinWindowContext { // we will reach this point only once in most platforms except android. // create window/surface/make context current once and just use them forever. - viewport.egui_winit.get_or_insert_with(|| { - egui_winit::State::new( - viewport_id, - event_loop, - Some(window.scale_factor() as f32), - self.max_texture_side, - ) - }); - viewport.gl_surface = Some(gl_surface); + self.current_gl_context = Some(current_gl_context); - self.viewport_from_window - .insert(window.id(), viewport.ids.this); - self.window_from_viewport - .insert(viewport.ids.this, window.id()); } + self.viewport_from_window.insert(window.id(), viewport_id); + self.window_from_viewport.insert(viewport_id, window.id()); + Ok(()) } @@ -1145,6 +1159,7 @@ impl GlutinWindowContext { fn handle_viewport_output( &mut self, + event_loop: &EventLoopWindowTarget, egui_ctx: &egui::Context, viewport_output: ViewportIdMap, ) { @@ -1189,6 +1204,9 @@ impl GlutinWindowContext { } } + // Create windows for any new viewports: + self.initialize_all_windows(event_loop); + // GC old viewports self.viewports .retain(|id, _| active_viewports_ids.contains(id)); @@ -1287,10 +1305,12 @@ fn render_immediate_viewport( viewport_ui_cb, } = immediate_viewport; + let viewport_id = ids.this; + { let mut glutin = glutin.borrow_mut(); - let viewport = initialize_or_update_viewport( + initialize_or_update_viewport( egui_ctx, &mut glutin.viewports, ids, @@ -1300,17 +1320,18 @@ fn render_immediate_viewport( None, ); - if viewport.gl_surface.is_none() { - glutin - .init_viewport(ids.this, event_loop) - .expect("Failed to initialize window in egui::Context::show_viewport_immediate"); + if let Err(err) = glutin.initialize_window(viewport_id, event_loop) { + log::error!( + "Failed to initialize a window for immediate viewport {viewport_id:?}: {err}" + ); + return; } } let input = { let mut glutin = glutin.borrow_mut(); - let Some(viewport) = glutin.viewports.get_mut(&ids.this) else { + let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else { return; }; let (Some(egui_winit), Some(window)) = (&mut viewport.egui_winit, &viewport.window) else { @@ -1318,7 +1339,6 @@ fn render_immediate_viewport( }; egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window); - egui_winit.update_pixels_per_point(egui_ctx, window); let mut raw_input = egui_winit.take_egui_input(window); raw_input.viewports = glutin .viewports @@ -1355,7 +1375,7 @@ fn render_immediate_viewport( .. } = &mut *glutin; - let Some(viewport) = viewports.get_mut(&ids.this) else { + let Some(viewport) = viewports.get_mut(&viewport_id) else { return; }; @@ -1414,9 +1434,9 @@ fn render_immediate_viewport( } } - egui_winit.handle_platform_output(window, egui_ctx, platform_output); + egui_winit.handle_platform_output(window, platform_output); - glutin.handle_viewport_output(egui_ctx, viewport_output); + glutin.handle_viewport_output(event_loop, egui_ctx, viewport_output); } #[cfg(feature = "__screenshot")] diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 5e3dc91554c..95e355a1048 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -1,10 +1,3 @@ -//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`]. -//! When making changes to one you often also want to apply it to the other. -//! -//! This is also very complex code, and not very pretty. -//! There is a bunch of improvements we could do, -//! like removing a bunch of `unwraps`. - use std::{cell::RefCell, time::Instant}; use winit::event_loop::{EventLoop, EventLoopBuilder}; @@ -34,12 +27,12 @@ fn create_event_loop_builder( event_loop_builder } -fn create_event_loop(native_options: &mut epi::NativeOptions) -> EventLoop { +fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result> { crate::profile_function!(); let mut builder = create_event_loop_builder(native_options); crate::profile_scope!("EventLoopBuilder::build"); - builder.build() + Ok(builder.build()?) } /// Access a thread-local event loop. @@ -49,16 +42,20 @@ fn create_event_loop(native_options: &mut epi::NativeOptions) -> EventLoop( mut native_options: epi::NativeOptions, f: impl FnOnce(&mut EventLoop, epi::NativeOptions) -> R, -) -> R { +) -> Result { thread_local!(static EVENT_LOOP: RefCell>> = RefCell::new(None)); EVENT_LOOP.with(|event_loop| { // Since we want to reference NativeOptions when creating the EventLoop we can't // do that as part of the lazy thread local storage initialization and so we instead // create the event loop lazily here - let mut event_loop = event_loop.borrow_mut(); - let event_loop = event_loop.get_or_insert_with(|| create_event_loop(&mut native_options)); - f(event_loop, native_options) + let mut event_loop_lock = event_loop.borrow_mut(); + let event_loop = if let Some(event_loop) = &mut *event_loop_lock { + event_loop + } else { + event_loop_lock.insert(create_event_loop(&mut native_options)?) + }; + Ok(f(event_loop, native_options)) }) } @@ -67,31 +64,39 @@ fn run_and_return( event_loop: &mut EventLoop, mut winit_app: impl WinitApp, ) -> Result<()> { - use winit::{event_loop::ControlFlow, platform::run_return::EventLoopExtRunReturn as _}; + use winit::{event_loop::ControlFlow, platform::run_on_demand::EventLoopExtRunOnDemand}; - log::debug!("Entering the winit event loop (run_return)…"); + log::debug!("Entering the winit event loop (run_on_demand)…"); // When to repaint what window let mut windows_next_repaint_times = HashMap::default(); let mut returned_result = Ok(()); - event_loop.run_return(|event, event_loop, control_flow| { + event_loop.run_on_demand(|event, event_loop_window_target| { crate::profile_scope!("winit_event", short_event_description(&event)); + log::trace!("winit event: {event:?}"); + + if matches!(event, winit::event::Event::AboutToWait) { + return; // early-out: don't trigger another wait + } + let event_result = match &event { - winit::event::Event::LoopDestroyed => { - // On Mac, Cmd-Q we get here and then `run_return` doesn't return (despite its name), + winit::event::Event::LoopExiting => { + // On Mac, Cmd-Q we get here and then `run_on_demand` doesn't return (despite its name), // so we need to save state now: - log::debug!("Received Event::LoopDestroyed - saving app state…"); + log::debug!("Received Event::LoopExiting - saving app state…"); winit_app.save_and_destroy(); - *control_flow = ControlFlow::Exit; return; } - winit::event::Event::RedrawRequested(window_id) => { + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::RedrawRequested, + window_id, + } => { windows_next_repaint_times.remove(window_id); - winit_app.run_ui_and_paint(*window_id) + winit_app.run_ui_and_paint(event_loop_window_target, *window_id) } winit::event::Event::UserEvent(UserEvent::RequestRepaint { @@ -120,8 +125,11 @@ fn run_and_return( EventResult::Wait } - event => match winit_app.on_event(event_loop, event) { - Ok(event_result) => event_result, + event => match winit_app.on_event(event_loop_window_target, event) { + Ok(event_result) => { + log::trace!("event_result: {event_result:?}"); + event_result + } Err(err) => { log::error!("Exiting because of error: {err} during event {event:?}"); returned_result = Err(err); @@ -132,21 +140,28 @@ fn run_and_return( match event_result { EventResult::Wait => { - control_flow.set_wait(); + event_loop_window_target.set_control_flow(ControlFlow::Wait); } EventResult::RepaintNow(window_id) => { - log::trace!("Repaint caused by {}", short_event_description(&event)); + log::trace!( + "RepaintNow of {window_id:?} caused by {}", + short_event_description(&event) + ); if cfg!(target_os = "windows") { // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 windows_next_repaint_times.remove(&window_id); - winit_app.run_ui_and_paint(window_id); + winit_app.run_ui_and_paint(event_loop_window_target, window_id); } else { // Fix for https://github.com/emilk/egui/issues/2425 windows_next_repaint_times.insert(window_id, Instant::now()); } } EventResult::RepaintNext(window_id) => { + log::trace!( + "RepaintNext of {window_id:?} caused by {}", + short_event_description(&event) + ); windows_next_repaint_times.insert(window_id, Instant::now()); } EventResult::RepaintAt(window_id, repaint_time) => { @@ -160,45 +175,35 @@ fn run_and_return( EventResult::Exit => { log::debug!("Asking to exit event loop…"); winit_app.save_and_destroy(); - *control_flow = ControlFlow::Exit; + event_loop_window_target.exit(); return; } } let mut next_repaint_time = windows_next_repaint_times.values().min().copied(); - // This is for not duplicating redraw requests - use winit::event::Event; - if matches!( - event, - Event::RedrawEventsCleared | Event::RedrawRequested(_) | Event::Resumed - ) { - windows_next_repaint_times.retain(|window_id, repaint_time| { - if Instant::now() < *repaint_time { - return true; - }; - - next_repaint_time = None; - control_flow.set_poll(); - - if let Some(window) = winit_app.window(*window_id) { - log::trace!("request_redraw for {window_id:?}"); - window.request_redraw(); - true - } else { - false - } - }); - } + windows_next_repaint_times.retain(|window_id, repaint_time| { + if Instant::now() < *repaint_time { + return true; // not yet ready + }; + + next_repaint_time = None; + event_loop_window_target.set_control_flow(ControlFlow::Poll); + + if let Some(window) = winit_app.window(*window_id) { + log::trace!("request_redraw for {window_id:?}"); + window.request_redraw(); + true + } else { + log::trace!("No window found for {window_id:?}"); + false + } + }); if let Some(next_repaint_time) = next_repaint_time { - let time_until_next = next_repaint_time.saturating_duration_since(Instant::now()); - if time_until_next < std::time::Duration::from_secs(10_000) { - log::trace!("WaitUntil {time_until_next:?}"); - } - control_flow.set_wait_until(next_repaint_time); + event_loop_window_target.set_control_flow(ControlFlow::WaitUntil(next_repaint_time)); }; - }); + })?; log::debug!("eframe window closed"); @@ -211,32 +216,47 @@ fn run_and_return( // we only apply this approach on Windows to minimize the affect. #[cfg(target_os = "windows")] { - event_loop.run_return(|_, _, control_flow| { - control_flow.set_exit(); - }); + event_loop + .run_on_demand(|_, event_loop_window_target| { + event_loop_window_target.exit(); + }) + .ok(); } returned_result } -fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + 'static) -> ! { +fn run_and_exit( + event_loop: EventLoop, + mut winit_app: impl WinitApp + 'static, +) -> Result<()> { + use winit::event_loop::ControlFlow; log::debug!("Entering the winit event loop (run)…"); // When to repaint what window let mut windows_next_repaint_times = HashMap::default(); - event_loop.run(move |event, event_loop, control_flow| { + event_loop.run(move |event, event_loop_window_target| { crate::profile_scope!("winit_event", short_event_description(&event)); + log::trace!("winit event: {event:?}"); + + if matches!(event, winit::event::Event::AboutToWait) { + return; // early-out: don't trigger another wait + } + let event_result = match &event { - winit::event::Event::LoopDestroyed => { - log::debug!("Received Event::LoopDestroyed"); + winit::event::Event::LoopExiting => { + log::debug!("Received Event::LoopExiting"); EventResult::Exit } - winit::event::Event::RedrawRequested(window_id) => { + winit::event::Event::WindowEvent { + event: winit::event::WindowEvent::RedrawRequested, + window_id, + } => { windows_next_repaint_times.remove(window_id); - winit_app.run_ui_and_paint(*window_id) + winit_app.run_ui_and_paint(event_loop_window_target, *window_id) } winit::event::Event::UserEvent(UserEvent::RequestRepaint { @@ -264,8 +284,11 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + EventResult::Wait } - event => match winit_app.on_event(event_loop, event) { - Ok(event_result) => event_result, + event => match winit_app.on_event(event_loop_window_target, event) { + Ok(event_result) => { + log::trace!("event_result: {event_result:?}"); + event_result + } Err(err) => { panic!("eframe encountered a fatal error: {err} during event {event:?}"); } @@ -274,22 +297,22 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + match event_result { EventResult::Wait => { - control_flow.set_wait(); + event_loop_window_target.set_control_flow(ControlFlow::Wait); } EventResult::RepaintNow(window_id) => { - log::trace!("Repaint caused by {}", short_event_description(&event)); + log::trace!("RepaintNow caused by {}", short_event_description(&event)); if cfg!(target_os = "windows") { // Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280 windows_next_repaint_times.remove(&window_id); - winit_app.run_ui_and_paint(window_id); + winit_app.run_ui_and_paint(event_loop_window_target, window_id); } else { // Fix for https://github.com/emilk/egui/issues/2425 windows_next_repaint_times.insert(window_id, Instant::now()); } } EventResult::RepaintNext(window_id) => { - log::trace!("Repaint caused by {}", short_event_description(&event)); + log::trace!("RepaintNext caused by {}", short_event_description(&event)); windows_next_repaint_times.insert(window_id, Instant::now()); } EventResult::RepaintAt(window_id, repaint_time) => { @@ -303,6 +326,8 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + EventResult::Exit => { log::debug!("Quitting - saving app state…"); winit_app.save_and_destroy(); + + log::debug!("Exiting with return code 0"); #[allow(clippy::exit)] std::process::exit(0); } @@ -310,36 +335,25 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + let mut next_repaint_time = windows_next_repaint_times.values().min().copied(); - // This is for not duplicating redraw requests - use winit::event::Event; - if matches!( - event, - Event::RedrawEventsCleared | Event::RedrawRequested(_) | Event::Resumed - ) { - windows_next_repaint_times.retain(|window_id, repaint_time| { - if Instant::now() < *repaint_time { - return true; - } - - next_repaint_time = None; - control_flow.set_poll(); + windows_next_repaint_times.retain(|window_id, repaint_time| { + if Instant::now() < *repaint_time { + return true; // not yet ready + } - if let Some(window) = winit_app.window(*window_id) { - log::trace!("request_redraw for {window_id:?}"); - window.request_redraw(); - true - } else { - false - } - }); - } + next_repaint_time = None; + event_loop_window_target.set_control_flow(ControlFlow::Poll); - if let Some(next_repaint_time) = next_repaint_time { - let time_until_next = next_repaint_time.saturating_duration_since(Instant::now()); - if time_until_next < std::time::Duration::from_secs(10_000) { - log::trace!("WaitUntil {time_until_next:?}"); + if let Some(window) = winit_app.window(*window_id) { + log::trace!("request_redraw for {window_id:?}"); + window.request_redraw(); + true + } else { + log::trace!("No window found for {window_id:?}"); + false } + }); + if let Some(next_repaint_time) = next_repaint_time { // WaitUntil seems to not work on iOS #[cfg(target_os = "ios")] winit_app @@ -350,9 +364,13 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + .map(|window| window.request_redraw()) }); - control_flow.set_wait_until(next_repaint_time); + event_loop_window_target.set_control_flow(ControlFlow::WaitUntil(next_repaint_time)); }; - }) + })?; + + log::debug!("winit event loop unexpectedly returned"); + + Ok(()) } // ---------------------------------------------------------------------------- @@ -370,12 +388,12 @@ pub fn run_glow( return with_event_loop(native_options, |event_loop, native_options| { let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator); run_and_return(event_loop, glow_eframe) - }); + })?; } - let event_loop = create_event_loop(&mut native_options); + let event_loop = create_event_loop(&mut native_options)?; let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator); - run_and_exit(event_loop, glow_eframe); + run_and_exit(event_loop, glow_eframe) } // ---------------------------------------------------------------------------- @@ -393,10 +411,10 @@ pub fn run_wgpu( return with_event_loop(native_options, |event_loop, native_options| { let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator); run_and_return(event_loop, wgpu_eframe) - }); + })?; } - let event_loop = create_event_loop(&mut native_options); + let event_loop = create_event_loop(&mut native_options)?; let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator); - run_and_exit(event_loop, wgpu_eframe); + run_and_exit(event_loop, wgpu_eframe) } diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 3f9cf14e983..8f82b7380b7 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -1,3 +1,10 @@ +//! Note that this file contains code very similar to [`glow_integration`]. +//! When making changes to one you often also want to apply it to the other. +//! +//! This is also very complex code, and not very pretty. +//! There is a bunch of improvements we could do, +//! like removing a bunch of `unwraps`. + use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant}; use parking_lot::Mutex; @@ -109,7 +116,8 @@ impl WgpuWinitApp { } } - fn build_windows(&mut self, event_loop: &EventLoopWindowTarget) { + /// Create a window for all viewports lacking one. + fn initialized_all_windows(&mut self, event_loop: &EventLoopWindowTarget) { let Some(running) = &mut self.running else { return; }; @@ -122,14 +130,12 @@ impl WgpuWinitApp { } = &mut *shared; for viewport in viewports.values_mut() { - if viewport.window.is_none() { - viewport.init_window( - &running.integration.egui_ctx, - viewport_from_window, - painter, - event_loop, - ); - } + viewport.initialize_window( + event_loop, + &running.integration.egui_ctx, + viewport_from_window, + painter, + ); } } @@ -205,6 +211,7 @@ impl WgpuWinitApp { #[allow(unused_mut)] // used for accesskit let mut egui_winit = egui_winit::State::new( + egui_ctx.clone(), ViewportId::ROOT, event_loop, Some(window.scale_factor() as f32), @@ -349,7 +356,13 @@ impl WinitApp for WgpuWinitApp { } } - fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult { + fn run_ui_and_paint( + &mut self, + event_loop: &EventLoopWindowTarget, + window_id: WindowId, + ) -> EventResult { + self.initialized_all_windows(event_loop); + if let Some(running) = &mut self.running { running.run_ui_and_paint(window_id) } else { @@ -360,14 +373,16 @@ impl WinitApp for WgpuWinitApp { fn on_event( &mut self, event_loop: &EventLoopWindowTarget, - event: &winit::event::Event<'_, UserEvent>, + event: &winit::event::Event, ) -> Result { crate::profile_function!(winit_integration::short_event_description(event)); - self.build_windows(event_loop); + self.initialized_all_windows(event_loop); Ok(match event { winit::event::Event::Resumed => { + log::debug!("Event::Resumed"); + let running = if let Some(running) = &self.running { running } else { @@ -488,10 +503,7 @@ impl WgpuWinitRunning { let mut shared_lock = shared.borrow_mut(); let SharedState { - egui_ctx, - viewports, - painter, - .. + viewports, painter, .. } = &mut *shared_lock; if viewport_id != ViewportId::ROOT { @@ -539,7 +551,6 @@ impl WgpuWinitRunning { } let egui_winit = egui_winit.as_mut().unwrap(); - egui_winit.update_pixels_per_point(egui_ctx, window); let mut raw_input = egui_winit.take_egui_input(window); integration.pre_update(); @@ -596,7 +607,7 @@ impl WgpuWinitRunning { viewport_output, } = full_output; - egui_winit.handle_platform_output(window, egui_ctx, platform_output); + egui_winit.handle_platform_output(window, platform_output); { let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point); @@ -663,7 +674,7 @@ impl WgpuWinitRunning { fn on_window_event( &mut self, window_id: WindowId, - event: &winit::event::WindowEvent<'_>, + event: &winit::event::WindowEvent, ) -> EventResult { crate::profile_function!(egui_winit::short_window_event_description(event)); @@ -712,18 +723,6 @@ impl WgpuWinitRunning { } } - winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { - use std::num::NonZeroU32; - if let (Some(width), Some(height), Some(viewport_id)) = ( - NonZeroU32::new(new_inner_size.width), - NonZeroU32::new(new_inner_size.height), - viewport_id, - ) { - repaint_asap = true; - shared.painter.on_window_resized(viewport_id, width, height); - } - } - winit::event::WindowEvent::CloseRequested => { if viewport_id == Some(ViewportId::ROOT) && integration.should_close() { log::debug!( @@ -778,13 +777,18 @@ impl WgpuWinitRunning { } impl Viewport { - fn init_window( + /// Create winit window, if needed. + fn initialize_window( &mut self, + event_loop: &EventLoopWindowTarget, egui_ctx: &egui::Context, windows_id: &mut HashMap, painter: &mut egui_wgpu::winit::Painter, - event_loop: &EventLoopWindowTarget, ) { + if self.window.is_some() { + return; // we already have one + } + crate::profile_function!(); let viewport_id = self.ids.this; @@ -799,6 +803,7 @@ impl Viewport { } self.egui_winit = Some(egui_winit::State::new( + egui_ctx.clone(), viewport_id, event_loop, Some(window.scale_factor() as f32), @@ -872,7 +877,7 @@ fn render_immediate_viewport( None, ); if viewport.window.is_none() { - viewport.init_window(egui_ctx, viewport_from_window, painter, event_loop); + viewport.initialize_window(event_loop, egui_ctx, viewport_from_window, painter); } let (Some(window), Some(egui_winit)) = (&viewport.window, &mut viewport.egui_winit) else { @@ -880,7 +885,6 @@ fn render_immediate_viewport( }; egui_winit::update_viewport_info(&mut viewport.info, egui_ctx, window); - egui_winit.update_pixels_per_point(egui_ctx, window); let mut input = egui_winit.take_egui_input(window); input.viewports = viewports .iter() @@ -944,7 +948,7 @@ fn render_immediate_viewport( false, ); - egui_winit.handle_platform_output(window, &egui_ctx, platform_output); + egui_winit.handle_platform_output(window, platform_output); handle_viewport_output(&egui_ctx, viewport_output, viewports, *focused_viewport); } diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 53de13cc44d..a7e4bb384b3 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -74,12 +74,16 @@ pub trait WinitApp { fn save_and_destroy(&mut self); - fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult; + fn run_ui_and_paint( + &mut self, + event_loop: &EventLoopWindowTarget, + window_id: WindowId, + ) -> EventResult; fn on_event( &mut self, event_loop: &EventLoopWindowTarget, - event: &winit::event::Event<'_, UserEvent>, + event: &winit::event::Event, ) -> crate::Result; } @@ -117,11 +121,9 @@ pub fn system_theme(window: &Window, options: &crate::NativeOptions) -> Option) -> &'static str { - use winit::event::Event; - +pub fn short_event_description(event: &winit::event::Event) -> &'static str { match event { - Event::UserEvent(user_event) => match user_event { + winit::event::Event::UserEvent(user_event) => match user_event { UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint", #[cfg(feature = "accesskit")] UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest", diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index f5aab09a367..42cca65ed21 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -13,7 +13,7 @@ pub struct AppRunner { app: Box, pub(crate) needs_repaint: std::sync::Arc, last_save_time: f64, - pub(crate) text_cursor_pos: Option, + pub(crate) ime: Option, pub(crate) mutable_text_under_cursor: bool, // Output for the last run: @@ -112,7 +112,7 @@ impl AppRunner { app, needs_repaint, last_save_time: now_sec(), - text_cursor_pos: None, + ime: None, mutable_text_under_cursor: false, textures_delta: Default::default(), clipped_primitives: None, @@ -244,7 +244,7 @@ impl AppRunner { copied_text, events: _, // already handled mutable_text_under_cursor, - text_cursor_pos, + ime, #[cfg(feature = "accesskit")] accesskit_update: _, // not currently implemented } = platform_output; @@ -264,9 +264,9 @@ impl AppRunner { self.mutable_text_under_cursor = mutable_text_under_cursor; - if self.text_cursor_pos != text_cursor_pos { - super::text_agent::move_text_cursor(text_cursor_pos, self.canvas_id()); - self.text_cursor_pos = text_cursor_pos; + if self.ime != ime { + super::text_agent::move_text_cursor(ime, self.canvas_id()); + self.ime = ime; } } } diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index 9bb5c0f885a..cec66bc20a5 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -91,6 +91,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa if let Some(key) = egui_key { runner.input.raw.events.push(egui::Event::Key { key, + physical_key: None, // TODO pressed: true, repeat: false, // egui will fill this in for us! modifiers, @@ -157,6 +158,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa if let Some(key) = translate_key(&event.key()) { runner.input.raw.events.push(egui::Event::Key { key, + physical_key: None, // TODO pressed: false, repeat: false, modifiers, @@ -515,6 +517,7 @@ pub(crate) fn install_canvas_events(runner_ref: &WebRunner) -> Result<(), JsValu move |event: web_sys::DragEvent, runner| { if let Some(data_transfer) = event.data_transfer() { + // TODO(https://github.com/emilk/egui/issues/3702): support dropping folders runner.input.raw.hovered_files.clear(); runner.needs_repaint.repaint_asap(); diff --git a/crates/eframe/src/web/input.rs b/crates/eframe/src/web/input.rs index 33ea30b6dce..96cad32e20f 100644 --- a/crates/eframe/src/web/input.rs +++ b/crates/eframe/src/web/input.rs @@ -112,91 +112,7 @@ pub fn should_ignore_key(key: &str) -> bool { /// Web sends all keys as strings, so it is up to us to figure out if it is /// a real text input or the name of a key. pub fn translate_key(key: &str) -> Option { - use egui::Key; - - match key { - "ArrowDown" => Some(Key::ArrowDown), - "ArrowLeft" => Some(Key::ArrowLeft), - "ArrowRight" => Some(Key::ArrowRight), - "ArrowUp" => Some(Key::ArrowUp), - - "Esc" | "Escape" => Some(Key::Escape), - "Tab" => Some(Key::Tab), - "Backspace" => Some(Key::Backspace), - "Enter" => Some(Key::Enter), - "Space" | " " => Some(Key::Space), - - "Help" | "Insert" => Some(Key::Insert), - "Delete" => Some(Key::Delete), - "Home" => Some(Key::Home), - "End" => Some(Key::End), - "PageUp" => Some(Key::PageUp), - "PageDown" => Some(Key::PageDown), - - "-" => Some(Key::Minus), - "+" | "=" => Some(Key::PlusEquals), - - "0" => Some(Key::Num0), - "1" => Some(Key::Num1), - "2" => Some(Key::Num2), - "3" => Some(Key::Num3), - "4" => Some(Key::Num4), - "5" => Some(Key::Num5), - "6" => Some(Key::Num6), - "7" => Some(Key::Num7), - "8" => Some(Key::Num8), - "9" => Some(Key::Num9), - - "a" | "A" => Some(Key::A), - "b" | "B" => Some(Key::B), - "c" | "C" => Some(Key::C), - "d" | "D" => Some(Key::D), - "e" | "E" => Some(Key::E), - "f" | "F" => Some(Key::F), - "g" | "G" => Some(Key::G), - "h" | "H" => Some(Key::H), - "i" | "I" => Some(Key::I), - "j" | "J" => Some(Key::J), - "k" | "K" => Some(Key::K), - "l" | "L" => Some(Key::L), - "m" | "M" => Some(Key::M), - "n" | "N" => Some(Key::N), - "o" | "O" => Some(Key::O), - "p" | "P" => Some(Key::P), - "q" | "Q" => Some(Key::Q), - "r" | "R" => Some(Key::R), - "s" | "S" => Some(Key::S), - "t" | "T" => Some(Key::T), - "u" | "U" => Some(Key::U), - "v" | "V" => Some(Key::V), - "w" | "W" => Some(Key::W), - "x" | "X" => Some(Key::X), - "y" | "Y" => Some(Key::Y), - "z" | "Z" => Some(Key::Z), - - "F1" => Some(Key::F1), - "F2" => Some(Key::F2), - "F3" => Some(Key::F3), - "F4" => Some(Key::F4), - "F5" => Some(Key::F5), - "F6" => Some(Key::F6), - "F7" => Some(Key::F7), - "F8" => Some(Key::F8), - "F9" => Some(Key::F9), - "F10" => Some(Key::F10), - "F11" => Some(Key::F11), - "F12" => Some(Key::F12), - "F13" => Some(Key::F13), - "F14" => Some(Key::F14), - "F15" => Some(Key::F15), - "F16" => Some(Key::F16), - "F17" => Some(Key::F17), - "F18" => Some(Key::F18), - "F19" => Some(Key::F19), - "F20" => Some(Key::F20), - - _ => None, - } + egui::Key::from_name(key) } pub fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers { diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index d4f3b5ca3c6..0bf8b532b7c 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -205,11 +205,13 @@ fn is_mobile() -> Option { // candidate window moves following text element (agent), // so it appears that the IME candidate window moves with text cursor. // On mobile devices, there is no need to do that. -pub fn move_text_cursor(cursor: Option, canvas_id: &str) -> Option<()> { +pub fn move_text_cursor(ime: Option, canvas_id: &str) -> Option<()> { let style = text_agent().style(); - // Note: movint agent on mobile devices will lead to unpredictable scroll. + // Note: moving agent on mobile devices will lead to unpredictable scroll. if is_mobile() == Some(false) { - cursor.as_ref().and_then(|&egui::Pos2 { x, y }| { + ime.as_ref().and_then(|ime| { + let egui::Pos2 { x, y } = ime.cursor_rect.left_top(); + let canvas = canvas_element(canvas_id)?; let bounding_rect = text_agent().get_bounding_client_rect(); let y = (y + (canvas.scroll_top() + canvas.offset_top()) as f32) diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index e02c6dd973a..cd62758688f 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -15,7 +15,7 @@ pub(crate) struct WebPainterGlow { } impl WebPainterGlow { - pub fn gl(&self) -> &std::rc::Rc { + pub fn gl(&self) -> &std::sync::Arc { self.painter.gl() } @@ -24,7 +24,8 @@ impl WebPainterGlow { let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas, options.webgl_context_option)?; - let gl = std::rc::Rc::new(gl); + #[allow(clippy::arc_with_non_send_sync)] + let gl = std::sync::Arc::new(gl); let painter = egui_glow::Painter::new(gl, shader_prefix, None) .map_err(|err| format!("Error starting glow painter: {err}"))?; diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 22200bafdbe..fc11b5792a1 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -51,7 +51,9 @@ wgpu.workspace = true ## Enable this when generating docs. document-features = { version = "0.2", optional = true } -winit = { version = "0.28", default-features = false, optional = true } +winit = { version = "0.29.4", default-features = false, optional = true, features = [ + "rwh_05", +] } # Native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 46e97d0a939..c2ce3e5b986 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -69,6 +69,9 @@ impl RenderState { ) -> Result { crate::profile_scope!("RenderState::create"); // async yield give bad names using `profile_function` + #[cfg(not(target_arch = "wasm32"))] + let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).collect(); + let adapter = { crate::profile_scope!("request_adapter"); instance @@ -78,9 +81,51 @@ impl RenderState { force_fallback_adapter: false, }) .await - .ok_or(WgpuError::NoSuitableAdapterFound)? + .ok_or_else(|| { + #[cfg(not(target_arch = "wasm32"))] + if adapters.is_empty() { + log::info!("No wgpu adapters found"); + } else if adapters.len() == 1 { + log::info!( + "The only available wgpu adapter was not suitable: {}", + adapter_info_summary(&adapters[0].get_info()) + ); + } else { + log::info!( + "No suitable wgpu adapter found out of the {} available ones: {}", + adapters.len(), + describe_adapters(&adapters) + ); + } + + WgpuError::NoSuitableAdapterFound + })? }; + #[cfg(target_arch = "wasm32")] + log::debug!( + "Picked wgpu adapter: {}", + adapter_info_summary(&adapter.get_info()) + ); + + #[cfg(not(target_arch = "wasm32"))] + if adapters.len() == 1 { + log::debug!( + "Picked the only available wgpu adapter: {}", + adapter_info_summary(&adapter.get_info()) + ); + } else { + log::info!( + "There were {} available wgpu adapters: {}", + adapters.len(), + describe_adapters(&adapters) + ); + log::debug!( + "Picked wgpu adapter: {}", + adapter_info_summary(&adapter.get_info()) + ); + } + let capabilities = { crate::profile_scope!("get_capabilities"); surface.get_capabilities(&adapter).formats @@ -106,6 +151,24 @@ impl RenderState { } } +#[cfg(not(target_arch = "wasm32"))] +fn describe_adapters(adapters: &[wgpu::Adapter]) -> String { + if adapters.is_empty() { + "(none)".to_owned() + } else if adapters.len() == 1 { + adapter_info_summary(&adapters[0].get_info()) + } else { + let mut list_string = String::new(); + for adapter in adapters { + if !list_string.is_empty() { + list_string += ", "; + } + list_string += &format!("{{{}}}", adapter_info_summary(&adapter.get_info())); + } + list_string + } +} + /// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`] pub enum SurfaceErrorAction { /// Do nothing and skip the current frame. @@ -116,6 +179,10 @@ pub enum SurfaceErrorAction { } /// Configuration for using wgpu with eframe or the egui-wgpu winit feature. +/// +/// This can be configured with the environment variables: +/// * `WGPU_BACKEND`: `vulkan`, `dx11`, `dx12`, `metal`, `opengl`, `webgpu` +/// * `WGPU_POWER_PREF`: `low`, `high` or `none` #[derive(Clone)] pub struct WgpuConfiguration { /// Backends that should be supported (wgpu will pick one of these) @@ -151,6 +218,7 @@ impl Default for WgpuConfiguration { // (note however, that the GL backend needs to be opted-in via a wgpu feature flag) supported_backends: wgpu::util::backend_bits_from_env() .unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL), + device_descriptor: Arc::new(|adapter| { let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl { wgpu::Limits::downlevel_webgl2_defaults() @@ -169,7 +237,9 @@ impl Default for WgpuConfiguration { }, } }), + present_mode: wgpu::PresentMode::AutoVsync, + power_preference: wgpu::util::power_preference_from_env() .unwrap_or(wgpu::PowerPreference::HighPerformance), @@ -204,7 +274,7 @@ pub fn preferred_framebuffer_format( } formats - .get(0) + .first() .copied() .ok_or(WgpuError::NoSurfaceFormatsAvailable) } @@ -224,6 +294,47 @@ pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option String { + let wgpu::AdapterInfo { + name, + vendor, + device, + device_type, + driver, + driver_info, + backend, + } = &info; + + // Example values: + // > name: "llvmpipe (LLVM 16.0.6, 256 bits)", device_type: Cpu, backend: Vulkan, driver: "llvmpipe", driver_info: "Mesa 23.1.6-arch1.4 (LLVM 16.0.6)" + // > name: "Apple M1 Pro", device_type: IntegratedGpu, backend: Metal, driver: "", driver_info: "" + // > name: "ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)", device_type: IntegratedGpu, backend: Gl, driver: "", driver_info: "" + + let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}"); + + if !name.is_empty() { + summary += &format!(", name: {name:?}"); + } + if !driver.is_empty() { + summary += &format!(", driver: {driver:?}"); + } + if !driver_info.is_empty() { + summary += &format!(", driver_info: {driver_info:?}"); + } + if *vendor != 0 { + // TODO(emilk): decode using https://github.com/gfx-rs/wgpu/blob/767ac03245ee937d3dc552edc13fe7ab0a860eec/wgpu-hal/src/auxil/mod.rs#L7 + summary += &format!(", vendor: 0x{vendor:04X}"); + } + if *device != 0 { + summary += &format!(", device: 0x{device:02X}"); + } + + summary +} + +// --------------------------------------------------------------------------- + mod profiling_scopes { #![allow(unused_macros)] #![allow(unused_imports)] diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 178d99cdaf2..8e8a8108ab3 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -61,12 +61,12 @@ egui = { version = "0.24.1", path = "../egui", default-features = false, feature log = { version = "0.4", features = ["std"] } raw-window-handle.workspace = true web-time = { version = "0.2" } # We use web-time so we can (maybe) compile for web -winit = { version = "0.28", default-features = false } +winit = { version = "0.29.4", default-features = false, features = ["rwh_05"] } #! ### Optional dependencies # feature accesskit -accesskit_winit = { version = "0.15.0", optional = true } +accesskit_winit = { version = "0.16.0", optional = true } ## Enable this when generating docs. document-features = { version = "0.2", optional = true } @@ -76,7 +76,7 @@ serde = { version = "1.0", optional = true, features = ["derive"] } webbrowser = { version = "0.8.3", optional = true } [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] -smithay-clipboard = { version = "0.6.3", optional = true } +smithay-clipboard = { version = "0.7.0", optional = true } [target.'cfg(not(target_os = "android"))'.dependencies] arboard = { version = "3.2", optional = true, default-features = false } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index f22dd81d2f4..c7123648b1b 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -69,6 +69,9 @@ pub struct EventResponse { /// /// Instantiate one of these per viewport/window. pub struct State { + /// Shared clone. + egui_ctx: egui::Context, + viewport_id: ViewportId, start_time: web_time::Instant, egui_input: egui::RawInput, @@ -76,9 +79,6 @@ pub struct State { any_pointer_button_down: bool, current_cursor_icon: Option, - /// What egui uses. - current_pixels_per_point: f32, // TODO: remove - calculate with [`pixels_per_point`] instead - clipboard: clipboard::Clipboard, /// If `true`, mouse inputs will be treated as touches. @@ -104,6 +104,7 @@ pub struct State { impl State { /// Construct a new instance pub fn new( + egui_ctx: egui::Context, viewport_id: ViewportId, display_target: &dyn HasRawDisplayHandle, native_pixels_per_point: Option, @@ -117,13 +118,13 @@ impl State { }; let mut slf = Self { + egui_ctx, viewport_id, start_time: web_time::Instant::now(), egui_input, pointer_pos_in_points: None, any_pointer_button_down: false, current_cursor_icon: None, - current_pixels_per_point: native_pixels_per_point.unwrap_or(1.0), clipboard: clipboard::Clipboard::new(display_target), @@ -172,9 +173,8 @@ impl State { } #[inline] - #[deprecated = "Use egui_winit::pixels_per_point instead"] - pub fn pixels_per_point(&self) -> f32 { - self.current_pixels_per_point + pub fn egui_ctx(&self) -> &egui::Context { + &self.egui_ctx } /// The current input state. @@ -191,18 +191,12 @@ impl State { &mut self.egui_input } - /// Update the given viewport info with the current state of the window. - #[deprecated = "Use egui_winit::update_viewport_info instead"] - pub fn update_viewport_info(&self, info: &mut ViewportInfo, window: &Window) { - update_viewport_info_impl(info, window, self.current_pixels_per_point); - } - /// Prepare for a new frame by extracting the accumulated input, /// /// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect). /// /// You need to set [`egui::RawInput::viewports`] yourself though. - /// Use [`Self::update_viewport_info`] to update the info for each + /// Use [`update_viewport_info`] to update the info for each /// viewport. pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput { crate::profile_function!(); @@ -213,7 +207,8 @@ impl State { // See: https://github.com/rust-windowing/winit/issues/208 // This solves an issue where egui window positions would be changed when minimizing on Windows. let screen_size_in_pixels = screen_size_in_pixels(window); - let screen_size_in_points = screen_size_in_pixels / self.current_pixels_per_point; + let screen_size_in_points = + screen_size_in_pixels / pixels_per_point(&self.egui_ctx, window); self.egui_input.screen_rect = (screen_size_in_points.x > 0.0 && screen_size_in_points.y > 0.0) @@ -231,22 +226,21 @@ impl State { self.egui_input.take() } - // TODO(emilk): remove asap. - #[doc(hidden)] - pub fn update_pixels_per_point(&mut self, egui_ctx: &egui::Context, window: &Window) { - self.current_pixels_per_point = pixels_per_point(egui_ctx, window); - } - /// Call this when there is a new event. /// /// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`]. pub fn on_window_event( &mut self, - egui_ctx: &egui::Context, - event: &winit::event::WindowEvent<'_>, + window: &Window, + event: &winit::event::WindowEvent, ) -> EventResponse { crate::profile_function!(short_window_event_description(event)); + #[cfg(feature = "accesskit")] + if let Some(accesskit) = &self.accesskit { + accesskit.process_event(window, event); + } + use winit::event::WindowEvent; match event { WindowEvent::ScaleFactorChanged { scale_factor, .. } => { @@ -257,7 +251,7 @@ impl State { .entry(self.viewport_id) .or_default() .native_pixels_per_point = Some(native_pixels_per_point); - self.current_pixels_per_point = egui_ctx.zoom_factor() * native_pixels_per_point; + EventResponse { repaint: true, consumed: false, @@ -267,21 +261,21 @@ impl State { self.on_mouse_button_input(*state, *button); EventResponse { repaint: true, - consumed: egui_ctx.wants_pointer_input(), + consumed: self.egui_ctx.wants_pointer_input(), } } WindowEvent::MouseWheel { delta, .. } => { - self.on_mouse_wheel(*delta); + self.on_mouse_wheel(window, *delta); EventResponse { repaint: true, - consumed: egui_ctx.wants_pointer_input(), + consumed: self.egui_ctx.wants_pointer_input(), } } WindowEvent::CursorMoved { position, .. } => { - self.on_cursor_moved(*position); + self.on_cursor_moved(window, *position); EventResponse { repaint: true, - consumed: egui_ctx.is_using_pointer(), + consumed: self.egui_ctx.is_using_pointer(), } } WindowEvent::CursorLeft { .. } => { @@ -294,37 +288,19 @@ impl State { } // WindowEvent::TouchpadPressure {device_id, pressure, stage, .. } => {} // TODO WindowEvent::Touch(touch) => { - self.on_touch(touch); + self.on_touch(window, touch); let consumed = match touch.phase { winit::event::TouchPhase::Started | winit::event::TouchPhase::Ended - | winit::event::TouchPhase::Cancelled => egui_ctx.wants_pointer_input(), - winit::event::TouchPhase::Moved => egui_ctx.is_using_pointer(), + | winit::event::TouchPhase::Cancelled => self.egui_ctx.wants_pointer_input(), + winit::event::TouchPhase::Moved => self.egui_ctx.is_using_pointer(), }; EventResponse { repaint: true, consumed, } } - WindowEvent::ReceivedCharacter(ch) => { - // On Mac we get here when the user presses Cmd-C (copy), ctrl-W, etc. - // We need to ignore these characters that are side-effects of commands. - let is_mac_cmd = cfg!(target_os = "macos") - && (self.egui_input.modifiers.ctrl || self.egui_input.modifiers.mac_cmd); - let consumed = if is_printable_char(*ch) && !is_mac_cmd { - self.egui_input - .events - .push(egui::Event::Text(ch.to_string())); - egui_ctx.wants_keyboard_input() - } else { - false - }; - EventResponse { - repaint: true, - consumed, - } - } WindowEvent::Ime(ime) => { // on Mac even Cmd-C is pressed during ime, a `c` is pushed to Preedit. // So no need to check is_mac_cmd. @@ -361,14 +337,15 @@ impl State { EventResponse { repaint: true, - consumed: egui_ctx.wants_keyboard_input(), + consumed: self.egui_ctx.wants_keyboard_input(), } } - WindowEvent::KeyboardInput { input, .. } => { - self.on_keyboard_input(input); + WindowEvent::KeyboardInput { event, .. } => { // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. - let consumed = egui_ctx.wants_keyboard_input() - || input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab); + let consumed = self.on_keyboard_input(event) + || self.egui_ctx.wants_keyboard_input() + || event.logical_key + == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab); EventResponse { repaint: true, consumed, @@ -416,15 +393,23 @@ impl State { } } WindowEvent::ModifiersChanged(state) => { - self.egui_input.modifiers.alt = state.alt(); - self.egui_input.modifiers.ctrl = state.ctrl(); - self.egui_input.modifiers.shift = state.shift(); - self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && state.logo(); + let state = state.state(); + + let alt = state.alt_key(); + let ctrl = state.control_key(); + let shift = state.shift_key(); + let super_ = state.super_key(); + + self.egui_input.modifiers.alt = alt; + self.egui_input.modifiers.ctrl = ctrl; + self.egui_input.modifiers.shift = shift; + self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && super_; self.egui_input.modifiers.command = if cfg!(target_os = "macos") { - state.logo() + super_ } else { - state.ctrl() + ctrl }; + EventResponse { repaint: true, consumed: false, @@ -432,7 +417,8 @@ impl State { } // Things that may require repaint: - WindowEvent::CursorEntered { .. } + WindowEvent::RedrawRequested + | WindowEvent::CursorEntered { .. } | WindowEvent::Destroyed | WindowEvent::Occluded(_) | WindowEvent::Resized(_) @@ -445,7 +431,8 @@ impl State { }, // Things we completely ignore: - WindowEvent::AxisMotion { .. } + WindowEvent::ActivationTokenDone { .. } + | WindowEvent::AxisMotion { .. } | WindowEvent::SmartMagnify { .. } | WindowEvent::TouchpadRotate { .. } => EventResponse { repaint: false, @@ -459,7 +446,7 @@ impl State { self.egui_input.events.push(egui::Event::Zoom(zoom_factor)); EventResponse { repaint: true, - consumed: egui_ctx.wants_pointer_input(), + consumed: self.egui_ctx.wants_pointer_input(), } } } @@ -520,10 +507,16 @@ impl State { } } - fn on_cursor_moved(&mut self, pos_in_pixels: winit::dpi::PhysicalPosition) { + fn on_cursor_moved( + &mut self, + window: &Window, + pos_in_pixels: winit::dpi::PhysicalPosition, + ) { + let pixels_per_point = pixels_per_point(&self.egui_ctx, window); + let pos_in_points = egui::pos2( - pos_in_pixels.x as f32 / self.current_pixels_per_point, - pos_in_pixels.y as f32 / self.current_pixels_per_point, + pos_in_pixels.x as f32 / pixels_per_point, + pos_in_pixels.y as f32 / pixels_per_point, ); self.pointer_pos_in_points = Some(pos_in_points); @@ -548,7 +541,9 @@ impl State { } } - fn on_touch(&mut self, touch: &winit::event::Touch) { + fn on_touch(&mut self, window: &Window, touch: &winit::event::Touch) { + let pixels_per_point = pixels_per_point(&self.egui_ctx, window); + // Emit touch event self.egui_input.events.push(egui::Event::Touch { device_id: egui::TouchDeviceId(egui::epaint::util::hash(touch.device_id)), @@ -560,8 +555,8 @@ impl State { winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel, }, pos: egui::pos2( - touch.location.x as f32 / self.current_pixels_per_point, - touch.location.y as f32 / self.current_pixels_per_point, + touch.location.x as f32 / pixels_per_point, + touch.location.y as f32 / pixels_per_point, ), force: match touch.force { Some(winit::event::Force::Normalized(force)) => Some(force as f32), @@ -581,14 +576,14 @@ impl State { winit::event::TouchPhase::Started => { self.pointer_touch_id = Some(touch.id); // First move the pointer to the right location - self.on_cursor_moved(touch.location); + self.on_cursor_moved(window, touch.location); self.on_mouse_button_input( winit::event::ElementState::Pressed, winit::event::MouseButton::Left, ); } winit::event::TouchPhase::Moved => { - self.on_cursor_moved(touch.location); + self.on_cursor_moved(window, touch.location); } winit::event::TouchPhase::Ended => { self.pointer_touch_id = None; @@ -610,7 +605,9 @@ impl State { } } - fn on_mouse_wheel(&mut self, delta: winit::event::MouseScrollDelta) { + fn on_mouse_wheel(&mut self, window: &Window, delta: winit::event::MouseScrollDelta) { + let pixels_per_point = pixels_per_point(&self.egui_ctx, window); + { let (unit, delta) = match delta { winit::event::MouseScrollDelta::LineDelta(x, y) => { @@ -621,7 +618,7 @@ impl State { y, }) => ( egui::MouseWheelUnit::Point, - egui::vec2(x as f32, y as f32) / self.current_pixels_per_point, + egui::vec2(x as f32, y as f32) / pixels_per_point, ), }; let modifiers = self.egui_input.modifiers; @@ -637,7 +634,7 @@ impl State { egui::vec2(x, y) * points_per_scroll_line } winit::event::MouseScrollDelta::PixelDelta(delta) => { - egui::vec2(delta.x as f32, delta.y as f32) / self.current_pixels_per_point + egui::vec2(delta.x as f32, delta.y as f32) / pixels_per_point } }; @@ -656,36 +653,99 @@ impl State { } } - fn on_keyboard_input(&mut self, input: &winit::event::KeyboardInput) { - if let Some(keycode) = input.virtual_keycode { - let pressed = input.state == winit::event::ElementState::Pressed; + fn on_keyboard_input(&mut self, event: &winit::event::KeyEvent) -> bool { + let winit::event::KeyEvent { + // Represents the position of a key independent of the currently active layout. + // + // It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode). + // The most prevalent use case for this is games. For example the default keys for the player + // to move around might be the W, A, S, and D keys on a US layout. The position of these keys + // is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY" + // layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.) + physical_key, + + // Represents the results of a keymap, i.e. what character a certain key press represents. + // When telling users "Press Ctrl-F to find", this is where we should + // look for the "F" key, because they may have a dvorak layout on + // a qwerty keyboard, and so the logical "F" character may not be located on the physical `KeyCode::KeyF` position. + logical_key, + + text, + + state, + + location: _, // e.g. is it on the numpad? + repeat: _, // egui will figure this out for us + .. + } = event; + let pressed = *state == winit::event::ElementState::Pressed; + + let physical_key = if let winit::keyboard::PhysicalKey::Code(keycode) = *physical_key { + key_from_key_code(keycode) + } else { + None + }; + + let logical_key = key_from_winit_key(logical_key); + + // Helpful logging to enable when adding new key support + log::trace!( + "logical {:?} -> {:?}, physical {:?} -> {:?}", + event.logical_key, + logical_key, + event.physical_key, + physical_key + ); + + if let Some(logical_key) = logical_key { if pressed { - // VirtualKeyCode::Paste etc in winit are broken/untrustworthy, - // so we detect these things manually: - if is_cut_command(self.egui_input.modifiers, keycode) { + if is_cut_command(self.egui_input.modifiers, logical_key) { self.egui_input.events.push(egui::Event::Cut); - } else if is_copy_command(self.egui_input.modifiers, keycode) { + return true; + } else if is_copy_command(self.egui_input.modifiers, logical_key) { self.egui_input.events.push(egui::Event::Copy); - } else if is_paste_command(self.egui_input.modifiers, keycode) { + return true; + } else if is_paste_command(self.egui_input.modifiers, logical_key) { if let Some(contents) = self.clipboard.get() { let contents = contents.replace("\r\n", "\n"); if !contents.is_empty() { self.egui_input.events.push(egui::Event::Paste(contents)); } } + return true; } } - if let Some(key) = translate_virtual_key_code(keycode) { - self.egui_input.events.push(egui::Event::Key { - key, - pressed, - repeat: false, // egui will fill this in for us! - modifiers: self.egui_input.modifiers, - }); + self.egui_input.events.push(egui::Event::Key { + key: logical_key, + physical_key, + pressed, + repeat: false, // egui will fill this in for us! + modifiers: self.egui_input.modifiers, + }); + } + + if let Some(text) = &text { + // Make sure there is text, and that it is not control characters + // (e.g. delete is sent as "\u{f728}" on macOS). + if !text.is_empty() && text.chars().all(is_printable_char) { + // On some platforms we get here when the user presses Cmd-C (copy), ctrl-W, etc. + // We need to ignore these characters that are side-effects of commands. + // Also make sure the key is pressed (not released). On Linux, text might + // contain some data even when the key is released. + let is_cmd = self.egui_input.modifiers.ctrl + || self.egui_input.modifiers.command + || self.egui_input.modifiers.mac_cmd; + if pressed && !is_cmd { + self.egui_input + .events + .push(egui::Event::Text(text.to_string())); + } } } + + false } /// Call with the output given by `egui`. @@ -699,7 +759,6 @@ impl State { pub fn handle_platform_output( &mut self, window: &Window, - egui_ctx: &egui::Context, platform_output: egui::PlatformOutput, ) { crate::profile_function!(); @@ -710,13 +769,11 @@ impl State { copied_text, events: _, // handled elsewhere mutable_text_under_cursor: _, // only used in eframe web - text_cursor_pos, + ime, #[cfg(feature = "accesskit")] accesskit_update, } = platform_output; - self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI - self.set_cursor_icon(window, cursor_icon); if let Some(open_url) = open_url { @@ -727,14 +784,25 @@ impl State { self.clipboard.set(copied_text); } - let allow_ime = text_cursor_pos.is_some(); + let allow_ime = ime.is_some(); if self.allow_ime != allow_ime { self.allow_ime = allow_ime; window.set_ime_allowed(allow_ime); } - if let Some(egui::Pos2 { x, y }) = text_cursor_pos { - window.set_ime_position(winit::dpi::LogicalPosition { x, y }); + if let Some(ime) = ime { + let rect = ime.rect; + let pixels_per_point = pixels_per_point(&self.egui_ctx, window); + window.set_ime_cursor_area( + winit::dpi::PhysicalPosition { + x: pixels_per_point * rect.min.x, + y: pixels_per_point * rect.min.y, + }, + winit::dpi::PhysicalSize { + width: pixels_per_point * rect.width(), + height: pixels_per_point * rect.height(), + }, + ); } #[cfg(feature = "accesskit")] @@ -772,17 +840,15 @@ impl State { /// Update the given viewport info with the current state of the window. /// /// Call before [`State::take_egui_input`]. -pub fn update_viewport_info(info: &mut ViewportInfo, egui_ctx: &egui::Context, window: &Window) { - update_viewport_info_impl(info, window, pixels_per_point(egui_ctx, window)); -} - -fn update_viewport_info_impl( +pub fn update_viewport_info( viewport_info: &mut ViewportInfo, + egui_ctx: &egui::Context, window: &Window, - pixels_per_point: f32, ) { crate::profile_function!(); + let pixels_per_point = pixels_per_point(egui_ctx, window); + let has_a_position = match window.is_minimized() { None | Some(true) => false, Some(false) => true, @@ -886,25 +952,22 @@ fn is_printable_char(chr: char) -> bool { !is_in_private_use_area && !chr.is_ascii_control() } -fn is_cut_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { - (modifiers.command && keycode == winit::event::VirtualKeyCode::X) - || (cfg!(target_os = "windows") - && modifiers.shift - && keycode == winit::event::VirtualKeyCode::Delete) +fn is_cut_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool { + keycode == egui::Key::Cut + || (modifiers.command && keycode == egui::Key::X) + || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Delete) } -fn is_copy_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { - (modifiers.command && keycode == winit::event::VirtualKeyCode::C) - || (cfg!(target_os = "windows") - && modifiers.ctrl - && keycode == winit::event::VirtualKeyCode::Insert) +fn is_copy_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool { + keycode == egui::Key::Copy + || (modifiers.command && keycode == egui::Key::C) + || (cfg!(target_os = "windows") && modifiers.ctrl && keycode == egui::Key::Insert) } -fn is_paste_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool { - (modifiers.command && keycode == winit::event::VirtualKeyCode::V) - || (cfg!(target_os = "windows") - && modifiers.shift - && keycode == winit::event::VirtualKeyCode::Insert) +fn is_paste_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool { + keycode == egui::Key::Paste + || (modifiers.command && keycode == egui::Key::V) + || (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Insert) } fn translate_mouse_button(button: winit::event::MouseButton) -> Option { @@ -912,100 +975,165 @@ fn translate_mouse_button(button: winit::event::MouseButton) -> Option Some(egui::PointerButton::Primary), winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary), winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle), - winit::event::MouseButton::Other(1) => Some(egui::PointerButton::Extra1), - winit::event::MouseButton::Other(2) => Some(egui::PointerButton::Extra2), + winit::event::MouseButton::Back => Some(egui::PointerButton::Extra1), + winit::event::MouseButton::Forward => Some(egui::PointerButton::Extra2), winit::event::MouseButton::Other(_) => None, } } -fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option { - use egui::Key; - use winit::event::VirtualKeyCode; +fn key_from_winit_key(key: &winit::keyboard::Key) -> Option { + match key { + winit::keyboard::Key::Named(named_key) => key_from_named_key(*named_key), + winit::keyboard::Key::Character(str) => egui::Key::from_name(str.as_str()), + winit::keyboard::Key::Unidentified(_) | winit::keyboard::Key::Dead(_) => None, + } +} - Some(match key { - VirtualKeyCode::Down => Key::ArrowDown, - VirtualKeyCode::Left => Key::ArrowLeft, - VirtualKeyCode::Right => Key::ArrowRight, - VirtualKeyCode::Up => Key::ArrowUp, - - VirtualKeyCode::Escape => Key::Escape, - VirtualKeyCode::Tab => Key::Tab, - VirtualKeyCode::Back => Key::Backspace, - VirtualKeyCode::Return | VirtualKeyCode::NumpadEnter => Key::Enter, - VirtualKeyCode::Space => Key::Space, - - VirtualKeyCode::Insert => Key::Insert, - VirtualKeyCode::Delete => Key::Delete, - VirtualKeyCode::Home => Key::Home, - VirtualKeyCode::End => Key::End, - VirtualKeyCode::PageUp => Key::PageUp, - VirtualKeyCode::PageDown => Key::PageDown, - - VirtualKeyCode::Minus | VirtualKeyCode::NumpadSubtract => Key::Minus, - // Using Mac the key with the Plus sign on it is reported as the Equals key - // (with both English and Swedish keyboard). - VirtualKeyCode::Equals | VirtualKeyCode::Plus | VirtualKeyCode::NumpadAdd => { - Key::PlusEquals +fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option { + use egui::Key; + use winit::keyboard::NamedKey; + + Some(match named_key { + NamedKey::Enter => Key::Enter, + NamedKey::Tab => Key::Tab, + NamedKey::Space => Key::Space, + NamedKey::ArrowDown => Key::ArrowDown, + NamedKey::ArrowLeft => Key::ArrowLeft, + NamedKey::ArrowRight => Key::ArrowRight, + NamedKey::ArrowUp => Key::ArrowUp, + NamedKey::End => Key::End, + NamedKey::Home => Key::Home, + NamedKey::PageDown => Key::PageDown, + NamedKey::PageUp => Key::PageUp, + NamedKey::Backspace => Key::Backspace, + NamedKey::Delete => Key::Delete, + NamedKey::Insert => Key::Insert, + NamedKey::Escape => Key::Escape, + NamedKey::Cut => Key::Cut, + NamedKey::Copy => Key::Copy, + NamedKey::Paste => Key::Paste, + NamedKey::F1 => Key::F1, + NamedKey::F2 => Key::F2, + NamedKey::F3 => Key::F3, + NamedKey::F4 => Key::F4, + NamedKey::F5 => Key::F5, + NamedKey::F6 => Key::F6, + NamedKey::F7 => Key::F7, + NamedKey::F8 => Key::F8, + NamedKey::F9 => Key::F9, + NamedKey::F10 => Key::F10, + NamedKey::F11 => Key::F11, + NamedKey::F12 => Key::F12, + NamedKey::F13 => Key::F13, + NamedKey::F14 => Key::F14, + NamedKey::F15 => Key::F15, + NamedKey::F16 => Key::F16, + NamedKey::F17 => Key::F17, + NamedKey::F18 => Key::F18, + NamedKey::F19 => Key::F19, + NamedKey::F20 => Key::F20, + _ => { + log::trace!("Unknown key: {named_key:?}"); + return None; } + }) +} + +fn key_from_key_code(key: winit::keyboard::KeyCode) -> Option { + use egui::Key; + use winit::keyboard::KeyCode; - VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0, - VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1, - VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2, - VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3, - VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4, - VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5, - VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6, - VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7, - VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8, - VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9, - - VirtualKeyCode::A => Key::A, - VirtualKeyCode::B => Key::B, - VirtualKeyCode::C => Key::C, - VirtualKeyCode::D => Key::D, - VirtualKeyCode::E => Key::E, - VirtualKeyCode::F => Key::F, - VirtualKeyCode::G => Key::G, - VirtualKeyCode::H => Key::H, - VirtualKeyCode::I => Key::I, - VirtualKeyCode::J => Key::J, - VirtualKeyCode::K => Key::K, - VirtualKeyCode::L => Key::L, - VirtualKeyCode::M => Key::M, - VirtualKeyCode::N => Key::N, - VirtualKeyCode::O => Key::O, - VirtualKeyCode::P => Key::P, - VirtualKeyCode::Q => Key::Q, - VirtualKeyCode::R => Key::R, - VirtualKeyCode::S => Key::S, - VirtualKeyCode::T => Key::T, - VirtualKeyCode::U => Key::U, - VirtualKeyCode::V => Key::V, - VirtualKeyCode::W => Key::W, - VirtualKeyCode::X => Key::X, - VirtualKeyCode::Y => Key::Y, - VirtualKeyCode::Z => Key::Z, - - VirtualKeyCode::F1 => Key::F1, - VirtualKeyCode::F2 => Key::F2, - VirtualKeyCode::F3 => Key::F3, - VirtualKeyCode::F4 => Key::F4, - VirtualKeyCode::F5 => Key::F5, - VirtualKeyCode::F6 => Key::F6, - VirtualKeyCode::F7 => Key::F7, - VirtualKeyCode::F8 => Key::F8, - VirtualKeyCode::F9 => Key::F9, - VirtualKeyCode::F10 => Key::F10, - VirtualKeyCode::F11 => Key::F11, - VirtualKeyCode::F12 => Key::F12, - VirtualKeyCode::F13 => Key::F13, - VirtualKeyCode::F14 => Key::F14, - VirtualKeyCode::F15 => Key::F15, - VirtualKeyCode::F16 => Key::F16, - VirtualKeyCode::F17 => Key::F17, - VirtualKeyCode::F18 => Key::F18, - VirtualKeyCode::F19 => Key::F19, - VirtualKeyCode::F20 => Key::F20, + Some(match key { + KeyCode::ArrowDown => Key::ArrowDown, + KeyCode::ArrowLeft => Key::ArrowLeft, + KeyCode::ArrowRight => Key::ArrowRight, + KeyCode::ArrowUp => Key::ArrowUp, + + KeyCode::Escape => Key::Escape, + KeyCode::Tab => Key::Tab, + KeyCode::Backspace => Key::Backspace, + KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter, + KeyCode::Space => Key::Space, + + KeyCode::Insert => Key::Insert, + KeyCode::Delete => Key::Delete, + KeyCode::Home => Key::Home, + KeyCode::End => Key::End, + KeyCode::PageUp => Key::PageUp, + KeyCode::PageDown => Key::PageDown, + + KeyCode::Comma => Key::Comma, + KeyCode::Period => Key::Period, + // KeyCode::Colon => Key::Colon, // NOTE: there is no physical colon key on an american keyboard + KeyCode::Semicolon => Key::Semicolon, + + KeyCode::Cut => Key::Cut, + KeyCode::Copy => Key::Copy, + KeyCode::Paste => Key::Paste, + + KeyCode::Minus | KeyCode::NumpadSubtract => Key::Minus, + + KeyCode::NumpadAdd => Key::Plus, + KeyCode::Equal => Key::Equals, + + KeyCode::Digit0 | KeyCode::Numpad0 => Key::Num0, + KeyCode::Digit1 | KeyCode::Numpad1 => Key::Num1, + KeyCode::Digit2 | KeyCode::Numpad2 => Key::Num2, + KeyCode::Digit3 | KeyCode::Numpad3 => Key::Num3, + KeyCode::Digit4 | KeyCode::Numpad4 => Key::Num4, + KeyCode::Digit5 | KeyCode::Numpad5 => Key::Num5, + KeyCode::Digit6 | KeyCode::Numpad6 => Key::Num6, + KeyCode::Digit7 | KeyCode::Numpad7 => Key::Num7, + KeyCode::Digit8 | KeyCode::Numpad8 => Key::Num8, + KeyCode::Digit9 | KeyCode::Numpad9 => Key::Num9, + + KeyCode::KeyA => Key::A, + KeyCode::KeyB => Key::B, + KeyCode::KeyC => Key::C, + KeyCode::KeyD => Key::D, + KeyCode::KeyE => Key::E, + KeyCode::KeyF => Key::F, + KeyCode::KeyG => Key::G, + KeyCode::KeyH => Key::H, + KeyCode::KeyI => Key::I, + KeyCode::KeyJ => Key::J, + KeyCode::KeyK => Key::K, + KeyCode::KeyL => Key::L, + KeyCode::KeyM => Key::M, + KeyCode::KeyN => Key::N, + KeyCode::KeyO => Key::O, + KeyCode::KeyP => Key::P, + KeyCode::KeyQ => Key::Q, + KeyCode::KeyR => Key::R, + KeyCode::KeyS => Key::S, + KeyCode::KeyT => Key::T, + KeyCode::KeyU => Key::U, + KeyCode::KeyV => Key::V, + KeyCode::KeyW => Key::W, + KeyCode::KeyX => Key::X, + KeyCode::KeyY => Key::Y, + KeyCode::KeyZ => Key::Z, + + KeyCode::F1 => Key::F1, + KeyCode::F2 => Key::F2, + KeyCode::F3 => Key::F3, + KeyCode::F4 => Key::F4, + KeyCode::F5 => Key::F5, + KeyCode::F6 => Key::F6, + KeyCode::F7 => Key::F7, + KeyCode::F8 => Key::F8, + KeyCode::F9 => Key::F9, + KeyCode::F10 => Key::F10, + KeyCode::F11 => Key::F11, + KeyCode::F12 => Key::F12, + KeyCode::F13 => Key::F13, + KeyCode::F14 => Key::F14, + KeyCode::F15 => Key::F15, + KeyCode::F16 => Key::F16, + KeyCode::F17 => Key::F17, + KeyCode::F18 => Key::F18, + KeyCode::F19 => Key::F19, + KeyCode::F20 => Key::F20, _ => { return None; @@ -1030,7 +1158,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option Some(winit::window::CursorIcon::Move), egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop), egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed), - egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Hand), + egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer), egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress), egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize), @@ -1118,12 +1246,18 @@ fn process_viewport_command( ViewportCommand::InnerSize(size) => { let width_px = pixels_per_point * size.x.max(1.0); let height_px = pixels_per_point * size.y.max(1.0); - window.set_inner_size(PhysicalSize::new(width_px, height_px)); + if window + .request_inner_size(PhysicalSize::new(width_px, height_px)) + .is_some() + { + log::debug!("ViewportCommand::InnerSize ignored by winit"); + } } ViewportCommand::BeginResize(direction) => { if let Err(err) = window.drag_resize_window(match direction { egui::viewport::ResizeDirection::North => ResizeDirection::North, egui::viewport::ResizeDirection::South => ResizeDirection::South, + egui::viewport::ResizeDirection::East => ResizeDirection::East, egui::viewport::ResizeDirection::West => ResizeDirection::West, egui::viewport::ResizeDirection::NorthEast => ResizeDirection::NorthEast, egui::viewport::ResizeDirection::SouthEast => ResizeDirection::SouthEast, @@ -1202,11 +1336,14 @@ fn process_viewport_command( .expect("Invalid ICON data!") })); } - ViewportCommand::IMEPosition(pos) => { - window.set_ime_position(PhysicalPosition::new( - pixels_per_point * pos.x, - pixels_per_point * pos.y, - )); + ViewportCommand::IMERect(rect) => { + window.set_ime_cursor_area( + PhysicalPosition::new(pixels_per_point * rect.min.x, pixels_per_point * rect.min.y), + PhysicalSize::new( + pixels_per_point * rect.size().x, + pixels_per_point * rect.size().y, + ), + ); } ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v), ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(match p { @@ -1433,16 +1570,6 @@ pub fn create_winit_window_builder( window_builder } -/// Applies what `create_winit_window_builder` couldn't -#[deprecated = "Use apply_viewport_builder_to_window instead"] -pub fn apply_viewport_builder_to_new_window(window: &Window, builder: &ViewportBuilder) { - if let Some(mouse_passthrough) = builder.mouse_passthrough { - if let Err(err) = window.set_cursor_hittest(!mouse_passthrough) { - log::warn!("set_cursor_hittest failed: {err}"); - } - } -} - /// Applies what `create_winit_window_builder` couldn't pub fn apply_viewport_builder_to_window( egui_ctx: &egui::Context, @@ -1464,10 +1591,15 @@ pub fn apply_viewport_builder_to_window( let pixels_per_point = pixels_per_point(egui_ctx, window); if let Some(size) = builder.inner_size { - window.set_inner_size(PhysicalSize::new( - pixels_per_point * size.x, - pixels_per_point * size.y, - )); + if window + .request_inner_size(PhysicalSize::new( + pixels_per_point * size.x, + pixels_per_point * size.y, + )) + .is_some() + { + log::debug!("Failed to set window size"); + } } if let Some(size) = builder.min_inner_size { window.set_min_inner_size(Some(PhysicalSize::new( @@ -1492,16 +1624,15 @@ pub fn apply_viewport_builder_to_window( /// Short and fast description of an event. /// Useful for logging and profiling. -pub fn short_generic_event_description(event: &winit::event::Event<'_, T>) -> &'static str { +pub fn short_generic_event_description(event: &winit::event::Event) -> &'static str { use winit::event::{DeviceEvent, Event, StartCause}; match event { + Event::AboutToWait => "Event::AboutToWait", + Event::LoopExiting => "Event::LoopExiting", Event::Suspended => "Event::Suspended", Event::Resumed => "Event::Resumed", - Event::MainEventsCleared => "Event::MainEventsCleared", - Event::RedrawRequested(_) => "Event::RedrawRequested", - Event::RedrawEventsCleared => "Event::RedrawEventsCleared", - Event::LoopDestroyed => "Event::LoopDestroyed", + Event::MemoryWarning => "Event::MemoryWarning", Event::UserEvent(_) => "UserEvent", Event::DeviceEvent { event, .. } => match event { DeviceEvent::Added { .. } => "DeviceEvent::Added", @@ -1511,7 +1642,6 @@ pub fn short_generic_event_description(event: &winit::event::Event<'_, T>) -> DeviceEvent::Motion { .. } => "DeviceEvent::Motion", DeviceEvent::Button { .. } => "DeviceEvent::Button", DeviceEvent::Key { .. } => "DeviceEvent::Key", - DeviceEvent::Text { .. } => "DeviceEvent::Text", }, Event::NewEvents(start_cause) => match start_cause { StartCause::ResumeTimeReached { .. } => "NewEvents::ResumeTimeReached", @@ -1525,10 +1655,11 @@ pub fn short_generic_event_description(event: &winit::event::Event<'_, T>) -> /// Short and fast description of an event. /// Useful for logging and profiling. -pub fn short_window_event_description(event: &winit::event::WindowEvent<'_>) -> &'static str { +pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'static str { use winit::event::WindowEvent; match event { + WindowEvent::ActivationTokenDone { .. } => "WindowEvent::ActivationTokenDone", WindowEvent::Resized { .. } => "WindowEvent::Resized", WindowEvent::Moved { .. } => "WindowEvent::Moved", WindowEvent::CloseRequested { .. } => "WindowEvent::CloseRequested", @@ -1536,7 +1667,6 @@ pub fn short_window_event_description(event: &winit::event::WindowEvent<'_>) -> WindowEvent::DroppedFile { .. } => "WindowEvent::DroppedFile", WindowEvent::HoveredFile { .. } => "WindowEvent::HoveredFile", WindowEvent::HoveredFileCancelled { .. } => "WindowEvent::HoveredFileCancelled", - WindowEvent::ReceivedCharacter { .. } => "WindowEvent::ReceivedCharacter", WindowEvent::Focused { .. } => "WindowEvent::Focused", WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput", WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged", @@ -1547,6 +1677,7 @@ pub fn short_window_event_description(event: &winit::event::WindowEvent<'_>) -> WindowEvent::MouseWheel { .. } => "WindowEvent::MouseWheel", WindowEvent::MouseInput { .. } => "WindowEvent::MouseInput", WindowEvent::TouchpadMagnify { .. } => "WindowEvent::TouchpadMagnify", + WindowEvent::RedrawRequested { .. } => "WindowEvent::RedrawRequested", WindowEvent::SmartMagnify { .. } => "WindowEvent::SmartMagnify", WindowEvent::TouchpadRotate { .. } => "WindowEvent::TouchpadRotate", WindowEvent::TouchpadPressure { .. } => "WindowEvent::TouchpadPressure", diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index c1eab977da7..52abb7499ed 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -173,13 +173,6 @@ impl Area { self } - #[deprecated = "Use `constrain_to` instead"] - #[inline] - pub fn drag_bounds(mut self, constrain_rect: Rect) -> Self { - self.constrain_rect = Some(constrain_rect); - self - } - /// Where the "root" of the area is. /// /// For instance, if you set this to [`Align2::RIGHT_TOP`] @@ -272,7 +265,13 @@ impl Area { let layer_id = LayerId::new(order, id); - let state = ctx.memory(|mem| mem.areas().get(id).copied()); + let state = ctx + .memory(|mem| mem.areas().get(id).copied()) + .map(|mut state| { + // override the saved state with the correct value + state.pivot = pivot; + state + }); let is_new = state.is_none(); if is_new { ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 2fc28d3dd51..f4738fc0e0d 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -424,36 +424,6 @@ impl CollapsingHeader { self } - /// Can the [`CollapsingHeader`] be selected by clicking it? Default: `false`. - #[deprecated = "Use the more powerful egui::collapsing_header::CollapsingState::show_header"] // Deprecated in 2022-04-28, before egui 0.18 - #[inline] - pub fn selectable(mut self, selectable: bool) -> Self { - self.selectable = selectable; - self - } - - /// If you set this to 'true', the [`CollapsingHeader`] will be shown as selected. - /// - /// Example: - /// ``` - /// # egui::__run_test_ui(|ui| { - /// let mut selected = false; - /// let response = egui::CollapsingHeader::new("Select and open me") - /// .selectable(true) - /// .selected(selected) - /// .show(ui, |ui| ui.label("Body")); - /// if response.header_response.clicked() { - /// selected = true; - /// } - /// # }); - /// ``` - #[deprecated = "Use the more powerful egui::collapsing_header::CollapsingState::show_header"] // Deprecated in 2022-04-28, before egui 0.18 - #[inline] - pub fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - /// Should the [`CollapsingHeader`] show a background behind it? Default: `false`. /// /// To show it behind all [`CollapsingHeader`] you can just use: @@ -525,22 +495,22 @@ impl CollapsingHeader { let text_pos = available.min + vec2(ui.spacing().indent, 0.0); let wrap_width = available.right() - text_pos.x; let wrap = Some(false); - let text = text.into_galley(ui, wrap, wrap_width, TextStyle::Button); - let text_max_x = text_pos.x + text.size().x; + let galley = text.into_galley(ui, wrap, wrap_width, TextStyle::Button); + let text_max_x = text_pos.x + galley.size().x; let mut desired_width = text_max_x + button_padding.x - available.left(); if ui.visuals().collapsing_header_frame { desired_width = desired_width.max(available.width()); // fill full width } - let mut desired_size = vec2(desired_width, text.size().y + 2.0 * button_padding.y); + let mut desired_size = vec2(desired_width, galley.size().y + 2.0 * button_padding.y); desired_size = desired_size.at_least(ui.spacing().interact_size); let (_, rect) = ui.allocate_space(desired_size); let mut header_response = ui.interact(rect, id, Sense::click()); let text_pos = pos2( text_pos.x, - header_response.rect.center().y - text.size().y / 2.0, + header_response.rect.center().y - galley.size().y / 2.0, ); let mut state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open); @@ -555,7 +525,7 @@ impl CollapsingHeader { } header_response - .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, text.text())); + .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, galley.text())); let openness = state.openness(ui.ctx()); @@ -593,7 +563,7 @@ impl CollapsingHeader { } } - text.paint_with_visuals(ui.painter(), text_pos, &visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } Prepared { diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index 4bc3edc4b57..70ed046b396 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -327,7 +327,8 @@ fn combo_box_dyn<'c, R>( } let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect); - galley.paint_with_visuals(ui.painter(), text_rect.min, visuals); + ui.painter() + .galley(text_rect.min, galley, visuals.text_color()); } }); diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 49829247193..01c04be79cf 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -152,12 +152,6 @@ impl Frame { self } - #[deprecated = "Renamed inner_margin in egui 0.18"] - #[inline] - pub fn margin(self, margin: impl Into) -> Self { - self.inner_margin(margin) - } - #[inline] pub fn shadow(mut self, shadow: Shadow) -> Self { self.shadow = shadow; diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 66bb4a93081..4182af4e8c3 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -1,7 +1,9 @@ // WARNING: the code in here is horrible. It is a behemoth that needs breaking up into simpler parts. +use std::sync::Arc; + use crate::collapsing_header::CollapsingState; -use crate::{widget_text::WidgetTextGalley, *}; +use crate::*; use epaint::*; use super::*; @@ -209,15 +211,6 @@ impl<'open> Window<'open> { self } - #[deprecated = "Use `constrain_to` instead"] - #[inline] - pub fn drag_bounds(mut self, constrain_rect: Rect) -> Self { - #![allow(deprecated)] - - self.area = self.area.drag_bounds(constrain_rect); - self - } - /// Where the "root" of the window is. /// /// For instance, if you set this to [`Align2::RIGHT_TOP`] @@ -894,7 +887,7 @@ struct TitleBar { id: Id, /// Prepared text in the title - title_galley: WidgetTextGalley, + title_galley: Arc, /// Size of the title bar in a collapsed state (if window is collapsible), /// which includes all necessary space for showing the expand button, the @@ -993,11 +986,11 @@ impl TitleBar { let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range()); let text_pos = emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top(); - let text_pos = text_pos - self.title_galley.galley().rect.min.to_vec2(); + let text_pos = text_pos - self.title_galley.rect.min.to_vec2(); let text_pos = text_pos - 1.5 * Vec2::Y; // HACK: center on x-height of text (looks better) - self.title_galley.paint_with_fallback_color( - ui.painter(), + ui.painter().galley( text_pos, + self.title_galley.clone(), ui.visuals().text_color(), ); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 503177d1ff4..02d58390812 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -707,16 +707,6 @@ impl Context { }) } - /// Read-write access to [`Fonts`]. - #[inline] - #[deprecated = "This function will be removed"] - pub fn fonts_mut(&self, writer: impl FnOnce(Option<&mut Fonts>) -> R) -> R { - self.write(move |ctx| { - let pixels_per_point = ctx.pixels_per_point(); - writer(ctx.fonts.get_mut(&pixels_per_point.into())) - }) - } - /// Read-only access to [`Options`]. #[inline] pub fn options(&self, reader: impl FnOnce(&Options) -> R) -> R { @@ -1201,11 +1191,13 @@ impl Context { /// If this is called at least once in a frame, then there will be another frame right after this. /// Call as many times as you wish, only one repaint will be issued. /// + /// To request repaint with a delay, use [`Self::request_repaint_after`]. + /// /// If called from outside the UI thread, the UI thread will wake up and run, /// provided the egui integration has set that up via [`Self::set_request_repaint_callback`] /// (this will work on `eframe`). /// - /// This will repaint the current viewport + /// This will repaint the current viewport. pub fn request_repaint(&self) { self.request_repaint_of(self.viewport_id()); } @@ -1215,11 +1207,13 @@ impl Context { /// If this is called at least once in a frame, then there will be another frame right after this. /// Call as many times as you wish, only one repaint will be issued. /// + /// To request repaint with a delay, use [`Self::request_repaint_after_for`]. + /// /// If called from outside the UI thread, the UI thread will wake up and run, /// provided the egui integration has set that up via [`Self::set_request_repaint_callback`] /// (this will work on `eframe`). /// - /// This will repaint the specified viewport + /// This will repaint the specified viewport. pub fn request_repaint_of(&self, id: ViewportId) { self.write(|ctx| ctx.request_repaint(id)); } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 50073338b3c..451e7f4e6da 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -361,8 +361,20 @@ pub enum Event { /// A key was pressed or released. Key { + /// The logical key, heeding the users keymap. + /// + /// For instance, if the user is using Dvorak keyboard layout, + /// this will take that into account. key: Key, + /// The physical key, corresponding to the actual position on the keyboard. + /// + /// This ignored keymaps, so it is not recommended to use this. + /// The only thing it makes sense for is things like games, + /// where e.g. the physical location of WSAD on QWERTY should always map to movement, + /// even if the user is using Dvorak or AZERTY. + physical_key: Option, + /// Was it pressed or released? pressed: bool, @@ -572,15 +584,6 @@ impl Modifiers { command: false, }; - #[deprecated = "Use `Modifiers::ALT | Modifiers::SHIFT` instead"] - pub const ALT_SHIFT: Self = Self { - alt: true, - ctrl: false, - shift: true, - mac_cmd: false, - command: false, - }; - /// The Mac ⌘ Command key pub const MAC_CMD: Self = Self { alt: false, @@ -652,13 +655,68 @@ impl Modifiers { !self.alt && !self.shift && self.command } + /// Checks that the `ctrl/cmd` matches, and that the `shift/alt` of the argument is a subset + /// of the pressed ksey (`self`). + /// + /// This means that if the pattern has not set `shift`, then `self` can have `shift` set or not. + /// + /// The reason is that many logical keys require `shift` or `alt` on some keyboard layouts. + /// For instance, in order to press `+` on an English keyboard, you need to press `shift` and `=`, + /// but a Swedish keyboard has dedicated `+` key. + /// So if you want to make a [`KeyboardShortcut`] looking for `Cmd` + `+`, it makes sense + /// to ignore the shift key. + /// Similarly, the `Alt` key is sometimes used to type special characters. + /// + /// However, if the pattern (the argument) explicitly requires the `shift` or `alt` keys + /// to be pressed, then they must be pressed. + /// + /// # Example: + /// ``` + /// # use egui::Modifiers; + /// # let pressed_modifiers = Modifiers::default(); + /// if pressed_modifiers.matches(Modifiers::ALT | Modifiers::SHIFT) { + /// // Alt and Shift are pressed, and nothing else + /// } + /// ``` + /// + /// ## Behavior: + /// ``` + /// # use egui::Modifiers; + /// assert!(Modifiers::CTRL.matches_logically(Modifiers::CTRL)); + /// assert!(!Modifiers::CTRL.matches_logically(Modifiers::CTRL | Modifiers::SHIFT)); + /// assert!((Modifiers::CTRL | Modifiers::SHIFT).matches_logically(Modifiers::CTRL)); + /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches_logically(Modifiers::CTRL)); + /// assert!((Modifiers::CTRL | Modifiers::COMMAND).matches_logically(Modifiers::COMMAND)); + /// assert!((Modifiers::MAC_CMD | Modifiers::COMMAND).matches_logically(Modifiers::COMMAND)); + /// assert!(!Modifiers::COMMAND.matches_logically(Modifiers::MAC_CMD)); + /// ``` + pub fn matches_logically(&self, pattern: Modifiers) -> bool { + if pattern.alt && !self.alt { + return false; + } + if pattern.shift && !self.shift { + return false; + } + + self.cmd_ctrl_matches(pattern) + } + /// Check for equality but with proper handling of [`Self::command`]. /// + /// `self` here are the currently pressed modifiers, + /// and the argument the pattern we are testing for. + /// + /// Note that this will require the `shift` and `alt` keys match, even though + /// these modifiers are sometimes required to produce some logical keys. + /// For instance, to press `+` on an English keyboard, you need to press `shift` and `=`, + /// but on a Swedish keyboard you can press the dedicated `+` key. + /// Therefore, you often want to use [`Self::matches_logically`] instead. + /// /// # Example: /// ``` /// # use egui::Modifiers; - /// # let current_modifiers = Modifiers::default(); - /// if current_modifiers.matches(Modifiers::ALT | Modifiers::SHIFT) { + /// # let pressed_modifiers = Modifiers::default(); + /// if pressed_modifiers.matches(Modifiers::ALT | Modifiers::SHIFT) { /// // Alt and Shift are pressed, and nothing else /// } /// ``` @@ -674,12 +732,28 @@ impl Modifiers { /// assert!((Modifiers::MAC_CMD | Modifiers::COMMAND).matches(Modifiers::COMMAND)); /// assert!(!Modifiers::COMMAND.matches(Modifiers::MAC_CMD)); /// ``` - pub fn matches(&self, pattern: Modifiers) -> bool { + pub fn matches_exact(&self, pattern: Modifiers) -> bool { // alt and shift must always match the pattern: if pattern.alt != self.alt || pattern.shift != self.shift { return false; } + self.cmd_ctrl_matches(pattern) + } + + #[deprecated = "Renamed `matches_exact`, but maybe you want to use `matches_logically` instead"] + pub fn matches(&self, pattern: Modifiers) -> bool { + self.matches_exact(pattern) + } + + /// Checks only cmd/ctrl, not alt/shift. + /// + /// `self` here are the currently pressed modifiers, + /// and the argument the pattern we are testing for. + /// + /// This takes care to properly handle the difference between + /// [`Self::ctrl`], [`Self::command`] and [`Self::mac_cmd`]. + pub fn cmd_ctrl_matches(&self, pattern: Modifiers) -> bool { if pattern.mac_cmd { // Mac-specific match: if !self.mac_cmd { @@ -853,11 +927,8 @@ impl<'a> ModifierNames<'a> { /// Keyboard keys. /// -/// Includes all keys egui is interested in (such as `Home` and `End`) -/// plus a few that are useful for detecting keyboard shortcuts. -/// -/// Many keys are omitted because they are not always physical keys (depending on keyboard language), e.g. `;` and `§`, -/// and are therefore unsuitable as keyboard shortcuts if you want your app to be portable. +/// egui usually uses logical keys, i.e. after applying any user keymap. +// TODO(emilk): split into `LogicalKey` and `PhysicalKey` #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum Key { @@ -879,12 +950,35 @@ pub enum Key { PageUp, PageDown, - /// The virtual keycode for the Minus key. + Copy, + Cut, + Paste, + + // ---------------------------------------------- + // Punctuation: + /// `:` + Colon, + + /// `,` + Comma, + + /// `-` Minus, - /// The virtual keycode for the Plus/Equals key. - PlusEquals, + /// `.` + Period, + + /// `+` + Plus, + + /// `=` + Equals, + /// `;` + Semicolon, + + // ---------------------------------------------- + // Digits: /// Either from the main row or from the numpad. Num0, @@ -915,6 +1009,8 @@ pub enum Key { /// Either from the main row or from the numpad. Num9, + // ---------------------------------------------- + // Letters: A, // Used for cmd+A (select All) B, C, // |CMD COPY| @@ -942,7 +1038,8 @@ pub enum Key { Y, Z, // |CMD UNDO| - // The function keys: + // ---------------------------------------------- + // Function keys: F1, F2, F3, @@ -963,9 +1060,205 @@ pub enum Key { F18, F19, F20, + // When adding keys, remember to also update `crates/egui-winit/src/lib.rs` + // and [`Self::ALL`]. + // Also: don't add keys last; add them to the group they best belong to. } impl Key { + /// All egui keys + pub const ALL: &'static [Self] = &[ + Self::ArrowDown, + Self::ArrowLeft, + Self::ArrowRight, + Self::ArrowUp, + Self::Escape, + Self::Tab, + Self::Backspace, + Self::Enter, + Self::Space, + Self::Insert, + Self::Delete, + Self::Home, + Self::End, + Self::PageUp, + Self::PageDown, + Self::Copy, + Self::Cut, + Self::Paste, + // Punctuation: + Self::Colon, + Self::Comma, + Self::Minus, + Self::Period, + Self::Plus, + Self::Equals, + Self::Semicolon, + // Digits: + Self::Num0, + Self::Num1, + Self::Num2, + Self::Num3, + Self::Num4, + Self::Num5, + Self::Num6, + Self::Num7, + Self::Num8, + Self::Num9, + // Letters: + Self::A, + Self::B, + Self::C, + Self::D, + Self::E, + Self::F, + Self::G, + Self::H, + Self::I, + Self::J, + Self::K, + Self::L, + Self::M, + Self::N, + Self::O, + Self::P, + Self::Q, + Self::R, + Self::S, + Self::T, + Self::U, + Self::V, + Self::W, + Self::X, + Self::Y, + Self::Z, + // Function keys: + Self::F1, + Self::F2, + Self::F3, + Self::F4, + Self::F5, + Self::F6, + Self::F7, + Self::F8, + Self::F9, + Self::F10, + Self::F11, + Self::F12, + Self::F13, + Self::F14, + Self::F15, + Self::F16, + Self::F17, + Self::F18, + Self::F19, + Self::F20, + ]; + + /// Converts `"A"` to `Key::A`, `Space` to `Key::Space`, etc. + /// + /// Makes sense for logical keys. + /// + /// This will parse the output of both [`Self::name`] and [`Self::symbol_or_name`], + /// but will also parse single characters, so that both `"-"` and `"Minus"` will return `Key::Minus`. + /// + /// This should support both the names generated in a web browser, + /// and by winit. Please test on both with `eframe`. + pub fn from_name(key: &str) -> Option { + Some(match key { + "ArrowDown" | "Down" | "⏷" => Self::ArrowDown, + "ArrowLeft" | "Left" | "⏴" => Self::ArrowLeft, + "ArrowRight" | "Right" | "⏵" => Self::ArrowRight, + "ArrowUp" | "Up" | "⏶" => Self::ArrowUp, + + "Escape" | "Esc" => Self::Escape, + "Tab" => Self::Tab, + "Backspace" => Self::Backspace, + "Enter" | "Return" => Self::Enter, + "Space" | " " => Self::Space, + + "Help" | "Insert" => Self::Insert, + "Delete" => Self::Delete, + "Home" => Self::Home, + "End" => Self::End, + "PageUp" => Self::PageUp, + "PageDown" => Self::PageDown, + + "Copy" => Self::Copy, + "Cut" => Self::Cut, + "Paste" => Self::Paste, + + "Colon" | ":" => Self::Colon, + "Comma" | "," => Self::Comma, + "Minus" | "-" | "−" => Self::Minus, + "Period" | "." => Self::Period, + "Plus" | "+" => Self::Plus, + "Equals" | "=" => Self::Equals, + "Semicolon" | ";" => Self::Semicolon, + + "0" => Self::Num0, + "1" => Self::Num1, + "2" => Self::Num2, + "3" => Self::Num3, + "4" => Self::Num4, + "5" => Self::Num5, + "6" => Self::Num6, + "7" => Self::Num7, + "8" => Self::Num8, + "9" => Self::Num9, + + "a" | "A" => Self::A, + "b" | "B" => Self::B, + "c" | "C" => Self::C, + "d" | "D" => Self::D, + "e" | "E" => Self::E, + "f" | "F" => Self::F, + "g" | "G" => Self::G, + "h" | "H" => Self::H, + "i" | "I" => Self::I, + "j" | "J" => Self::J, + "k" | "K" => Self::K, + "l" | "L" => Self::L, + "m" | "M" => Self::M, + "n" | "N" => Self::N, + "o" | "O" => Self::O, + "p" | "P" => Self::P, + "q" | "Q" => Self::Q, + "r" | "R" => Self::R, + "s" | "S" => Self::S, + "t" | "T" => Self::T, + "u" | "U" => Self::U, + "v" | "V" => Self::V, + "w" | "W" => Self::W, + "x" | "X" => Self::X, + "y" | "Y" => Self::Y, + "z" | "Z" => Self::Z, + + "F1" => Self::F1, + "F2" => Self::F2, + "F3" => Self::F3, + "F4" => Self::F4, + "F5" => Self::F5, + "F6" => Self::F6, + "F7" => Self::F7, + "F8" => Self::F8, + "F9" => Self::F9, + "F10" => Self::F10, + "F11" => Self::F11, + "F12" => Self::F12, + "F13" => Self::F13, + "F14" => Self::F14, + "F15" => Self::F15, + "F16" => Self::F16, + "F17" => Self::F17, + "F18" => Self::F18, + "F19" => Self::F19, + "F20" => Self::F20, + + _ => return None, + }) + } + /// Emoji or name representing the key pub fn symbol_or_name(self) -> &'static str { // TODO(emilk): add support for more unicode symbols (see for instance https://wincent.com/wiki/Unicode_representations_of_modifier_keys). @@ -977,7 +1270,8 @@ impl Key { Key::ArrowRight => "⏵", Key::ArrowUp => "⏶", Key::Minus => crate::MINUS_CHAR_STR, - Key::PlusEquals => "+", + Key::Plus => "+", + Key::Equals => "=", _ => self.name(), } } @@ -989,19 +1283,32 @@ impl Key { Key::ArrowLeft => "Left", Key::ArrowRight => "Right", Key::ArrowUp => "Up", + Key::Escape => "Escape", Key::Tab => "Tab", Key::Backspace => "Backspace", Key::Enter => "Enter", Key::Space => "Space", + Key::Insert => "Insert", Key::Delete => "Delete", Key::Home => "Home", Key::End => "End", Key::PageUp => "PageUp", Key::PageDown => "PageDown", + + Key::Copy => "Copy", + Key::Cut => "Cut", + Key::Paste => "Paste", + + Key::Colon => "Colon", + Key::Comma => "Comma", Key::Minus => "Minus", - Key::PlusEquals => "Plus", + Key::Period => "Period", + Key::Plus => "Plus", + Key::Equals => "Equals", + Key::Semicolon => "Semicolon", + Key::Num0 => "0", Key::Num1 => "1", Key::Num2 => "2", @@ -1012,6 +1319,7 @@ impl Key { Key::Num7 => "7", Key::Num8 => "8", Key::Num9 => "9", + Key::A => "A", Key::B => "B", Key::C => "C", @@ -1062,6 +1370,31 @@ impl Key { } } +#[test] +fn test_key_from_name() { + assert_eq!( + Key::ALL.len(), + Key::F20 as usize + 1, + "Some keys are missing in Key::ALL" + ); + + for &key in Key::ALL { + let name = key.name(); + assert_eq!( + Key::from_name(name), + Some(key), + "Failed to roundtrip {key:?} from name {name:?}" + ); + + let symbol = key.symbol_or_name(); + assert_eq!( + Key::from_name(symbol), + Some(key), + "Failed to roundtrip {key:?} from symbol {symbol:?}" + ); + } +} + // ---------------------------------------------------------------------------- /// A keyboard shortcut, e.g. `Ctrl+Alt+W`. @@ -1069,14 +1402,19 @@ impl Key { /// Can be used with [`crate::InputState::consume_shortcut`] /// and [`crate::Context::format_shortcut`]. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct KeyboardShortcut { pub modifiers: Modifiers, - pub key: Key, + + pub logical_key: Key, } impl KeyboardShortcut { - pub const fn new(modifiers: Modifiers, key: Key) -> Self { - Self { modifiers, key } + pub const fn new(modifiers: Modifiers, logical_key: Key) -> Self { + Self { + modifiers, + logical_key, + } } pub fn format(&self, names: &ModifierNames<'_>, is_mac: bool) -> String { @@ -1085,9 +1423,9 @@ impl KeyboardShortcut { s += names.concat; } if names.is_short { - s += self.key.symbol_or_name(); + s += self.logical_key.symbol_or_name(); } else { - s += self.key.name(); + s += self.logical_key.name(); } s } diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 11275e7b800..7a239dd05c5 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -64,6 +64,21 @@ impl FullOutput { } } +/// Information about text being edited. +/// +/// Useful for IME. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct IMEOutput { + /// Where the [`crate::TextEdit`] is located on screen. + pub rect: crate::Rect, + + /// Where the cursor is. + /// + /// This is a very thin rectangle. + pub cursor_rect: crate::Rect, +} + /// The non-rendering part of what egui emits each frame. /// /// You can access (and modify) this with [`crate::Context::output`]. @@ -98,10 +113,10 @@ pub struct PlatformOutput { /// Use by `eframe` web to show/hide mobile keyboard and IME agent. pub mutable_text_under_cursor: bool, - /// Screen-space position of text edit cursor (used for IME). + /// This is et if, and only if, the user is currently editing text. /// - /// Iff `Some`, the user is editing text. - pub text_cursor_pos: Option, + /// Useful for IME. + pub ime: Option, /// The difference in the widget tree since last frame. /// @@ -111,14 +126,6 @@ pub struct PlatformOutput { } impl PlatformOutput { - /// Open the given url in a web browser. - /// - /// If egui is running in a browser, the same tab will be reused. - #[deprecated = "Use Context::open_url instead"] - pub fn open_url(&mut self, url: impl ToString) { - self.open_url = Some(OpenUrl::same_tab(url)); - } - /// This can be used by a text-to-speech system to describe the events (if any). pub fn events_description(&self) -> String { // only describe last event: @@ -145,7 +152,7 @@ impl PlatformOutput { copied_text, mut events, mutable_text_under_cursor, - text_cursor_pos, + ime, #[cfg(feature = "accesskit")] accesskit_update, } = newer; @@ -159,7 +166,7 @@ impl PlatformOutput { } self.events.append(&mut events); self.mutable_text_under_cursor = mutable_text_under_cursor; - self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos); + self.ime = ime.or(self.ime); #[cfg(feature = "accesskit")] { diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 1ecb319a9b7..374902e3a4b 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -341,14 +341,12 @@ impl Grid { /// Default is whatever is in [`crate::Visuals::striped`]. pub fn striped(self, striped: bool) -> Self { if striped { - self.with_row_color(move |row, style| { - if row % 2 == 1 { - return Some(style.visuals.faint_bg_color); - } - None - }) + self.with_row_color(striped_row_color) } else { - self + // Explicitly set the row color to nothing. + // Needed so that when the style.visuals.striped value is checked later on, + // it is clear that the user does not want stripes on this specific Grid. + self.with_row_color(|_row: usize, _style: &Style| None) } } @@ -410,11 +408,14 @@ impl Grid { max_cell_size, spacing, start_row, - color_picker, + mut color_picker, } = self; let min_col_width = min_col_width.unwrap_or_else(|| ui.spacing().interact_size.x); let min_row_height = min_row_height.unwrap_or_else(|| ui.spacing().interact_size.y); let spacing = spacing.unwrap_or_else(|| ui.spacing().item_spacing); + if color_picker.is_none() && ui.visuals().striped { + color_picker = Some(Box::new(striped_row_color)); + } let id = ui.make_persistent_id(id_source); let prev_state = State::load(ui.ctx(), id); @@ -454,3 +455,10 @@ impl Grid { }) } } + +fn striped_row_color(row: usize, style: &Style) -> Option { + if row % 2 == 1 { + return Some(style.visuals.faint_bg_color); + } + None +} diff --git a/crates/egui/src/gui_zoom.rs b/crates/egui/src/gui_zoom.rs index fb4f9bd11ed..c4b5deb5ed6 100644 --- a/crates/egui/src/gui_zoom.rs +++ b/crates/egui/src/gui_zoom.rs @@ -6,9 +6,22 @@ use crate::*; pub mod kb_shortcuts { use super::*; - pub const ZOOM_IN: KeyboardShortcut = - KeyboardShortcut::new(Modifiers::COMMAND, Key::PlusEquals); + /// Primary keyboard shortcut for zooming in (`Cmd` + `+`). + pub const ZOOM_IN: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Plus); + + /// Secondary keyboard shortcut for zooming in (`Cmd` + `=`). + /// + /// On an English keyboard `+` is `Shift` + `=`, + /// but it is annoying to have to press shift. + /// So most browsers also allow `Cmd` + `=` for zooming in. + /// We do the same. + pub const ZOOM_IN_SECONDARY: KeyboardShortcut = + KeyboardShortcut::new(Modifiers::COMMAND, Key::Equals); + + /// Keyboard shortcut for zooming in (`Cmd` + `-`). pub const ZOOM_OUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Minus); + + /// Keyboard shortcut for resetting zoom in (`Cmd` + `0`). pub const ZOOM_RESET: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Num0); } @@ -21,7 +34,9 @@ pub(crate) fn zoom_with_keyboard(ctx: &Context) { if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_RESET)) { ctx.set_zoom_factor(1.0); } else { - if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_IN)) { + if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_IN)) + || ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_IN_SECONDARY)) + { zoom_in(ctx); } if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_OUT)) { diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index e588271d9d8..098b1b10a25 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -37,21 +37,13 @@ impl Id { /// though obviously it will lead to a lot of collisions if you do use it! pub const NULL: Self = Self(0); - #[deprecated = "Use Id::NULL"] - pub fn null() -> Self { - Self(0) - } - pub(crate) const fn background() -> Self { Self(1) } /// Generate a new [`Id`] by hashing some source (e.g. a string or integer). pub fn new(source: impl std::hash::Hash) -> Id { - use std::hash::{BuildHasher, Hasher}; - let mut hasher = epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).build_hasher(); - source.hash(&mut hasher); - Id(hasher.finish()) + Id(epaint::ahash::RandomState::with_seeds(1, 2, 3, 4).hash_one(source)) } /// Generate a new [`Id`] by hashing the parent [`Id`] and the given argument. diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 693f6c56ee2..b95303ec819 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -289,7 +289,13 @@ impl InputState { /// Count presses of a key. If non-zero, the presses are consumed, so that this will only return non-zero once. /// /// Includes key-repeat events. - pub fn count_and_consume_key(&mut self, modifiers: Modifiers, key: Key) -> usize { + /// + /// This uses [`Modifiers::matches_logically`] to match modifiers. + /// This means that e.g. the shortcut `Ctrl` + `Key::Plus` will be matched + /// as long as `Ctrl` and `Plus` are pressed, ignoring if + /// `Shift` or `Alt` are also pressed (because those modifiers might + /// be required to produce the logical `Key::Plus`). + pub fn count_and_consume_key(&mut self, modifiers: Modifiers, logical_key: Key) -> usize { let mut count = 0usize; self.events.retain(|event| { @@ -300,7 +306,7 @@ impl InputState { modifiers: ev_mods, pressed: true, .. - } if *ev_key == key && ev_mods.matches(modifiers) + } if *ev_key == logical_key && ev_mods.matches_logically(modifiers) ); count += is_match as usize; @@ -314,8 +320,8 @@ impl InputState { /// Check for a key press. If found, `true` is returned and the key pressed is consumed, so that this will only return `true` once. /// /// Includes key-repeat events. - pub fn consume_key(&mut self, modifiers: Modifiers, key: Key) -> bool { - self.count_and_consume_key(modifiers, key) > 0 + pub fn consume_key(&mut self, modifiers: Modifiers, logical_key: Key) -> bool { + self.count_and_consume_key(modifiers, logical_key) > 0 } /// Check if the given shortcut has been pressed. @@ -324,8 +330,11 @@ impl InputState { /// /// Includes key-repeat events. pub fn consume_shortcut(&mut self, shortcut: &KeyboardShortcut) -> bool { - let KeyboardShortcut { modifiers, key } = *shortcut; - self.consume_key(modifiers, key) + let KeyboardShortcut { + modifiers, + logical_key, + } = *shortcut; + self.consume_key(modifiers, logical_key) } /// Was the given key pressed this frame? diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 377860057fc..b61c313c900 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -333,6 +333,10 @@ //! }); // the temporary settings are reverted here //! # }); //! ``` +//! +//! ## Installing additional fonts +//! The default egui fonts only support latin and cryllic characters, and some emojis. +//! To use egui with e.g. asian characters you need to install your own font (`.ttf` or `.otf`) using [`Context::set_fonts`]. #![allow(clippy::float_cmp)] #![allow(clippy::manual_range_contains)] diff --git a/crates/egui/src/load.rs b/crates/egui/src/load.rs index f1c61a644e1..0b31af1c9d6 100644 --- a/crates/egui/src/load.rs +++ b/crates/egui/src/load.rs @@ -127,8 +127,6 @@ pub type Result = std::result::Result; /// Used mostly for rendering SVG:s to a good size. /// /// All variants will preserve the original aspect ratio. -/// -/// Similar to `usvg::FitTo`. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SizeHint { /// Scale original size by some factor. @@ -145,12 +143,14 @@ pub enum SizeHint { } impl Default for SizeHint { + #[inline] fn default() -> Self { Self::Scale(1.0.ord()) } } impl From for SizeHint { + #[inline] fn from(value: Vec2) -> Self { Self::Size(value.x.round() as u32, value.y.round() as u32) } @@ -412,6 +412,7 @@ impl From<(TextureId, Vec2)> for SizedTexture { } impl<'a> From<&'a TextureHandle> for SizedTexture { + #[inline] fn from(handle: &'a TextureHandle) -> Self { Self::from_handle(handle) } @@ -435,12 +436,21 @@ pub enum TexturePoll { } impl TexturePoll { - pub fn size(self) -> Option { + #[inline] + pub fn size(&self) -> Option { match self { - TexturePoll::Pending { size } => size, + TexturePoll::Pending { size } => *size, TexturePoll::Ready { texture } => Some(texture.size), } } + + #[inline] + pub fn texture_id(&self) -> Option { + match self { + TexturePoll::Pending { .. } => None, + TexturePoll::Ready { texture } => Some(texture.id), + } + } } pub type TextureLoadResult = Result; diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index fb15fdd3897..a96d8d973be 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -39,7 +39,7 @@ pub struct Memory { /// /// This will be saved between different program runs if you use the `persistence` feature. /// - /// To store a state common for all your widgets (a singleton), use [`Id::null`] as the key. + /// To store a state common for all your widgets (a singleton), use [`Id::NULL`] as the key. pub data: crate::util::IdTypeMap, // ------------------------------------------ @@ -666,21 +666,6 @@ impl Memory { } } - /// Set an event filter for a widget. - /// - /// You must first give focus to the widget before calling this. - #[deprecated = "Use set_focus_lock_filter instead"] - pub fn lock_focus(&mut self, id: Id, lock_focus: bool) { - self.set_focus_lock_filter( - id, - EventFilter { - tab: lock_focus, - arrows: lock_focus, - escape: false, - }, - ); - } - /// Give keyboard focus to a specific widget. /// See also [`crate::Response::request_focus`]. #[inline(always)] diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 34b33a31fb6..03c4ebce6c2 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -367,20 +367,18 @@ impl MenuRoot { let response = response.interact(Sense::click()); response.ctx.input(|input| { let pointer = &input.pointer; - if pointer.any_pressed() { - if let Some(pos) = pointer.interact_pos() { - let mut destroy = false; - let mut in_old_menu = false; - if let Some(root) = root { - in_old_menu = root.menu_state.read().area_contains(pos); - destroy = root.id == response.id; - } - if !in_old_menu { - if response.hovered() && pointer.secondary_down() { - return MenuResponse::Create(pos, id); - } else if (response.hovered() && pointer.primary_down()) || destroy { - return MenuResponse::Close; - } + if let Some(pos) = pointer.interact_pos() { + let mut in_old_menu = false; + let mut destroy = false; + if let Some(root) = root { + in_old_menu = root.menu_state.read().area_contains(pos); + destroy = !in_old_menu && pointer.any_pressed() && root.id == response.id; + } + if !in_old_menu { + if response.hovered() && response.secondary_clicked() { + return MenuResponse::Create(pos, id); + } else if (response.hovered() && pointer.primary_down()) || destroy { + return MenuResponse::Close; } } } @@ -503,8 +501,8 @@ impl SubMenuButton { } let text_color = visuals.text_color(); - text_galley.paint_with_fallback_color(ui.painter(), text_pos, text_color); - icon_galley.paint_with_fallback_color(ui.painter(), icon_pos, text_color); + ui.painter().galley(text_pos, text_galley, text_color); + ui.painter().galley(icon_pos, icon_galley, text_color); } response } diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index a88862325a6..8d6e3e6a8d6 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -83,22 +83,12 @@ impl Painter { pub(crate) fn set_invisible(&mut self) { self.fade_to_color = Some(Color32::TRANSPARENT); } - - #[deprecated = "Use Painter::with_clip_rect"] // Deprecated in 2022-04-18, before egui 0.18 - pub fn sub_region(&self, rect: Rect) -> Self { - Self { - ctx: self.ctx.clone(), - layer_id: self.layer_id, - clip_rect: rect.intersect(self.clip_rect), - fade_to_color: self.fade_to_color, - } - } } /// ## Accessors etc impl Painter { /// Get a reference to the parent [`Context`]. - #[inline(always)] + #[inline] pub fn ctx(&self) -> &Context { &self.ctx } @@ -106,45 +96,45 @@ impl Painter { /// Read-only access to the shared [`Fonts`]. /// /// See [`Context`] documentation for how locks work. - #[inline(always)] + #[inline] pub fn fonts(&self, reader: impl FnOnce(&Fonts) -> R) -> R { self.ctx.fonts(reader) } /// Where we paint - #[inline(always)] + #[inline] pub fn layer_id(&self) -> LayerId { self.layer_id } /// Everything painted in this [`Painter`] will be clipped against this. /// This means nothing outside of this rectangle will be visible on screen. - #[inline(always)] + #[inline] pub fn clip_rect(&self) -> Rect { self.clip_rect } /// Everything painted in this [`Painter`] will be clipped against this. /// This means nothing outside of this rectangle will be visible on screen. - #[inline(always)] + #[inline] pub fn set_clip_rect(&mut self, clip_rect: Rect) { self.clip_rect = clip_rect; } /// Useful for pixel-perfect rendering. - #[inline(always)] + #[inline] pub fn round_to_pixel(&self, point: f32) -> f32 { self.ctx().round_to_pixel(point) } /// Useful for pixel-perfect rendering. - #[inline(always)] + #[inline] pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 { self.ctx().round_vec_to_pixels(vec) } /// Useful for pixel-perfect rendering. - #[inline(always)] + #[inline] pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 { self.ctx().round_pos_to_pixels(pos) } @@ -246,7 +236,7 @@ impl Painter { 0.0, Color32::from_black_alpha(150), )); - self.galley(rect.min, galley); + self.galley(rect.min, galley, color); frame_rect } } @@ -389,14 +379,15 @@ impl Painter { ) -> Rect { let galley = self.layout_no_wrap(text.to_string(), font_id, text_color); let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size())); - self.galley(rect.min, galley); + self.galley(rect.min, galley, text_color); rect } /// Will wrap text at the given width and line break at `\n`. /// /// Paint the results with [`Self::galley`]. - #[inline(always)] + #[inline] + #[must_use] pub fn layout( &self, text: String, @@ -410,7 +401,8 @@ impl Painter { /// Will line break at `\n`. /// /// Paint the results with [`Self::galley`]. - #[inline(always)] + #[inline] + #[must_use] pub fn layout_no_wrap( &self, text: String, @@ -424,11 +416,13 @@ impl Painter { /// /// You can create the [`Galley`] with [`Self::layout`]. /// - /// If you want to change the color of the text, use [`Self::galley_with_color`]. - #[inline(always)] - pub fn galley(&self, pos: Pos2, galley: Arc) { + /// Any uncolored parts of the [`Galley`] (using [`Color32::PLACEHOLDER`]) will be replaced with the given color. + /// + /// Any non-placeholder color in the galley takes precedence over this fallback color. + #[inline] + pub fn galley(&self, pos: Pos2, galley: Arc, fallback_color: Color32) { if !galley.is_empty() { - self.add(Shape::galley(pos, galley)); + self.add(Shape::galley(pos, galley, fallback_color)); } } @@ -436,17 +430,36 @@ impl Painter { /// /// You can create the [`Galley`] with [`Self::layout`]. /// - /// The text color in the [`Galley`] will be replaced with the given color. - #[inline(always)] + /// All text color in the [`Galley`] will be replaced with the given color. + #[inline] + pub fn galley_with_override_text_color( + &self, + pos: Pos2, + galley: Arc, + text_color: Color32, + ) { + if !galley.is_empty() { + self.add(Shape::galley_with_override_text_color( + pos, galley, text_color, + )); + } + } + + #[deprecated = "Use `Painter::galley` or `Painter::galley_with_override_text_color` instead"] + #[inline] pub fn galley_with_color(&self, pos: Pos2, galley: Arc, text_color: Color32) { if !galley.is_empty() { - self.add(Shape::galley_with_color(pos, galley, text_color)); + self.add(Shape::galley_with_override_text_color( + pos, galley, text_color, + )); } } } fn tint_shape_towards(shape: &mut Shape, target: Color32) { epaint::shape_transform::adjust_colors(shape, &|color| { - *color = crate::ecolor::tint_color_towards(*color, target); + if *color != Color32::PLACEHOLDER { + *color = crate::ecolor::tint_color_towards(*color, target); + } }); } diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 8474bcb0e72..4c4c0501f3a 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -2269,7 +2269,8 @@ fn register_rect(ui: &Ui, rect: Rect) { if !callstack.is_empty() { let font_id = FontId::monospace(12.0); let text = format!("{callstack}\n\n(click to copy)"); - let galley = painter.layout_no_wrap(text, font_id, Color32::WHITE); + let text_color = Color32::WHITE; + let galley = painter.layout_no_wrap(text, font_id, text_color); // Position the text either under or above: let screen_rect = ui.ctx().screen_rect(); @@ -2299,7 +2300,7 @@ fn register_rect(ui: &Ui, rect: Rect) { }; let text_rect = Rect::from_min_size(text_pos, galley.size()); painter.rect(text_rect, 0.0, text_bg_color, (1.0, text_rect_stroke_color)); - painter.galley(text_pos, galley); + painter.galley(text_pos, galley, text_color); if ui.input(|i| i.pointer.any_click()) { ui.ctx().copy_text(callstack); diff --git a/crates/egui/src/util/id_type_map.rs b/crates/egui/src/util/id_type_map.rs index 9e33849a991..d7af233ac25 100644 --- a/crates/egui/src/util/id_type_map.rs +++ b/crates/egui/src/util/id_type_map.rs @@ -318,7 +318,7 @@ use crate::Id; /// /// Values can either be "persisted" (serializable) or "temporary" (cleared when egui is shut down). /// -/// You can store state using the key [`Id::null`]. The state will then only be identified by its type. +/// You can store state using the key [`Id::NULL`]. The state will then only be identified by its type. /// /// ``` /// # use egui::{Id, util::IdTypeMap}; diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index f811f95e0cc..bba152943e0 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -439,7 +439,7 @@ impl ViewportBuilder { /// /// If this is not set, some platform-specific dimensions will be used. /// - /// Should be bigger then 0 + /// Should be bigger than 0 /// Look at winit for more details #[inline] pub fn with_inner_size(mut self, size: impl Into) -> Self { @@ -452,7 +452,7 @@ impl ViewportBuilder { /// If this is not set, the window will have no minimum dimensions (aside /// from reserved). /// - /// Should be bigger then 0 + /// Should be bigger than 0 /// Look at winit for more details #[inline] pub fn with_min_inner_size(mut self, size: impl Into) -> Self { @@ -465,7 +465,7 @@ impl ViewportBuilder { /// If this is not set, the window will have no maximum or will be set to /// the primary monitor's dimensions by the platform. /// - /// Should be bigger then 0 + /// Should be bigger than 0 /// Look at winit for more details #[inline] pub fn with_max_inner_size(mut self, size: impl Into) -> Self { @@ -808,6 +808,7 @@ pub enum CursorGrab { pub enum ResizeDirection { North, South, + East, West, NorthEast, SouthEast, @@ -854,16 +855,16 @@ pub enum ViewportCommand { /// Set the outer position of the viewport, i.e. moves the window. OuterPosition(Pos2), - /// Should be bigger then 0 + /// Should be bigger than 0 InnerSize(Vec2), - /// Should be bigger then 0 + /// Should be bigger than 0 MinInnerSize(Vec2), - /// Should be bigger then 0 + /// Should be bigger than 0 MaxInnerSize(Vec2), - /// Should be bigger then 0 + /// Should be bigger than 0 ResizeIncrements(Option), /// Begin resizing the viewport with the left mouse button until the button is released. @@ -899,7 +900,8 @@ pub enum ViewportCommand { /// The the window icon. Icon(Option>), - IMEPosition(Pos2), + /// Set the IME cursor editing area. + IMERect(crate::Rect), IMEAllowed(bool), IMEPurpose(IMEPurpose), diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index fe0de370c01..9bad481cb38 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,8 +1,8 @@ use std::{borrow::Cow, sync::Arc}; use crate::{ - style::WidgetVisuals, text::LayoutJob, Align, Color32, FontFamily, FontSelection, Galley, Pos2, - Style, TextStyle, Ui, Visuals, + text::LayoutJob, Align, Color32, FontFamily, FontSelection, Galley, Style, TextStyle, Ui, + Visuals, }; /// Text and optional style choices for it. @@ -247,6 +247,9 @@ impl RichText { } /// Override text color. + /// + /// If not set, [`Color32::PLACEHOLDER`] will be used, + /// which will be replaced with a color chosen by the widget that paints the text. #[inline] pub fn color(mut self, color: impl Into) -> Self { self.text_color = Some(color.into()); @@ -310,17 +313,14 @@ impl RichText { layout_job.append(&text, 0.0, format); } - fn into_text_job( + fn into_layout_job( self, style: &Style, fallback_font: FontSelection, default_valign: Align, - ) -> WidgetTextJob { - let job_has_color = self.get_text_color(&style.visuals).is_some(); + ) -> LayoutJob { let (text, text_format) = self.into_text_and_format(style, fallback_font, default_valign); - - let job = LayoutJob::single_section(text, text_format); - WidgetTextJob { job, job_has_color } + LayoutJob::single_section(text, text_format) } fn into_text_and_format( @@ -350,7 +350,7 @@ impl RichText { } = self; let line_color = text_color.unwrap_or_else(|| style.visuals.text_color()); - let text_color = text_color.unwrap_or(crate::Color32::TEMPORARY_COLOR); + let text_color = text_color.unwrap_or(crate::Color32::PLACEHOLDER); let font_id = { let mut font_id = text_style @@ -429,6 +429,9 @@ impl RichText { /// but it can be a [`RichText`] (text with color, style, etc), /// a [`LayoutJob`] (for when you want full control of how the text looks) /// or text that has already been laid out in a [`Galley`]. +/// +/// You can color the text however you want, or use [`Color32::PLACEHOLDER`] +/// which will be replaced with a color chosen by the widget that paints the text. #[derive(Clone)] pub enum WidgetText { RichText(RichText), @@ -442,9 +445,15 @@ pub enum WidgetText { /// of the [`Ui`] the widget is placed in. /// If you want all parts of the [`LayoutJob`] respected, then convert it to a /// [`Galley`] and use [`Self::Galley`] instead. + /// + /// You can color the text however you want, or use [`Color32::PLACEHOLDER`] + /// which will be replaced with a color chosen by the widget that paints the text. LayoutJob(LayoutJob), /// Use exactly this galley when painting the text. + /// + /// You can color the text however you want, or use [`Color32::PLACEHOLDER`] + /// which will be replaced with a color chosen by the widget that paints the text. Galley(Arc), } @@ -616,25 +625,16 @@ impl WidgetText { } } - pub fn into_text_job( + pub fn into_layout_job( self, style: &Style, fallback_font: FontSelection, default_valign: Align, - ) -> WidgetTextJob { + ) -> LayoutJob { match self { - Self::RichText(text) => text.into_text_job(style, fallback_font, default_valign), - Self::LayoutJob(job) => WidgetTextJob { - job, - job_has_color: true, - }, - Self::Galley(galley) => { - let job: LayoutJob = (*galley.job).clone(); - WidgetTextJob { - job, - job_has_color: true, - } - } + Self::RichText(text) => text.into_layout_job(style, fallback_font, default_valign), + Self::LayoutJob(job) => job, + Self::Galley(galley) => (*galley.job).clone(), } } @@ -647,31 +647,22 @@ impl WidgetText { wrap: Option, available_width: f32, fallback_font: impl Into, - ) -> WidgetTextGalley { + ) -> Arc { let wrap = wrap.unwrap_or_else(|| ui.wrap_text()); let wrap_width = if wrap { available_width } else { f32::INFINITY }; match self { Self::RichText(text) => { let valign = ui.layout().vertical_align(); - let mut text_job = text.into_text_job(ui.style(), fallback_font.into(), valign); - text_job.job.wrap.max_width = wrap_width; - WidgetTextGalley { - galley: ui.fonts(|f| f.layout_job(text_job.job)), - galley_has_color: text_job.job_has_color, - } + let mut layout_job = text.into_layout_job(ui.style(), fallback_font.into(), valign); + layout_job.wrap.max_width = wrap_width; + ui.fonts(|f| f.layout_job(layout_job)) } Self::LayoutJob(mut job) => { job.wrap.max_width = wrap_width; - WidgetTextGalley { - galley: ui.fonts(|f| f.layout_job(job)), - galley_has_color: true, - } + ui.fonts(|f| f.layout_job(job)) } - Self::Galley(galley) => WidgetTextGalley { - galley, - galley_has_color: true, - }, + Self::Galley(galley) => galley, } } } @@ -724,86 +715,3 @@ impl From> for WidgetText { Self::Galley(galley) } } - -// ---------------------------------------------------------------------------- - -#[derive(Clone, PartialEq)] -pub struct WidgetTextJob { - pub job: LayoutJob, - pub job_has_color: bool, -} - -impl WidgetTextJob { - pub fn into_galley(self, fonts: &crate::text::Fonts) -> WidgetTextGalley { - let Self { job, job_has_color } = self; - let galley = fonts.layout_job(job); - WidgetTextGalley { - galley, - galley_has_color: job_has_color, - } - } -} - -// ---------------------------------------------------------------------------- - -/// Text that has been laid out and ready to be painted. -#[derive(Clone, PartialEq)] -pub struct WidgetTextGalley { - pub galley: Arc, - pub galley_has_color: bool, -} - -impl WidgetTextGalley { - /// Size of the laid out text. - #[inline] - pub fn size(&self) -> crate::Vec2 { - self.galley.size() - } - - /// The full, non-elided text of the input job. - #[inline] - pub fn text(&self) -> &str { - self.galley.text() - } - - #[inline] - pub fn galley(&self) -> &Arc { - &self.galley - } - - /// Use the colors in the original [`WidgetText`] if any, - /// else fall back to the one specified by the [`WidgetVisuals`]. - pub fn paint_with_visuals( - self, - painter: &crate::Painter, - text_pos: Pos2, - visuals: &WidgetVisuals, - ) { - self.paint_with_fallback_color(painter, text_pos, visuals.text_color()); - } - - /// Use the colors in the original [`WidgetText`] if any, - /// else fall back to the given color. - pub fn paint_with_fallback_color( - self, - painter: &crate::Painter, - text_pos: Pos2, - text_color: Color32, - ) { - if self.galley_has_color { - painter.galley(text_pos, self.galley); - } else { - painter.galley_with_color(text_pos, self.galley, text_color); - } - } - - /// Paint with this specific color. - pub fn paint_with_color_override( - self, - painter: &crate::Painter, - text_pos: Pos2, - text_color: Color32, - ) { - painter.galley_with_color(text_pos, self.galley, text_color); - } -} diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index 236d68e05c1..6ca7cf86e40 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -210,8 +210,9 @@ impl Widget for Button<'_> { text_wrap_width -= 60.0; // Some space for the shortcut text (which we never wrap). } - let text = text.map(|text| text.into_galley(ui, wrap, text_wrap_width, TextStyle::Button)); - let shortcut_text = (!shortcut_text.is_empty()) + let galley = + text.map(|text| text.into_galley(ui, wrap, text_wrap_width, TextStyle::Button)); + let shortcut_galley = (!shortcut_text.is_empty()) .then(|| shortcut_text.into_galley(ui, Some(false), f32::INFINITY, TextStyle::Button)); let mut desired_size = Vec2::ZERO; @@ -219,14 +220,14 @@ impl Widget for Button<'_> { desired_size.x += image_size.x; desired_size.y = desired_size.y.max(image_size.y); } - if image.is_some() && text.is_some() { + if image.is_some() && galley.is_some() { desired_size.x += ui.spacing().icon_spacing; } - if let Some(text) = &text { + if let Some(text) = &galley { desired_size.x += text.size().x; desired_size.y = desired_size.y.max(text.size().y); } - if let Some(shortcut_text) = &shortcut_text { + if let Some(shortcut_text) = &shortcut_galley { desired_size.x += ui.spacing().item_spacing.x + shortcut_text.size().x; desired_size.y = desired_size.y.max(shortcut_text.size().y); } @@ -238,8 +239,8 @@ impl Widget for Button<'_> { let (rect, mut response) = ui.allocate_at_least(desired_size, sense); response.widget_info(|| { - if let Some(text) = &text { - WidgetInfo::labeled(WidgetType::Button, text.text()) + if let Some(galley) = &galley { + WidgetInfo::labeled(WidgetType::Button, galley.text()) } else { WidgetInfo::new(WidgetType::Button) } @@ -297,30 +298,30 @@ impl Widget for Button<'_> { widgets::image::texture_load_result_response(image.source(), &tlr, response); } - if image.is_some() && text.is_some() { + if image.is_some() && galley.is_some() { cursor_x += ui.spacing().icon_spacing; } - if let Some(text) = text { - let text_pos = if image.is_some() || shortcut_text.is_some() { - pos2(cursor_x, rect.center().y - 0.5 * text.size().y) + if let Some(galley) = galley { + let text_pos = if image.is_some() || shortcut_galley.is_some() { + pos2(cursor_x, rect.center().y - 0.5 * galley.size().y) } else { // Make sure button text is centered if within a centered layout ui.layout() - .align_size_within_rect(text.size(), rect.shrink2(button_padding)) + .align_size_within_rect(galley.size(), rect.shrink2(button_padding)) .min }; - text.paint_with_visuals(ui.painter(), text_pos, visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } - if let Some(shortcut_text) = shortcut_text { + if let Some(shortcut_galley) = shortcut_galley { let shortcut_text_pos = pos2( - rect.max.x - button_padding.x - shortcut_text.size().x, - rect.center().y - 0.5 * shortcut_text.size().y, + rect.max.x - button_padding.x - shortcut_galley.size().x, + rect.center().y - 0.5 * shortcut_galley.size().y, ); - shortcut_text.paint_with_fallback_color( - ui.painter(), + ui.painter().galley( shortcut_text_pos, + shortcut_galley, ui.visuals().weak_text_color(), ); } @@ -378,18 +379,18 @@ impl<'a> Widget for Checkbox<'a> { let icon_width = spacing.icon_width; let icon_spacing = spacing.icon_spacing; - let (text, mut desired_size) = if text.is_empty() { + let (galley, mut desired_size) = if text.is_empty() { (None, vec2(icon_width, 0.0)) } else { let total_extra = vec2(icon_width + icon_spacing, 0.0); let wrap_width = ui.available_width() - total_extra.x; - let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); + let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); - let mut desired_size = total_extra + text.size(); + let mut desired_size = total_extra + galley.size(); desired_size = desired_size.at_least(spacing.interact_size); - (Some(text), desired_size) + (Some(galley), desired_size) }; desired_size = desired_size.at_least(Vec2::splat(spacing.interact_size.y)); @@ -404,7 +405,7 @@ impl<'a> Widget for Checkbox<'a> { WidgetInfo::selected( WidgetType::Checkbox, *checked, - text.as_ref().map_or("", |x| x.text()), + galley.as_ref().map_or("", |x| x.text()), ) }); @@ -430,12 +431,12 @@ impl<'a> Widget for Checkbox<'a> { visuals.fg_stroke, )); } - if let Some(text) = text { + if let Some(galley) = galley { let text_pos = pos2( rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * text.size().y, + rect.center().y - 0.5 * galley.size().y, ); - text.paint_with_visuals(ui.painter(), text_pos, visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } } @@ -487,7 +488,7 @@ impl Widget for RadioButton { let icon_width = spacing.icon_width; let icon_spacing = spacing.icon_spacing; - let (text, mut desired_size) = if text.is_empty() { + let (galley, mut desired_size) = if text.is_empty() { (None, vec2(icon_width, 0.0)) } else { let total_extra = vec2(icon_width + icon_spacing, 0.0); @@ -509,7 +510,7 @@ impl Widget for RadioButton { WidgetInfo::selected( WidgetType::RadioButton, checked, - text.as_ref().map_or("", |x| x.text()), + galley.as_ref().map_or("", |x| x.text()), ) }); @@ -538,12 +539,12 @@ impl Widget for RadioButton { }); } - if let Some(text) = text { + if let Some(galley) = galley { let text_pos = pos2( rect.min.x + icon_width + icon_spacing, - rect.center().y - 0.5 * text.size().y, + rect.center().y - 0.5 * galley.size().y, ); - text.paint_with_visuals(ui.painter(), text_pos, visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } } diff --git a/crates/egui/src/widgets/hyperlink.rs b/crates/egui/src/widgets/hyperlink.rs index 5d592272641..79a833bd77e 100644 --- a/crates/egui/src/widgets/hyperlink.rs +++ b/crates/egui/src/widgets/hyperlink.rs @@ -34,8 +34,8 @@ impl Widget for Link { let Link { text } = self; let label = Label::new(text).sense(Sense::click()); - let (pos, text_galley, response) = label.layout_in_ui(ui); - response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, text_galley.text())); + let (pos, galley, response) = label.layout_in_ui(ui); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, galley.text())); if response.hovered() { ui.ctx().set_cursor_icon(CursorIcon::PointingHand); @@ -51,13 +51,8 @@ impl Widget for Link { Stroke::NONE }; - ui.painter().add(epaint::TextShape { - pos, - galley: text_galley.galley, - override_text_color: Some(color), - underline, - angle: 0.0, - }); + ui.painter() + .add(epaint::TextShape::new(pos, galley, color).with_underline(underline)); } response diff --git a/crates/egui/src/widgets/label.rs b/crates/egui/src/widgets/label.rs index eb786e75013..dee9b1c303f 100644 --- a/crates/egui/src/widgets/label.rs +++ b/crates/egui/src/widgets/label.rs @@ -1,4 +1,6 @@ -use crate::{widget_text::WidgetTextGalley, *}; +use std::sync::Arc; + +use crate::*; /// Static text. /// @@ -94,7 +96,7 @@ impl Label { impl Label { /// Do layout and position the galley in the ui, without painting it or adding widget info. - pub fn layout_in_ui(self, ui: &mut Ui) -> (Pos2, WidgetTextGalley, Response) { + pub fn layout_in_ui(self, ui: &mut Ui) -> (Pos2, Arc, Response) { let sense = self.sense.unwrap_or_else(|| { // We only want to focus labels if the screen reader is on. if ui.memory(|mem| mem.options.screen_reader) { @@ -111,17 +113,13 @@ impl Label { Align::Center => rect.center_top(), Align::RIGHT => rect.right_top(), }; - let text_galley = WidgetTextGalley { - galley, - galley_has_color: true, - }; - return (pos, text_galley, response); + return (pos, galley, response); } let valign = ui.layout().vertical_align(); - let mut text_job = self + let mut layout_job = self .text - .into_text_job(ui.style(), FontSelection::Default, valign); + .into_layout_job(ui.style(), FontSelection::Default, valign); let truncate = self.truncate; let wrap = !truncate && self.wrap.unwrap_or_else(|| ui.wrap_text()); @@ -139,70 +137,65 @@ impl Label { let first_row_indentation = available_width - ui.available_size_before_wrap().x; egui_assert!(first_row_indentation.is_finite()); - text_job.job.wrap.max_width = available_width; - text_job.job.first_row_min_height = cursor.height(); - text_job.job.halign = Align::Min; - text_job.job.justify = false; - if let Some(first_section) = text_job.job.sections.first_mut() { + layout_job.wrap.max_width = available_width; + layout_job.first_row_min_height = cursor.height(); + layout_job.halign = Align::Min; + layout_job.justify = false; + if let Some(first_section) = layout_job.sections.first_mut() { first_section.leading_space = first_row_indentation; } - let text_galley = ui.fonts(|f| text_job.into_galley(f)); + let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); let pos = pos2(ui.max_rect().left(), ui.cursor().top()); - assert!( - !text_galley.galley.rows.is_empty(), - "Galleys are never empty" - ); + assert!(!galley.rows.is_empty(), "Galleys are never empty"); // collect a response from many rows: - let rect = text_galley.galley.rows[0] - .rect - .translate(vec2(pos.x, pos.y)); + let rect = galley.rows[0].rect.translate(vec2(pos.x, pos.y)); let mut response = ui.allocate_rect(rect, sense); - for row in text_galley.galley.rows.iter().skip(1) { + for row in galley.rows.iter().skip(1) { let rect = row.rect.translate(vec2(pos.x, pos.y)); response |= ui.allocate_rect(rect, sense); } - (pos, text_galley, response) + (pos, galley, response) } else { if truncate { - text_job.job.wrap.max_width = available_width; - text_job.job.wrap.max_rows = 1; - text_job.job.wrap.break_anywhere = true; + layout_job.wrap.max_width = available_width; + layout_job.wrap.max_rows = 1; + layout_job.wrap.break_anywhere = true; } else if wrap { - text_job.job.wrap.max_width = available_width; + layout_job.wrap.max_width = available_width; } else { - text_job.job.wrap.max_width = f32::INFINITY; + layout_job.wrap.max_width = f32::INFINITY; }; if ui.is_grid() { // TODO(emilk): remove special Grid hacks like these - text_job.job.halign = Align::LEFT; - text_job.job.justify = false; + layout_job.halign = Align::LEFT; + layout_job.justify = false; } else { - text_job.job.halign = ui.layout().horizontal_placement(); - text_job.job.justify = ui.layout().horizontal_justify(); + layout_job.halign = ui.layout().horizontal_placement(); + layout_job.justify = ui.layout().horizontal_justify(); }; - let text_galley = ui.fonts(|f| text_job.into_galley(f)); - let (rect, response) = ui.allocate_exact_size(text_galley.size(), sense); - let pos = match text_galley.galley.job.halign { + let galley = ui.fonts(|fonts| fonts.layout_job(layout_job)); + let (rect, response) = ui.allocate_exact_size(galley.size(), sense); + let pos = match galley.job.halign { Align::LEFT => rect.left_top(), Align::Center => rect.center_top(), Align::RIGHT => rect.right_top(), }; - (pos, text_galley, response) + (pos, galley, response) } } } impl Widget for Label { fn ui(self, ui: &mut Ui) -> Response { - let (pos, text_galley, mut response) = self.layout_in_ui(ui); - response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, text_galley.text())); + let (pos, galley, mut response) = self.layout_in_ui(ui); + response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, galley.text())); - if text_galley.galley.elided { + if galley.elided { // Show the full (non-elided) text on hover: - response = response.on_hover_text(text_galley.text()); + response = response.on_hover_text(galley.text()); } if ui.is_rect_visible(response.rect) { @@ -214,19 +207,8 @@ impl Widget for Label { Stroke::NONE }; - let override_text_color = if text_galley.galley_has_color { - None - } else { - Some(response_color) - }; - - ui.painter().add(epaint::TextShape { - pos, - galley: text_galley.galley, - override_text_color, - underline, - angle: 0.0, - }); + ui.painter() + .add(epaint::TextShape::new(pos, galley, response_color).with_underline(underline)); } response diff --git a/crates/egui/src/widgets/progress_bar.rs b/crates/egui/src/widgets/progress_bar.rs index ad3c298399a..c8439802544 100644 --- a/crates/egui/src/widgets/progress_bar.rs +++ b/crates/egui/src/widgets/progress_bar.rs @@ -161,11 +161,9 @@ impl Widget for ProgressBar { let text_color = visuals .override_text_color .unwrap_or(visuals.selection.stroke.color); - galley.paint_with_fallback_color( - &ui.painter().with_clip_rect(outer_rect), - text_pos, - text_color, - ); + ui.painter() + .with_clip_rect(outer_rect) + .galley(text_pos, galley, text_color); } } diff --git a/crates/egui/src/widgets/selected_label.rs b/crates/egui/src/widgets/selected_label.rs index 364ba0391e6..105232b40a5 100644 --- a/crates/egui/src/widgets/selected_label.rs +++ b/crates/egui/src/widgets/selected_label.rs @@ -44,19 +44,19 @@ impl Widget for SelectableLabel { let total_extra = button_padding + button_padding; let wrap_width = ui.available_width() - total_extra.x; - let text = text.into_galley(ui, None, wrap_width, TextStyle::Button); + let galley = text.into_galley(ui, None, wrap_width, TextStyle::Button); - let mut desired_size = total_extra + text.size(); + let mut desired_size = total_extra + galley.size(); desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y); let (rect, response) = ui.allocate_at_least(desired_size, Sense::click()); response.widget_info(|| { - WidgetInfo::selected(WidgetType::SelectableLabel, selected, text.text()) + WidgetInfo::selected(WidgetType::SelectableLabel, selected, galley.text()) }); if ui.is_rect_visible(response.rect) { let text_pos = ui .layout() - .align_size_within_rect(text.size(), rect.shrink2(button_padding)) + .align_size_within_rect(galley.size(), rect.shrink2(button_padding)) .min; let visuals = ui.style().interact_selectable(&response, selected); @@ -72,7 +72,7 @@ impl Widget for SelectableLabel { ); } - text.paint_with_visuals(ui.painter(), text_pos, &visuals); + ui.painter().galley(text_pos, galley, visuals.text_color()); } response diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index d96a8a216f7..ded1f335d57 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -164,13 +164,14 @@ impl<'t> TextEdit<'t> { /// .desired_width(f32::INFINITY); /// let output = text_edit.show(ui); /// let painter = ui.painter_at(output.response.rect); + /// let text_color = Color32::from_rgba_premultiplied(100, 100, 100, 100); /// let galley = painter.layout( /// String::from("Enter text"), /// FontId::default(), - /// Color32::from_rgba_premultiplied(100, 100, 100, 100), + /// text_color, /// f32::INFINITY /// ); - /// painter.galley(output.text_draw_pos, galley); + /// painter.galley(output.text_draw_pos, galley, text_color); /// # }); /// ``` #[inline] @@ -193,11 +194,6 @@ impl<'t> TextEdit<'t> { self } - #[deprecated = "Use .font(…) instead"] - pub fn text_style(self, text_style: TextStyle) -> Self { - self.font(text_style) - } - #[inline] pub fn text_color(mut self, text_color: Color32) -> Self { self.text_color = Some(text_color); @@ -669,7 +665,7 @@ impl<'t> TextEdit<'t> { }; if ui.is_rect_visible(rect) { - painter.galley(text_draw_pos, galley.clone()); + painter.galley(text_draw_pos, galley.clone(), text_color); if text.as_str().is_empty() && !hint_text.is_empty() { let hint_text_color = ui.visuals().weak_text_color(); @@ -678,7 +674,7 @@ impl<'t> TextEdit<'t> { } else { hint_text.into_galley(ui, Some(false), f32::INFINITY, font_id) }; - galley.paint_with_fallback_color(&painter, response.rect.min, hint_text_color); + painter.galley(response.rect.min, galley, hint_text_color); } if ui.memory(|mem| mem.has_focus(id)) { @@ -688,7 +684,7 @@ impl<'t> TextEdit<'t> { paint_cursor_selection(ui, &painter, text_draw_pos, &galley, &cursor_range); if text.is_mutable() { - let cursor_pos = paint_cursor_end( + let cursor_rect = paint_cursor_end( ui, row_height, &painter, @@ -699,23 +695,14 @@ impl<'t> TextEdit<'t> { let is_fully_visible = ui.clip_rect().contains_rect(rect); // TODO: remove this HACK workaround for https://github.com/emilk/egui/issues/1531 if (response.changed || selection_changed) && !is_fully_visible { - ui.scroll_to_rect(cursor_pos, None); // keep cursor in view + ui.scroll_to_rect(cursor_rect, None); // keep cursor in view } if interactive { - // eframe web uses `text_cursor_pos` when showing IME, - // so only set it when text is editable and visible! - // But `winit` and `egui_web` differs in how to set the - // position of IME. - if cfg!(target_arch = "wasm32") { - ui.ctx().output_mut(|o| { - o.text_cursor_pos = Some(cursor_pos.left_top()); - }); - } else { - ui.ctx().output_mut(|o| { - o.text_cursor_pos = Some(cursor_pos.left_bottom()); - }); - } + // For IME, so only set it when text is editable and visible! + ui.ctx().output_mut(|o| { + o.ime = Some(crate::output::IMEOutput { rect, cursor_rect }); + }); } } } @@ -1003,13 +990,13 @@ fn events( pressed: true, modifiers, .. - } if modifiers.matches(Modifiers::COMMAND) => { + } if modifiers.matches_logically(Modifiers::COMMAND) => { if let Some((undo_ccursor_range, undo_txt)) = state .undoer .lock() .undo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned())) { - text.replace(undo_txt); + text.replace_with(undo_txt); Some(*undo_ccursor_range) } else { None @@ -1020,15 +1007,16 @@ fn events( pressed: true, modifiers, .. - } if (modifiers.matches(Modifiers::COMMAND) && *key == Key::Y) - || (modifiers.matches(Modifiers::SHIFT | Modifiers::COMMAND) && *key == Key::Z) => + } if (modifiers.matches_logically(Modifiers::COMMAND) && *key == Key::Y) + || (modifiers.matches_logically(Modifiers::SHIFT | Modifiers::COMMAND) + && *key == Key::Z) => { if let Some((redo_ccursor_range, redo_txt)) = state .undoer .lock() .redo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned())) { - text.replace(redo_txt); + text.replace_with(redo_txt); Some(*redo_ccursor_range) } else { None @@ -1063,7 +1051,8 @@ fn events( } Event::CompositionEnd(prediction) => { - if prediction != "\n" && prediction != "\r" && state.has_ime { + // CompositionEnd only characters may be typed into TextEdit without trigger CompositionStart first, so do not check `state.has_ime = true` in the following statement. + if prediction != "\n" && prediction != "\r" { state.has_ime = false; let mut ccursor = delete_selected(text, &cursor_range); if !prediction.is_empty() { diff --git a/crates/egui/src/widgets/text_edit/text_buffer.rs b/crates/egui/src/widgets/text_edit/text_buffer.rs index a97d264a15c..58f5afd30fa 100644 --- a/crates/egui/src/widgets/text_edit/text_buffer.rs +++ b/crates/egui/src/widgets/text_edit/text_buffer.rs @@ -44,7 +44,7 @@ pub trait TextBuffer { } /// Replaces all contents of this string with `text` - fn replace(&mut self, text: &str) { + fn replace_with(&mut self, text: &str) { self.clear(); self.insert_text(text, 0); } @@ -91,7 +91,7 @@ impl TextBuffer for String { self.clear(); } - fn replace(&mut self, text: &str) { + fn replace_with(&mut self, text: &str) { *self = text.to_owned(); } @@ -121,7 +121,7 @@ impl<'a> TextBuffer for Cow<'a, str> { ::clear(self.to_mut()); } - fn replace(&mut self, text: &str) { + fn replace_with(&mut self, text: &str) { *self = Cow::Owned(text.to_owned()); } diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index b7c8e24d244..2d8d449c988 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -73,7 +73,10 @@ serde = { version = "1", optional = true, features = ["derive"] } # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } rfd = { version = "0.11", optional = true } # web: diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index a118a90fcb6..7e8d7e54c48 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -260,7 +260,11 @@ impl ColoredText { job.wrap.max_width = ui.available_width(); let galley = ui.fonts(|f| f.layout_job(job)); let (response, painter) = ui.allocate_painter(galley.size(), egui::Sense::hover()); - painter.add(egui::Shape::galley(response.rect.min, galley)); + painter.add(egui::Shape::galley( + response.rect.min, + galley, + ui.visuals().text_color(), + )); } } } diff --git a/crates/egui_demo_app/src/main.rs b/crates/egui_demo_app/src/main.rs index d81cedddb3b..45b0ee067a4 100644 --- a/crates/egui_demo_app/src/main.rs +++ b/crates/egui_demo_app/src/main.rs @@ -1,6 +1,7 @@ //! Demo app for egui #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +#![allow(clippy::never_loop)] // False positive // When compiling natively: fn main() -> Result<(), eframe::Error> { diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index cfe9fd4c724..14189bc6787 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -52,7 +52,7 @@ serde = { version = "1", optional = true, features = ["derive"] } [dev-dependencies] -criterion = { version = "0.4", default-features = false } +criterion.workspace = true [[bench]] diff --git a/crates/egui_demo_lib/benches/benchmark.rs b/crates/egui_demo_lib/benches/benchmark.rs index 1baaefd7e01..75c68a0de32 100644 --- a/crates/egui_demo_lib/benches/benchmark.rs +++ b/crates/egui_demo_lib/benches/benchmark.rs @@ -89,7 +89,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let max_texture_side = 8 * 1024; let wrap_width = 512.0; let font_id = egui::FontId::default(); - let color = egui::Color32::WHITE; + let text_color = egui::Color32::WHITE; let fonts = egui::epaint::text::Fonts::new( pixels_per_point, max_texture_side, @@ -104,7 +104,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { let job = LayoutJob::simple( LOREM_IPSUM_LONG.to_owned(), font_id.clone(), - color, + text_color, wrap_width, ); layout(&mut locked_fonts.fonts, job.into()) @@ -116,13 +116,13 @@ pub fn criterion_benchmark(c: &mut Criterion) { fonts.layout( LOREM_IPSUM_LONG.to_owned(), font_id.clone(), - color, + text_color, wrap_width, ) }); }); - let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width); + let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, text_color, wrap_width); let font_image_size = fonts.font_image_size(); let prepared_discs = fonts.texture_atlas().lock().prepared_discs(); let mut tessellator = egui::epaint::Tessellator::new( @@ -132,7 +132,7 @@ pub fn criterion_benchmark(c: &mut Criterion) { prepared_discs, ); let mut mesh = egui::epaint::Mesh::default(); - let text_shape = TextShape::new(egui::Pos2::ZERO, galley); + let text_shape = TextShape::new(egui::Pos2::ZERO, galley, text_color); c.bench_function("tessellate_text", |b| { b.iter(|| { tessellator.tessellate_text(&text_shape, &mut mesh); diff --git a/crates/egui_demo_lib/src/lib.rs b/crates/egui_demo_lib/src/lib.rs index f45b1f6db4b..cf31ee8d43b 100644 --- a/crates/egui_demo_lib/src/lib.rs +++ b/crates/egui_demo_lib/src/lib.rs @@ -18,7 +18,7 @@ mod demo; pub mod easy_mark; pub use color_test::ColorTest; -pub use demo::DemoWindows; +pub use demo::{DemoWindows, WidgetGallery}; /// View some Rust code with syntax highlighting and selection. pub(crate) fn rust_view_ui(ui: &mut egui::Ui, code: &str) { diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 9208b85c860..5cc3804182c 100644 --- a/crates/egui_extras/CHANGELOG.md +++ b/crates/egui_extras/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.24.2 - 2023-12-08 - `Table` scroll bug fix +* Fix `Table` scrolling bug [#3690](https://github.com/emilk/egui/pull/3690) + + ## 0.24.1 - 2023-11-30 * Add more years for datepicker [#3599](https://github.com/emilk/egui/pull/3599) (thanks [@vaqxai](https://github.com/vaqxai)!) diff --git a/crates/egui_extras/Cargo.toml b/crates/egui_extras/Cargo.toml index 92c2a44af6e..f6b8c2b1104 100644 --- a/crates/egui_extras/Cargo.toml +++ b/crates/egui_extras/Cargo.toml @@ -53,7 +53,7 @@ image = ["dep:image"] puffin = ["dep:puffin", "egui/puffin"] ## Support loading svg images. -svg = ["resvg", "tiny-skia", "usvg"] +svg = ["resvg"] ## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect). syntect = ["dep:syntect"] @@ -92,9 +92,7 @@ syntect = { version = "5", optional = true, default-features = false, features = ] } # svg feature -resvg = { version = "0.28", optional = true, default-features = false } -tiny-skia = { version = "0.8", optional = true, default-features = false } # must be updated in lock-step with resvg -usvg = { version = "0.28", optional = true, default-features = false } +resvg = { version = "0.37", optional = true, default-features = false } # http feature ehttp = { version = "0.3.1", optional = true, default-features = false } diff --git a/crates/egui_extras/src/image.rs b/crates/egui_extras/src/image.rs index 1ad3cea6c7e..31263aa9a66 100644 --- a/crates/egui_extras/src/image.rs +++ b/crates/egui_extras/src/image.rs @@ -1,9 +1,9 @@ #![allow(deprecated)] -use egui::{mutex::Mutex, TextureFilter, TextureOptions}; +use egui::{mutex::Mutex, TextureOptions}; #[cfg(feature = "svg")] -pub use usvg::FitTo; +use egui::SizeHint; /// An image to be shown in egui. /// @@ -64,7 +64,7 @@ impl RetainedImage { /// On invalid image #[cfg(feature = "svg")] pub fn from_svg_bytes(debug_name: impl Into, svg_bytes: &[u8]) -> Result { - Self::from_svg_bytes_with_size(debug_name, svg_bytes, FitTo::Original) + Self::from_svg_bytes_with_size(debug_name, svg_bytes, None) } /// Pass in the str of an SVG that you've loaded. @@ -85,11 +85,11 @@ impl RetainedImage { pub fn from_svg_bytes_with_size( debug_name: impl Into, svg_bytes: &[u8], - size: FitTo, + size_hint: Option, ) -> Result { Ok(Self::from_color_image( debug_name, - load_svg_bytes_with_size(svg_bytes, size)?, + load_svg_bytes_with_size(svg_bytes, size_hint)?, )) } @@ -123,14 +123,6 @@ impl RetainedImage { self } - #[deprecated = "Use with_options instead"] - pub fn with_texture_filter(self, filter: TextureFilter) -> Self { - self.with_options(TextureOptions { - magnification: filter, - minification: filter, - }) - } - /// The size of the image data (number of pixels wide/high). pub fn size(&self) -> [usize; 2] { self.size @@ -228,7 +220,7 @@ pub fn load_image_bytes(image_bytes: &[u8]) -> Result /// On invalid image #[cfg(feature = "svg")] pub fn load_svg_bytes(svg_bytes: &[u8]) -> Result { - load_svg_bytes_with_size(svg_bytes, FitTo::Original) + load_svg_bytes_with_size(svg_bytes, None) } /// Load an SVG and rasterize it into an egui image with a scaling parameter. @@ -240,36 +232,31 @@ pub fn load_svg_bytes(svg_bytes: &[u8]) -> Result { #[cfg(feature = "svg")] pub fn load_svg_bytes_with_size( svg_bytes: &[u8], - fit_to: FitTo, + size_hint: Option, ) -> Result { + use resvg::tiny_skia::{IntSize, Pixmap}; + use resvg::usvg::{Options, Tree, TreeParsing}; + crate::profile_function!(); - let opt = usvg::Options::default(); - - let rtree = usvg::Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?; - - let pixmap_size = rtree.size.to_screen_size(); - let [w, h] = match fit_to { - FitTo::Original => [pixmap_size.width(), pixmap_size.height()], - FitTo::Size(w, h) => [w, h], - FitTo::Height(h) => [ - (pixmap_size.width() as f32 * (h as f32 / pixmap_size.height() as f32)) as u32, - h, - ], - FitTo::Width(w) => [ - w, - (pixmap_size.height() as f32 * (w as f32 / pixmap_size.width() as f32)) as u32, - ], - FitTo::Zoom(z) => [ - (pixmap_size.width() as f32 * z) as u32, - (pixmap_size.height() as f32 * z) as u32, - ], + let opt = Options::default(); + + let mut rtree = Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?; + + let mut size = rtree.size.to_int_size(); + match size_hint { + None => (), + Some(SizeHint::Size(w, h)) => size = size.scale_to(IntSize::from_wh(w, h).unwrap()), + Some(SizeHint::Height(h)) => size = size.scale_to_height(h).unwrap(), + Some(SizeHint::Width(w)) => size = size.scale_to_width(w).unwrap(), + Some(SizeHint::Scale(z)) => size = size.scale_by(z.into_inner()).unwrap(), }; + let (w, h) = (size.width(), size.height()); - let mut pixmap = tiny_skia::Pixmap::new(w, h) - .ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?; + let mut pixmap = + Pixmap::new(w, h).ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?; - resvg::render(&rtree, fit_to, Default::default(), pixmap.as_mut()) - .ok_or_else(|| "Failed to render SVG".to_owned())?; + rtree.size = size.to_size(); + resvg::Tree::from_usvg(&rtree).render(Default::default(), &mut pixmap.as_mut()); let image = egui::ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data()); diff --git a/crates/egui_extras/src/loaders/ehttp_loader.rs b/crates/egui_extras/src/loaders/ehttp_loader.rs index ad7ce49d22d..216dba703b7 100644 --- a/crates/egui_extras/src/loaders/ehttp_loader.rs +++ b/crates/egui_extras/src/loaders/ehttp_loader.rs @@ -95,8 +95,7 @@ impl BytesLoader for EhttpLoader { } }; log::trace!("finished loading {uri:?}"); - let prev = cache.lock().insert(uri, Poll::Ready(result)); - assert!(matches!(prev, Some(Poll::Pending))); + cache.lock().insert(uri, Poll::Ready(result)); ctx.request_repaint(); } }); diff --git a/crates/egui_extras/src/loaders/svg_loader.rs b/crates/egui_extras/src/loaders/svg_loader.rs index ff2cab86844..1794a8724f4 100644 --- a/crates/egui_extras/src/loaders/svg_loader.rs +++ b/crates/egui_extras/src/loaders/svg_loader.rs @@ -1,10 +1,11 @@ +use std::{mem::size_of, path::Path, sync::Arc}; + use egui::{ ahash::HashMap, load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint}, mutex::Mutex, ColorImage, }; -use std::{mem::size_of, path::Path, sync::Arc}; type Entry = Result, String>; @@ -48,14 +49,8 @@ impl ImageLoader for SvgLoader { match ctx.try_load_bytes(&uri) { Ok(BytesPoll::Ready { bytes, .. }) => { log::trace!("started loading {uri:?}"); - let fit_to = match size_hint { - SizeHint::Scale(factor) => usvg::FitTo::Zoom(factor.into_inner()), - SizeHint::Width(w) => usvg::FitTo::Width(w), - SizeHint::Height(h) => usvg::FitTo::Height(h), - SizeHint::Size(w, h) => usvg::FitTo::Size(w, h), - }; - let result = - crate::image::load_svg_bytes_with_size(&bytes, fit_to).map(Arc::new); + let result = crate::image::load_svg_bytes_with_size(&bytes, Some(size_hint)) + .map(Arc::new); log::trace!("finished loading {uri:?}"); cache.insert((uri, size_hint), result.clone()); match result { diff --git a/crates/egui_extras/src/syntax_highlighting.rs b/crates/egui_extras/src/syntax_highlighting.rs index 8708dcf9fbf..aced1ad5d8e 100644 --- a/crates/egui_extras/src/syntax_highlighting.rs +++ b/crates/egui_extras/src/syntax_highlighting.rs @@ -3,6 +3,8 @@ //! Turn on the `syntect` feature for great syntax highlighting of any language. //! Otherwise, a very simple fallback will be used, that works okish for C, C++, Rust, and Python. +#![allow(clippy::mem_forget)] // False positive from enum_map macro + use egui::text::LayoutJob; /// View some code with syntax highlighting and selection. diff --git a/crates/egui_extras/src/table.rs b/crates/egui_extras/src/table.rs index 65fba0d84d3..8d61ad5d560 100644 --- a/crates/egui_extras/src/table.rs +++ b/crates/egui_extras/src/table.rs @@ -287,11 +287,6 @@ impl<'a> TableBuilder<'a> { self } - #[deprecated = "Renamed to vscroll"] - pub fn scroll(self, vscroll: bool) -> Self { - self.vscroll(vscroll) - } - /// Enables scrolling the table's contents using mouse drag (default: `true`). /// /// See [`ScrollArea::drag_to_scroll`] for more. @@ -613,7 +608,7 @@ impl<'a> Table<'a> { auto_shrink, } = scroll_options; - let avail_rect = ui.available_rect_before_wrap(); + let cursor_position = ui.cursor().min; let mut scroll_area = ScrollArea::new([false, vscroll]) .auto_shrink(true) @@ -634,6 +629,8 @@ impl<'a> Table<'a> { scroll_area.show(ui, move |ui| { let mut scroll_to_y_range = None; + let clip_rect = ui.clip_rect(); + // Hide first-frame-jitters when auto-sizing. ui.add_visible_ui(!first_frame_auto_size_columns, |ui| { let hovered_row_index_id = self.state_id.with("__table_hovered_row"); @@ -652,8 +649,8 @@ impl<'a> Table<'a> { max_used_widths: max_used_widths_ref, striped, row_index: 0, - start_y: avail_rect.top(), - end_y: avail_rect.bottom(), + start_y: clip_rect.top(), + end_y: clip_rect.bottom(), scroll_to_row: scroll_to_row.map(|(r, _)| r), scroll_to_y_range: &mut scroll_to_y_range, hovered_row_index, @@ -677,7 +674,7 @@ impl<'a> Table<'a> { let bottom = ui.min_rect().bottom(); let spacing_x = ui.spacing().item_spacing.x; - let mut x = avail_rect.left() - spacing_x * 0.5; + let mut x = cursor_position.x - spacing_x * 0.5; for (i, column_width) in state.column_widths.iter_mut().enumerate() { let column = &columns[i]; let column_is_resizable = column.resizable.unwrap_or(resizable); @@ -835,7 +832,8 @@ impl<'a> TableBody<'a> { /// Add a single row with the given height. /// - /// If you have many thousands of row it can be more performant to instead use [`Self::rows`] or [`Self::heterogeneous_rows`]. + /// ⚠️ It is much more performant to use [`Self::rows`] or [`Self::heterogeneous_rows`], + /// as those functions will only render the visible rows. pub fn row(&mut self, height: f32, add_row_content: impl FnOnce(TableRow<'a, '_>)) { let mut response: Option = None; let top_y = self.layout.cursor.y; diff --git a/crates/egui_glow/Cargo.toml b/crates/egui_glow/Cargo.toml index 3d169aa3b51..bd0657dd994 100644 --- a/crates/egui_glow/Cargo.toml +++ b/crates/egui_glow/Cargo.toml @@ -49,7 +49,7 @@ egui = { version = "0.24.1", path = "../egui", default-features = false, feature ] } bytemuck = "1.7" -glow = "0.12" +glow.workspace = true log = { version = "0.4", features = ["std"] } memoffset = "0.7" @@ -69,9 +69,9 @@ wasm-bindgen = "0.2" [dev-dependencies] -glutin = "0.30" # examples/pure_glow +glutin = "0.31" # examples/pure_glow raw-window-handle.workspace = true -glutin-winit = "0.3.0" +glutin-winit = "0.4.0" [[example]] diff --git a/crates/egui_glow/examples/pure_glow.rs b/crates/egui_glow/examples/pure_glow.rs index 5709a4d03e6..a802249a718 100644 --- a/crates/egui_glow/examples/pure_glow.rs +++ b/crates/egui_glow/examples/pure_glow.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![allow(unsafe_code)] +#![allow(clippy::arc_with_non_send_sync)] // glow::Context was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e use egui_winit::winit; @@ -19,7 +20,7 @@ impl GlutinWindowContext { #[allow(unsafe_code)] unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget) -> Self { use egui::NumExt; - use glutin::context::NotCurrentGlContextSurfaceAccessor; + use glutin::context::NotCurrentGlContext; use glutin::display::GetGlDisplay; use glutin::display::GlDisplay; use glutin::prelude::GlSurface; @@ -42,7 +43,7 @@ impl GlutinWindowContext { log::debug!("trying to get gl_config"); let (mut window, gl_config) = glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation - .with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150 + .with_preference(glutin_winit::ApiPreference::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150 .with_window_builder(Some(winit_window_builder.clone())) .build( event_loop, @@ -150,9 +151,11 @@ pub enum UserEvent { fn main() { let mut clear_color = [0.1, 0.1, 0.1]; - let event_loop = winit::event_loop::EventLoopBuilder::::with_user_event().build(); + let event_loop = winit::event_loop::EventLoopBuilder::::with_user_event() + .build() + .unwrap(); let (gl_window, gl) = create_display(&event_loop); - let gl = std::rc::Rc::new(gl); + let gl = std::sync::Arc::new(gl); let mut egui_glow = egui_glow::EguiGlow::new(&event_loop, gl.clone(), None, None); @@ -168,7 +171,7 @@ fn main() { let mut repaint_delay = std::time::Duration::MAX; - event_loop.run(move |event, _, control_flow| { + let _ = event_loop.run(move |event, event_loop_window_target| { let mut redraw = || { let mut quit = false; @@ -182,18 +185,20 @@ fn main() { }); }); - *control_flow = if quit { - winit::event_loop::ControlFlow::Exit - } else if repaint_delay.is_zero() { - gl_window.window().request_redraw(); - winit::event_loop::ControlFlow::Poll - } else if let Some(repaint_delay_instant) = - std::time::Instant::now().checked_add(repaint_delay) - { - winit::event_loop::ControlFlow::WaitUntil(repaint_delay_instant) + if quit { + event_loop_window_target.exit(); } else { - winit::event_loop::ControlFlow::Wait - }; + event_loop_window_target.set_control_flow(if repaint_delay.is_zero() { + gl_window.window().request_redraw(); + winit::event_loop::ControlFlow::Poll + } else if let Some(repaint_after_instant) = + std::time::Instant::now().checked_add(repaint_delay) + { + winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant) + } else { + winit::event_loop::ControlFlow::Wait + }); + } { unsafe { @@ -214,28 +219,23 @@ fn main() { }; match event { - // Platform-dependent event handlers to workaround a winit bug - // See: https://github.com/rust-windowing/winit/issues/987 - // See: https://github.com/rust-windowing/winit/issues/1619 - winit::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(), - winit::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(), - winit::event::Event::WindowEvent { event, .. } => { use winit::event::WindowEvent; if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) { - *control_flow = winit::event_loop::ControlFlow::Exit; + event_loop_window_target.exit(); + return; + } + + if matches!(event, WindowEvent::RedrawRequested) { + redraw(); + return; } if let winit::event::WindowEvent::Resized(physical_size) = &event { gl_window.resize(*physical_size); - } else if let winit::event::WindowEvent::ScaleFactorChanged { - new_inner_size, .. - } = &event - { - gl_window.resize(**new_inner_size); } - let event_response = egui_glow.on_window_event(&event); + let event_response = egui_glow.on_window_event(gl_window.window(), &event); if event_response.repaint { gl_window.window().request_redraw(); @@ -245,7 +245,7 @@ fn main() { winit::event::Event::UserEvent(UserEvent::Redraw(delay)) => { repaint_delay = delay; } - winit::event::Event::LoopDestroyed => { + winit::event::Event::LoopExiting => { egui_glow.destroy(); } winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached { diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index e37de40cbc3..4f2d437f9aa 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -1,7 +1,7 @@ #![allow(clippy::collapsible_else_if)] #![allow(unsafe_code)] -use std::{collections::HashMap, rc::Rc}; +use std::{collections::HashMap, sync::Arc}; use egui::{ emath::Rect, @@ -62,7 +62,7 @@ impl From for PainterError { /// /// NOTE: all egui viewports share the same painter. pub struct Painter { - gl: Rc, + gl: Arc, max_texture_side: usize, @@ -120,7 +120,7 @@ impl Painter { /// * failed to create postprocess on webgl with `sRGB` support /// * failed to create buffer pub fn new( - gl: Rc, + gl: Arc, shader_prefix: &str, shader_version: Option, ) -> Result { @@ -250,7 +250,7 @@ impl Painter { } /// Access the shared glow context. - pub fn gl(&self) -> &Rc { + pub fn gl(&self) -> &Arc { &self.gl } @@ -624,11 +624,6 @@ impl Painter { self.textures.get(&texture_id).copied() } - #[deprecated = "renamed 'texture'"] - pub fn get_texture(&self, texture_id: egui::TextureId) -> Option { - self.texture(texture_id) - } - #[allow(clippy::needless_pass_by_value)] // False positive pub fn register_native_texture(&mut self, native: glow::Texture) -> egui::TextureId { self.assert_not_destroyed(); diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index 653aef07a96..5d87d000416 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -24,7 +24,7 @@ impl EguiGlow { /// For automatic shader version detection set `shader_version` to `None`. pub fn new( event_loop: &winit::event_loop::EventLoopWindowTarget, - gl: std::rc::Rc, + gl: std::sync::Arc, shader_version: Option, native_pixels_per_point: Option, ) -> Self { @@ -34,7 +34,10 @@ impl EguiGlow { }) .unwrap(); + let egui_ctx = egui::Context::default(); + let egui_winit = egui_winit::State::new( + egui_ctx.clone(), ViewportId::ROOT, event_loop, native_pixels_per_point, @@ -42,7 +45,7 @@ impl EguiGlow { ); Self { - egui_ctx: Default::default(), + egui_ctx, egui_winit, painter, viewport_info: Default::default(), @@ -52,8 +55,12 @@ impl EguiGlow { } } - pub fn on_window_event(&mut self, event: &winit::event::WindowEvent<'_>) -> EventResponse { - self.egui_winit.on_window_event(&self.egui_ctx, event) + pub fn on_window_event( + &mut self, + window: &winit::window::Window, + event: &winit::event::WindowEvent, + ) -> EventResponse { + self.egui_winit.on_window_event(window, event) } /// Call [`Self::paint`] later to paint. @@ -87,7 +94,7 @@ impl EguiGlow { } self.egui_winit - .handle_platform_output(window, &self.egui_ctx, platform_output); + .handle_platform_output(window, platform_output); self.shapes = shapes; self.pixels_per_point = pixels_per_point; diff --git a/crates/egui_plot/CHANGELOG.md b/crates/egui_plot/CHANGELOG.md index c476a55ac6f..dcbf254e212 100644 --- a/crates/egui_plot/CHANGELOG.md +++ b/crates/egui_plot/CHANGELOG.md @@ -5,6 +5,10 @@ This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. +## 0.24.1 - 2024-12-03 +* Fix plot auto-bounds default [#3722](https://github.com/emilk/egui/pull/3722) (thanks [@abey79](https://github.com/abey79)!) + + ## 0.24.0 - 2023-11-23 * Add `emath::Vec2b`, replacing `egui_plot::AxisBools` [#3543](https://github.com/emilk/egui/pull/3543) * Add `auto_bounds/set_auto_bounds` to `PlotUi` [#3587](https://github.com/emilk/egui/pull/3587) [#3586](https://github.com/emilk/egui/pull/3586) (thanks [@abey79](https://github.com/abey79)!) diff --git a/crates/egui_plot/Cargo.toml b/crates/egui_plot/Cargo.toml index f364e4f3242..a4781aab94f 100644 --- a/crates/egui_plot/Cargo.toml +++ b/crates/egui_plot/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "egui_plot" version.workspace = true -authors = ["Emil Ernerfeldt "] +authors = [ + "Emil Ernerfeldt ", # https://github.com/emilk + "Jan Haller ", # https://github.com/Bromeon + "Sven Niederberger ", # https://github.com/EmbersArc +] description = "Immediate mode plotting for the egui GUI library" edition.workspace = true rust-version.workspace = true diff --git a/crates/egui_plot/src/axis.rs b/crates/egui_plot/src/axis.rs index 12dcac8828c..fae09e47ed3 100644 --- a/crates/egui_plot/src/axis.rs +++ b/crates/egui_plot/src/axis.rs @@ -1,7 +1,7 @@ use std::{fmt::Debug, ops::RangeInclusive, sync::Arc}; use egui::emath::{remap_clamp, round_to_decimals, Pos2, Rect}; -use egui::epaint::{Shape, Stroke, TextShape}; +use egui::epaint::{Shape, TextShape}; use crate::{Response, Sense, TextStyle, Ui, WidgetText}; @@ -247,14 +247,9 @@ impl AxisWidget { } }, }; - let shape = TextShape { - pos: text_pos, - galley: galley.galley, - underline: Stroke::NONE, - override_text_color: Some(text_color), - angle, - }; - ui.painter().add(shape); + + ui.painter() + .add(TextShape::new(text_pos, galley, text_color).with_angle(angle)); // --- add ticks --- let font_id = TextStyle::Body.resolve(ui.style()); @@ -311,7 +306,8 @@ impl AxisWidget { } }; - ui.painter().add(Shape::galley(text_pos, galley)); + ui.painter() + .add(Shape::galley(text_pos, galley, text_color)); } } } diff --git a/crates/egui_plot/src/items/mod.rs b/crates/egui_plot/src/items/mod.rs index b49f6773cba..c8949f58ce4 100644 --- a/crates/egui_plot/src/items/mod.rs +++ b/crates/egui_plot/src/items/mod.rs @@ -574,20 +574,6 @@ impl Polygon { self } - #[deprecated = "Use `fill_color`."] - #[allow(unused, clippy::needless_pass_by_value)] - #[inline] - pub fn color(mut self, color: impl Into) -> Self { - self - } - - #[deprecated = "Use `fill_color`."] - #[allow(unused, clippy::needless_pass_by_value)] - #[inline] - pub fn fill_alpha(mut self, _alpha: impl Into) -> Self { - self - } - /// Fill color. Defaults to the stroke color with added transparency. #[inline] pub fn fill_color(mut self, color: impl Into) -> Self { @@ -746,11 +732,7 @@ impl PlotItem for Text { .anchor .anchor_rect(Rect::from_min_size(pos, galley.size())); - let mut text_shape = epaint::TextShape::new(rect.min, galley.galley); - if !galley.galley_has_color { - text_shape.override_text_color = Some(color); - } - shapes.push(text_shape.into()); + shapes.push(epaint::TextShape::new(rect.min, galley, color).into()); if self.highlight { shapes.push(Shape::rect_stroke( diff --git a/crates/egui_plot/src/legend.rs b/crates/egui_plot/src/legend.rs index 7d2a3e00954..0a0f459f2f8 100644 --- a/crates/egui_plot/src/legend.rs +++ b/crates/egui_plot/src/legend.rs @@ -144,7 +144,7 @@ impl LegendEntry { }; let text_position = pos2(text_position_x, rect.center().y - 0.5 * galley.size().y); - painter.galley_with_color(text_position, galley, visuals.text_color()); + painter.galley(text_position, galley, visuals.text_color()); *checked ^= response.clicked_by(PointerButton::Primary); *hovered = response.hovered(); diff --git a/crates/egui_plot/src/lib.rs b/crates/egui_plot/src/lib.rs index 61b902834e4..bef977d81a0 100644 --- a/crates/egui_plot/src/lib.rs +++ b/crates/egui_plot/src/lib.rs @@ -225,7 +225,7 @@ impl Plot { allow_scroll: true, allow_double_click_reset: true, allow_boxed_zoom: true, - default_auto_bounds: false.into(), + default_auto_bounds: true.into(), min_auto_bounds: PlotBounds::NOTHING, margin_fraction: Vec2::splat(0.05), boxed_zoom_pointer_button: PointerButton::Secondary, @@ -498,7 +498,17 @@ impl Plot { self } + /// Set whether the bounds should be automatically set based on data by default. + /// + /// This is enabled by default. + #[inline] + pub fn auto_bounds(mut self, auto_bounds: Vec2b) -> Self { + self.default_auto_bounds = auto_bounds; + self + } + /// Expand bounds to fit all items across the x axis, including values given by `include_x`. + #[deprecated = "Use `auto_bounds` instead"] #[inline] pub fn auto_bounds_x(mut self) -> Self { self.default_auto_bounds.x = true; @@ -506,6 +516,7 @@ impl Plot { } /// Expand bounds to fit all items across the y axis, including values given by `include_y`. + #[deprecated = "Use `auto_bounds` instead"] #[inline] pub fn auto_bounds_y(mut self) -> Self { self.default_auto_bounds.y = true; @@ -770,7 +781,7 @@ impl Plot { max: pos + size, }; // Next we want to create this layout. - // Incides are only examples. + // Indices are only examples. // // left right // +---+---------x----------+ + @@ -1393,24 +1404,6 @@ impl PlotUi { &self.response } - /// Returns `true` if the plot area is currently hovered. - #[deprecated = "Use plot_ui.response().hovered()"] - pub fn plot_hovered(&self) -> bool { - self.response.hovered() - } - - /// Returns `true` if the plot was clicked by the primary button. - #[deprecated = "Use plot_ui.response().clicked()"] - pub fn plot_clicked(&self) -> bool { - self.response.clicked() - } - - /// Returns `true` if the plot was clicked by the secondary button. - #[deprecated = "Use plot_ui.response().secondary_clicked()"] - pub fn plot_secondary_clicked(&self) -> bool { - self.response.secondary_clicked() - } - /// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area. pub fn pointer_coordinate(&self) -> Option { // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 69ff7d30384..16f5633aadd 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -409,13 +409,7 @@ impl Rect { /// Linearly interpolate so that `[0, 0]` is [`Self::min`] and /// `[1, 1]` is [`Self::max`]. - #[deprecated = "Use `lerp_inside` instead"] - pub fn lerp(&self, t: Vec2) -> Pos2 { - self.lerp_inside(t) - } - - /// Linearly interpolate so that `[0, 0]` is [`Self::min`] and - /// `[1, 1]` is [`Self::max`]. + #[inline] pub fn lerp_inside(&self, t: Vec2) -> Pos2 { Pos2 { x: lerp(self.min.x..=self.max.x, t.x), @@ -424,6 +418,7 @@ impl Rect { } /// Linearly self towards other rect. + #[inline] pub fn lerp_towards(&self, other: &Rect, t: f32) -> Self { Self { min: self.min.lerp(other.min, t), @@ -618,6 +613,7 @@ impl std::fmt::Debug for Rect { /// from (min, max) or (left top, right bottom) impl From<[Pos2; 2]> for Rect { + #[inline] fn from([min, max]: [Pos2; 2]) -> Self { Self { min, max } } diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 15f66fe5edb..e39b8f0193c 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -98,7 +98,7 @@ backtrace = { version = "0.3", optional = true } [dev-dependencies] -criterion = { version = "0.4", default-features = false } +criterion.workspace = true [[bench]] diff --git a/crates/epaint/src/shape.rs b/crates/epaint/src/shape.rs index 07e06cb3db6..8d8a5ffba00 100644 --- a/crates/epaint/src/shape.rs +++ b/crates/epaint/src/shape.rs @@ -144,12 +144,39 @@ impl Shape { gap_length: f32, ) -> Vec { let mut shapes = Vec::new(); - dashes_from_line(path, stroke.into(), dash_length, gap_length, &mut shapes); + dashes_from_line( + path, + stroke.into(), + &[dash_length], + &[gap_length], + &mut shapes, + 0., + ); + shapes + } + + /// Turn a line into dashes with different dash/gap lengths and a start offset. + pub fn dashed_line_with_offset( + path: &[Pos2], + stroke: impl Into, + dash_lengths: &[f32], + gap_lengths: &[f32], + dash_offset: f32, + ) -> Vec { + let mut shapes = Vec::new(); + dashes_from_line( + path, + stroke.into(), + dash_lengths, + gap_lengths, + &mut shapes, + dash_offset, + ); shapes } /// Turn a line into dashes. If you need to create many dashed lines use this instead of - /// [`Self::dashed_line`] + /// [`Self::dashed_line`]. pub fn dashed_line_many( points: &[Pos2], stroke: impl Into, @@ -157,7 +184,34 @@ impl Shape { gap_length: f32, shapes: &mut Vec, ) { - dashes_from_line(points, stroke.into(), dash_length, gap_length, shapes); + dashes_from_line( + points, + stroke.into(), + &[dash_length], + &[gap_length], + shapes, + 0., + ); + } + + /// Turn a line into dashes with different dash/gap lengths and a start offset. If you need to + /// create many dashed lines use this instead of [`Self::dashed_line_with_offset`]. + pub fn dashed_line_many_with_offset( + points: &[Pos2], + stroke: impl Into, + dash_lengths: &[f32], + gap_lengths: &[f32], + dash_offset: f32, + shapes: &mut Vec, + ) { + dashes_from_line( + points, + stroke.into(), + dash_lengths, + gap_lengths, + shapes, + dash_offset, + ); } /// A convex polygon with a fill and optional stroke. @@ -211,24 +265,36 @@ impl Shape { ) -> Self { let galley = fonts.layout_no_wrap(text.to_string(), font_id, color); let rect = anchor.anchor_rect(Rect::from_min_size(pos, galley.size())); - Self::galley(rect.min, galley) + Self::galley(rect.min, galley, color) } + /// Any uncolored parts of the [`Galley`] (using [`Color32::PLACEHOLDER`]) will be replaced with the given color. + /// + /// Any non-placeholder color in the galley takes precedence over this fallback color. #[inline] - pub fn galley(pos: Pos2, galley: Arc) -> Self { - TextShape::new(pos, galley).into() + pub fn galley(pos: Pos2, galley: Arc, fallback_color: Color32) -> Self { + TextShape::new(pos, galley, fallback_color).into() } + /// All text color in the [`Galley`] will be replaced with the given color. #[inline] - /// The text color in the [`Galley`] will be replaced with the given color. + pub fn galley_with_override_text_color( + pos: Pos2, + galley: Arc, + text_color: Color32, + ) -> Self { + TextShape::new(pos, galley, text_color) + .with_override_text_color(text_color) + .into() + } + + #[inline] + #[deprecated = "Use `Shape::galley` or `Shape::galley_with_override_text_color` instead"] pub fn galley_with_color(pos: Pos2, galley: Arc, text_color: Color32) -> Self { - TextShape { - override_text_color: Some(text_color), - ..TextShape::new(pos, galley) - } - .into() + Self::galley_with_override_text_color(pos, galley, text_color) } + #[inline] pub fn mesh(mesh: Mesh) -> Self { crate::epaint_assert!(mesh.is_valid()); Self::Mesh(mesh) @@ -622,17 +688,6 @@ impl Rounding { } } - #[inline] - #[deprecated = "Use Rounding::ZERO"] - pub fn none() -> Self { - Self { - nw: 0.0, - ne: 0.0, - sw: 0.0, - se: 0.0, - } - } - /// Do all corners have the same rounding? #[inline] pub fn is_same(&self) -> bool { @@ -680,9 +735,14 @@ pub struct TextShape { /// You can also set an underline when creating the galley. pub underline: Stroke, + /// Any [`Color32::PLACEHOLDER`] in the galley will be replaced by the given color. + /// Affects everything: backgrounds, glyphs, strikethough, underline, etc. + pub fallback_color: Color32, + /// If set, the text color in the galley will be ignored and replaced /// with the given color. - /// This will NOT replace background color nor strikethrough/underline color. + /// + /// This only affects the glyphs and will NOT replace background color nor strikethrough/underline color. pub override_text_color: Option, /// Rotate text by this many radians clockwise. @@ -691,12 +751,16 @@ pub struct TextShape { } impl TextShape { + /// The given fallback color will be used for any uncolored part of the galley (using [`Color32::PLACEHOLDER`]). + /// + /// Any non-placeholder color in the galley takes precedence over this fallback color. #[inline] - pub fn new(pos: Pos2, galley: Arc) -> Self { + pub fn new(pos: Pos2, galley: Arc, fallback_color: Color32) -> Self { Self { pos, galley, underline: Stroke::NONE, + fallback_color, override_text_color: None, angle: 0.0, } @@ -707,6 +771,27 @@ impl TextShape { pub fn visual_bounding_rect(&self) -> Rect { self.galley.mesh_bounds.translate(self.pos.to_vec2()) } + + #[inline] + pub fn with_underline(mut self, underline: Stroke) -> Self { + self.underline = underline; + self + } + + /// Use the given color for the text, regardless of what color is already in the galley. + #[inline] + pub fn with_override_text_color(mut self, override_text_color: Color32) -> Self { + self.override_text_color = Some(override_text_color); + self + } + + /// Rotate text by this many radians clockwise. + /// The pivot is `pos` (the upper left corner of the text). + #[inline] + pub fn with_angle(mut self, angle: f32) -> Self { + self.angle = angle; + self + } } impl From for Shape { @@ -744,12 +829,16 @@ fn points_from_line( fn dashes_from_line( path: &[Pos2], stroke: Stroke, - dash_length: f32, - gap_length: f32, + dash_lengths: &[f32], + gap_lengths: &[f32], shapes: &mut Vec, + dash_offset: f32, ) { - let mut position_on_segment = 0.0; + assert_eq!(dash_lengths.len(), gap_lengths.len()); + let mut position_on_segment = dash_offset; let mut drawing_dash = false; + let mut step = 0; + let steps = dash_lengths.len(); path.windows(2).for_each(|window| { let (start, end) = (window[0], window[1]); let vector = end - start; @@ -761,11 +850,16 @@ fn dashes_from_line( if drawing_dash { // This is the end point. shapes.push(Shape::line_segment([start_point, new_point], stroke)); - position_on_segment += gap_length; + position_on_segment += gap_lengths[step]; + // Increment step counter + step += 1; + if step >= steps { + step = 0; + } } else { // Start a new dash. start_point = new_point; - position_on_segment += dash_length; + position_on_segment += dash_lengths[step]; } drawing_dash = !drawing_dash; } diff --git a/crates/epaint/src/shape_transform.rs b/crates/epaint/src/shape_transform.rs index 1b0e9c73e87..b36accb5a3f 100644 --- a/crates/epaint/src/shape_transform.rs +++ b/crates/epaint/src/shape_transform.rs @@ -1,5 +1,6 @@ use crate::*; +/// Remember to handle [`Color32::PLACEHOLDER`] specially! pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { #![allow(clippy::match_same_arms)] match shape { @@ -9,28 +10,62 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { adjust_colors(shape, adjust_color); } } - Shape::Circle(circle_shape) => { - adjust_color(&mut circle_shape.fill); - adjust_color(&mut circle_shape.stroke.color); - } - Shape::LineSegment { stroke, .. } => { + Shape::LineSegment { stroke, points: _ } => { adjust_color(&mut stroke.color); } - Shape::Path(path_shape) => { - adjust_color(&mut path_shape.fill); - adjust_color(&mut path_shape.stroke.color); - } - Shape::Rect(rect_shape) => { - adjust_color(&mut rect_shape.fill); - adjust_color(&mut rect_shape.stroke.color); + + Shape::Circle(CircleShape { + center: _, + radius: _, + fill, + stroke, + }) + | Shape::Path(PathShape { + points: _, + closed: _, + fill, + stroke, + }) + | Shape::Rect(RectShape { + rect: _, + rounding: _, + fill, + stroke, + fill_texture_id: _, + uv: _, + }) + | Shape::QuadraticBezier(QuadraticBezierShape { + points: _, + closed: _, + fill, + stroke, + }) + | Shape::CubicBezier(CubicBezierShape { + points: _, + closed: _, + fill, + stroke, + }) => { + adjust_color(fill); + adjust_color(&mut stroke.color); } - Shape::Text(text_shape) => { - if let Some(override_text_color) = &mut text_shape.override_text_color { + + Shape::Text(TextShape { + pos: _, + galley, + underline, + fallback_color, + override_text_color, + angle: _, + }) => { + adjust_color(&mut underline.color); + adjust_color(fallback_color); + if let Some(override_text_color) = override_text_color { adjust_color(override_text_color); } - if !text_shape.galley.is_empty() { - let galley = std::sync::Arc::make_mut(&mut text_shape.galley); + if !galley.is_empty() { + let galley = std::sync::Arc::make_mut(galley); for row in &mut galley.rows { for vertex in &mut row.visuals.mesh.vertices { adjust_color(&mut vertex.color); @@ -38,19 +73,17 @@ pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { } } } - Shape::Mesh(mesh) => { - for v in &mut mesh.vertices { + + Shape::Mesh(Mesh { + indices: _, + vertices, + texture_id: _, + }) => { + for v in vertices { adjust_color(&mut v.color); } } - Shape::QuadraticBezier(quadratic) => { - adjust_color(&mut quadratic.fill); - adjust_color(&mut quadratic.stroke.color); - } - Shape::CubicBezier(bezier) => { - adjust_color(&mut bezier.fill); - adjust_color(&mut bezier.stroke.color); - } + Shape::Callback(_) => { // Can't tint user callback code } diff --git a/crates/epaint/src/stroke.rs b/crates/epaint/src/stroke.rs index 72f5a8cfddc..fca821b1ac0 100644 --- a/crates/epaint/src/stroke.rs +++ b/crates/epaint/src/stroke.rs @@ -4,7 +4,7 @@ use super::*; /// Describes the width and color of a line. /// -/// The default stroke is the same as [`Stroke::none`]. +/// The default stroke is the same as [`Stroke::NONE`]. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Stroke { @@ -19,12 +19,6 @@ impl Stroke { color: Color32::TRANSPARENT, }; - #[deprecated = "Use Stroke::NONE instead"] - #[inline(always)] - pub fn none() -> Self { - Self::new(0.0, Color32::TRANSPARENT) - } - #[inline] pub fn new(width: impl Into, color: impl Into) -> Self { Self { diff --git a/crates/epaint/src/tessellator.rs b/crates/epaint/src/tessellator.rs index fa50f0df435..72899f2844c 100644 --- a/crates/epaint/src/tessellator.rs +++ b/crates/epaint/src/tessellator.rs @@ -1473,6 +1473,7 @@ impl Tessellator { galley, underline, override_text_color, + fallback_color, angle, } = text_shape; @@ -1539,11 +1540,16 @@ impl Tessellator { let Vertex { pos, uv, mut color } = *vertex; if let Some(override_text_color) = override_text_color { + // Only override the glyph color (not background color, strike-through color, etc) if row.visuals.glyph_vertex_range.contains(&i) { color = *override_text_color; } + } else if color == Color32::PLACEHOLDER { + color = *fallback_color; } + crate::epaint_assert!(color != Color32::PLACEHOLDER, "A placeholder color made it to the tessellator. You forgot to set a fallback color."); + let offset = if *angle == 0.0 { pos.to_vec2() } else { diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 61ed80fe0d4..177c7327819 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -348,6 +348,7 @@ impl FontDefinitions { /// If you are using `egui`, use `egui::Context::set_fonts` and `egui::Context::fonts`. /// /// You need to call [`Self::begin_frame`] and [`Self::font_image_delta`] once every frame. +#[derive(Clone)] pub struct Fonts(Arc>); impl Fonts { @@ -531,12 +532,7 @@ impl Fonts { font_id: FontId, wrap_width: f32, ) -> Arc { - self.layout_job(LayoutJob::simple( - text, - font_id, - crate::Color32::TEMPORARY_COLOR, - wrap_width, - )) + self.layout(text, font_id, crate::Color32::PLACEHOLDER, wrap_width) } } diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index 7b81cd82440..6d9748aa33a 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -509,8 +509,9 @@ pub struct RowVisuals { /// Does NOT include leading or trailing whitespace glyphs!! pub mesh_bounds: Rect, - /// The range of vertices in the mesh the contain glyphs. - /// Before comes backgrounds (if any), and after any underlines and strikethrough. + /// The range of vertices in the mesh that contain glyphs (as opposed to background, underlines, strikethorugh, etc). + /// + /// The glyph vertices comes before backgrounds (if any), and after any underlines and strikethrough. pub glyph_vertex_range: Range, } diff --git a/deny.toml b/deny.toml index 4536cd3f9f0..9fc18cbc359 100644 --- a/deny.toml +++ b/deny.toml @@ -3,17 +3,17 @@ # Note: running just `cargo deny check` without a `--target` can result in # false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324 targets = [ - { triple = "aarch64-apple-darwin" }, - { triple = "i686-pc-windows-gnu" }, - { triple = "i686-pc-windows-msvc" }, - { triple = "i686-unknown-linux-gnu" }, - { triple = "wasm32-unknown-unknown" }, - { triple = "x86_64-apple-darwin" }, - { triple = "x86_64-pc-windows-gnu" }, - { triple = "x86_64-pc-windows-msvc" }, - { triple = "x86_64-unknown-linux-gnu" }, - { triple = "x86_64-unknown-linux-musl" }, - { triple = "x86_64-unknown-redox" }, + { triple = "aarch64-apple-darwin" }, + { triple = "i686-pc-windows-gnu" }, + { triple = "i686-pc-windows-msvc" }, + { triple = "i686-unknown-linux-gnu" }, + { triple = "wasm32-unknown-unknown" }, + { triple = "x86_64-apple-darwin" }, + { triple = "x86_64-pc-windows-gnu" }, + { triple = "x86_64-pc-windows-msvc" }, + { triple = "x86_64-unknown-linux-gnu" }, + { triple = "x86_64-unknown-linux-musl" }, + { triple = "x86_64-unknown-redox" }, ] [advisories] @@ -21,40 +21,35 @@ vulnerability = "deny" unmaintained = "warn" yanked = "deny" ignore = [ - "RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 - chrono/time: Potential segfault in the time crate + "RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 - chrono/time: Potential segfault in the time crate ] [bans] multiple-versions = "deny" wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed deny = [ - { name = "cmake" }, # Lord no - { name = "openssl-sys" }, # prefer rustls - { name = "openssl" }, # prefer rustls + { name = "cmake" }, # Lord no + { name = "openssl-sys" }, # prefer rustls + { name = "openssl" }, # prefer rustls ] skip = [ - { name = "arrayvec" }, # old version via tiny-skiaz - { name = "base64" }, # small crate, old version from usvg - { name = "glow" }, # TODO(@wumpf): Old version use for glow backend right now, newer for wgpu. Updating this trickles out to updating winit. - { name = "glutin_wgl_sys" }, # TODO(@wumpf): Old version use for glow backend right now, newer for wgpu. Updating this trickles out to updating winit. - { name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7 - { name = "memoffset" }, # tiny dependency - { name = "nix" }, # old version via winit - { name = "redox_syscall" }, # old version via winit - { name = "spin" }, # old version via ring through rusttls and other libraries, newer for wgpu. - { name = "time" }, # old version pulled in by unmaintianed crate 'chrono' - { name = "tiny-skia" }, # winit uses a different version from egui_extras (TODO(emilk): update egui_extras!) - { name = "ttf-parser" }, # different versions pulled in by ab_glyph and usvg - { name = "wayland-sys" }, # old version via winit - { name = "windows_x86_64_msvc" }, # old version via glutin - { name = "windows-sys" }, # old version via glutin - { name = "windows" }, # old version via accesskit + { name = "bitflags" }, # old 1.0 version via glutin, png, spirv, … + { name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7 + { name = "memoffset" }, # tiny dependency + { name = "quick-xml" }, # old version via wayland-scanner + { name = "redox_syscall" }, # old version via directories-next + { name = "spin" }, # old version via ring through rusttls and other libraries, newer for wgpu. + { name = "time" }, # old version pulled in by unmaintianed crate 'chrono' + { name = "windows" }, # old version via accesskit_windows + { name = "x11rb" }, # old version via arboard + { name = "x11rb-protocol" }, # old version via arboard ] skip-tree = [ - { name = "criterion" }, # dev-dependency - { name = "foreign-types" }, # small crate. Old version via cocoa and core-graphics (winit). - { name = "rfd" }, # example dependency + { name = "criterion" }, # dev-dependency + { name = "foreign-types" }, # small crate. Old version via cocoa and core-graphics (winit). + { name = "objc2" }, # old version via accesskit_macos + { name = "rfd" }, # example dependency ] @@ -64,20 +59,20 @@ allow-osi-fsf-free = "neither" confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text copyleft = "deny" allow = [ - "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html - "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) - "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) - "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) - "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained - "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ - "ISC", # https://tldrlegal.com/license/-isc-license - "LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321 - "MIT", # https://tldrlegal.com/license/mit-license - "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11 - "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html - "OpenSSL", # https://www.openssl.org/source/license.html - "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html - "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) + "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html + "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) + "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) + "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) + "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained + "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ + "ISC", # https://tldrlegal.com/license/-isc-license + "LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321 + "MIT", # https://tldrlegal.com/license/mit-license + "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11 + "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html + "OpenSSL", # https://www.openssl.org/source/license.html + "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html + "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) ] [[licenses.clarify]] diff --git a/examples/confirm_exit/Cargo.toml b/examples/confirm_exit/Cargo.toml index 1a7504775d3..2b0fae85dbd 100644 --- a/examples/confirm_exit/Cargo.toml +++ b/examples/confirm_exit/Cargo.toml @@ -12,4 +12,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/custom_3d_glow/Cargo.toml b/examples/custom_3d_glow/Cargo.toml index 849faa5c463..0d268759c27 100644 --- a/examples/custom_3d_glow/Cargo.toml +++ b/examples/custom_3d_glow/Cargo.toml @@ -12,4 +12,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/custom_font/Cargo.toml b/examples/custom_font/Cargo.toml index c87ad9dadab..0eec65cd4d5 100644 --- a/examples/custom_font/Cargo.toml +++ b/examples/custom_font/Cargo.toml @@ -12,4 +12,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/custom_font_style/Cargo.toml b/examples/custom_font_style/Cargo.toml index 45ee60c352b..a429303bb07 100644 --- a/examples/custom_font_style/Cargo.toml +++ b/examples/custom_font_style/Cargo.toml @@ -12,4 +12,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/custom_window_frame/Cargo.toml b/examples/custom_window_frame/Cargo.toml index d518f615fd2..6dc91e4e503 100644 --- a/examples/custom_window_frame/Cargo.toml +++ b/examples/custom_window_frame/Cargo.toml @@ -12,4 +12,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/file_dialog/Cargo.toml b/examples/file_dialog/Cargo.toml index 3d5cc6d8af9..0ad331dff06 100644 --- a/examples/file_dialog/Cargo.toml +++ b/examples/file_dialog/Cargo.toml @@ -12,5 +12,8 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } rfd = "0.11" diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml index 9e846a2362e..fb2ca48c920 100644 --- a/examples/hello_world/Cargo.toml +++ b/examples/hello_world/Cargo.toml @@ -16,4 +16,7 @@ eframe = { path = "../../crates/eframe", features = [ # For image support: egui_extras = { path = "../../crates/egui_extras", features = ["image"] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/hello_world_par/Cargo.toml b/examples/hello_world_par/Cargo.toml index 65fb00646da..46d6aa741e9 100644 --- a/examples/hello_world_par/Cargo.toml +++ b/examples/hello_world_par/Cargo.toml @@ -14,4 +14,7 @@ eframe = { path = "../../crates/eframe", default-features = false, features = [ "default_fonts", "wgpu", ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/hello_world_simple/Cargo.toml b/examples/hello_world_simple/Cargo.toml index b08fc3857c6..36efecdda60 100644 --- a/examples/hello_world_simple/Cargo.toml +++ b/examples/hello_world_simple/Cargo.toml @@ -12,4 +12,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/images/Cargo.toml b/examples/images/Cargo.toml index d400e1087a6..bddc9afbfaa 100644 --- a/examples/images/Cargo.toml +++ b/examples/images/Cargo.toml @@ -13,7 +13,10 @@ eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } egui_extras = { path = "../../crates/egui_extras", features = ["all_loaders"] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } image = { version = "0.24", default-features = false, features = [ "jpeg", "png", diff --git a/examples/keyboard_events/Cargo.toml b/examples/keyboard_events/Cargo.toml index e38260ec38c..4f12579e03d 100644 --- a/examples/keyboard_events/Cargo.toml +++ b/examples/keyboard_events/Cargo.toml @@ -12,4 +12,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/multiple_viewports/Cargo.toml b/examples/multiple_viewports/Cargo.toml index 81e7c7b2d28..1e77e32ea74 100644 --- a/examples/multiple_viewports/Cargo.toml +++ b/examples/multiple_viewports/Cargo.toml @@ -7,9 +7,14 @@ edition = "2021" rust-version = "1.72" publish = false +[features] +wgpu = ["eframe/wgpu"] [dependencies] eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/puffin_profiler/Cargo.toml b/examples/puffin_profiler/Cargo.toml index d6098685ea5..d1329f9f31b 100644 --- a/examples/puffin_profiler/Cargo.toml +++ b/examples/puffin_profiler/Cargo.toml @@ -17,6 +17,9 @@ eframe = { path = "../../crates/eframe", features = [ "puffin", "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } puffin = "0.18" puffin_http = "0.15" diff --git a/examples/save_plot/Cargo.toml b/examples/save_plot/Cargo.toml index 04e337020c7..49a46f58094 100644 --- a/examples/save_plot/Cargo.toml +++ b/examples/save_plot/Cargo.toml @@ -14,4 +14,7 @@ eframe = { path = "../../crates/eframe", features = [ egui_plot = { path = "../../crates/egui_plot" } image = { version = "0.24", default-features = false, features = ["png"] } rfd = "0.11.0" -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/screenshot/Cargo.toml b/examples/screenshot/Cargo.toml index b2a521cac06..f7a65a8bf3f 100644 --- a/examples/screenshot/Cargo.toml +++ b/examples/screenshot/Cargo.toml @@ -16,5 +16,8 @@ eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO "wgpu", ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } image = { version = "0.24", default-features = false, features = ["png"] } diff --git a/examples/serial_windows/Cargo.toml b/examples/serial_windows/Cargo.toml index f8b0388a666..e3ba3159a92 100644 --- a/examples/serial_windows/Cargo.toml +++ b/examples/serial_windows/Cargo.toml @@ -12,4 +12,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/test_inline_glow_paint/Cargo.toml b/examples/test_inline_glow_paint/Cargo.toml index 4fabb8be269..472935ed5bf 100644 --- a/examples/test_inline_glow_paint/Cargo.toml +++ b/examples/test_inline_glow_paint/Cargo.toml @@ -11,4 +11,7 @@ publish = false [dependencies] eframe = { path = "../../crates/eframe" } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/test_viewports/Cargo.toml b/examples/test_viewports/Cargo.toml index 132f98cc7d7..9ebebae205f 100644 --- a/examples/test_viewports/Cargo.toml +++ b/examples/test_viewports/Cargo.toml @@ -14,4 +14,7 @@ wgpu = ["eframe/wgpu"] eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/examples/user_attention/Cargo.toml b/examples/user_attention/Cargo.toml index 01ebe116f43..91103635d20 100644 --- a/examples/user_attention/Cargo.toml +++ b/examples/user_attention/Cargo.toml @@ -11,4 +11,7 @@ publish = false eframe = { path = "../../crates/eframe", features = [ "__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO ] } -env_logger = "0.10" +env_logger = { version = "0.10", default-features = false, features = [ + "auto-color", + "humantime", +] } diff --git a/scripts/build_demo_web.sh b/scripts/build_demo_web.sh index 658c6bb3ca5..e905066d42a 100755 --- a/scripts/build_demo_web.sh +++ b/scripts/build_demo_web.sh @@ -19,23 +19,37 @@ OPTIMIZE=false BUILD=debug BUILD_FLAGS="" WEB_GPU=false +WASM_OPT_FLAGS="-O2 --fast-math" while test $# -gt 0; do case "$1" in -h|--help) echo "build_demo_web.sh [--release] [--webgpu] [--open]" echo "" - echo " --release: Build with --release, and enable extra optimization step" - echo " Runs wasm-opt." - echo " NOTE: --release also removes debug symbols which are otherwise useful for in-browser profiling." + echo " -g: Keep debug symbols even with --release." + echo " These are useful profiling and size trimming." echo "" - echo " --webgpu: Build a binary for WebGPU instead of WebGL" - echo " Note that the resulting wasm will ONLY work on browsers with WebGPU." + echo " --open: Open the result in a browser." + echo "" + echo " --release: Build with --release, and then run wasm-opt." + echo " NOTE: --release also removes debug symbols, unless you also use -g." echo "" - echo " --open: Open the result in a browser" + echo " --webgpu: Build a binary for WebGPU instead of WebGL." + echo " Note that the resulting wasm will ONLY work on browsers with WebGPU." exit 0 ;; + -g) + shift + WASM_OPT_FLAGS="${WASM_OPT_FLAGS} -g" + echo "'${WASM_OPT_FLAGS}'" + ;; + + --open) + shift + OPEN=true + ;; + --release) shift OPTIMIZE=true @@ -48,11 +62,6 @@ while test $# -gt 0; do WEB_GPU=true ;; - --open) - shift - OPEN=true - ;; - *) echo "Unknown option: $1" exit 1 @@ -96,6 +105,7 @@ wasm-bindgen "${WASM_PATH}" --out-dir web_demo --out-name ${OUT_FILE_NAME} --no- # if this fails with "error: cannot import from modules (`env`) with `--no-modules`", you can use: # wasm2wat target/wasm32-unknown-unknown/release/egui_demo_app.wasm | rg env # wasm2wat target/wasm32-unknown-unknown/release/egui_demo_app.wasm | rg "call .now\b" -B 20 # What calls `$now` (often a culprit) +# Or use https://rustwasm.github.io/twiggy/usage/command-line-interface/paths.html#twiggy-paths # to get wasm-strip: apt/brew/dnf install wabt # wasm-strip ${FINAL_WASM_PATH} @@ -103,7 +113,7 @@ wasm-bindgen "${WASM_PATH}" --out-dir web_demo --out-name ${OUT_FILE_NAME} --no- if [[ "${OPTIMIZE}" = true ]]; then echo "Optimizing wasm…" # to get wasm-opt: apt/brew/dnf install binaryen - wasm-opt "${FINAL_WASM_PATH}" -O2 --fast-math -o "${FINAL_WASM_PATH}" # add -g to get debug symbols + wasm-opt "${FINAL_WASM_PATH}" $WASM_OPT_FLAGS -o "${FINAL_WASM_PATH}" fi echo "Finished ${FINAL_WASM_PATH}" diff --git a/scripts/check.sh b/scripts/check.sh index eb9cee8c7fd..ab3063d91ca 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -9,8 +9,8 @@ set -x # Checks all tests, lints etc. # Basically does what the CI does. -cargo install cargo-cranky # Uses lints defined in Cranky.toml. See https://github.com/ericseppanen/cargo-cranky -cargo install typos-cli +cargo install --quiet cargo-cranky # Uses lints defined in Cranky.toml. See https://github.com/ericseppanen/cargo-cranky +cargo install --quiet typos-cli # web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses, # as well as by the wasm32-backend of the wgpu crate.