diff --git a/.github/workflows/spelling_and_links.yml b/.github/workflows/link_checker.yml similarity index 62% rename from .github/workflows/spelling_and_links.yml rename to .github/workflows/link_checker.yml index e09e01562..baf2aa402 100644 --- a/.github/workflows/spelling_and_links.yml +++ b/.github/workflows/link_checker.yml @@ -1,21 +1,8 @@ -name: Check spelling and links -on: [pull_request] +name: Link checker +# on: [pull_request] # Disabled because it is so broken +on: workflow_dispatch jobs: - typos: - # https://github.com/crate-ci/typos - # Add exceptions to .typos.toml - # install and run locally: cargo install typos-cli && typos - name: typos - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout Actions Repository - uses: actions/checkout@v4 - - - name: Check spelling of entire workspace - uses: crate-ci/typos@v1.38.0 - lychee: name: lychee runs-on: ubuntu-latest @@ -33,4 +20,3 @@ jobs: uses: lycheeverse/lychee-action@v2 with: args: "'**/*.md' '**/*.toml' --exclude localhost --exclude reddit.com" # I guess reddit doesn't like github action IPs - diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml new file mode 100644 index 000000000..19205d51e --- /dev/null +++ b/.github/workflows/typos.yml @@ -0,0 +1,17 @@ +name: Typos +on: [pull_request] + +jobs: + typos: + # https://github.com/crate-ci/typos + # Add exceptions to .typos.toml + # install and run locally: cargo install typos-cli && typos + name: typos + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + + - name: Check spelling of entire workspace + uses: crate-ci/typos@v1.38.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 856fe09da..12010e287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ 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.33.3 - 2025-12-11 +* Treat `.` as a word-splitter in text navigation [#7741](https://github.com/emilk/egui/pull/7741) by [@emilk](https://github.com/emilk) +* Change text color of selected text [#7691](https://github.com/emilk/egui/pull/7691) by [@emilk](https://github.com/emilk) + + ## 0.33.2 - 2025-11-13 ### ⭐ Added * Add `Plugin::on_widget_under_pointer` to support widget inspector [#7652](https://github.com/emilk/egui/pull/7652) by [@juancampa](https://github.com/juancampa) diff --git a/Cargo.lock b/Cargo.lock index 61bf459b5..a99c0d281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,6 +863,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb" +dependencies = [ + "bytemuck", +] + [[package]] name = "color-hex" version = "0.2.0" @@ -1248,7 +1257,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.33.2" +version = "0.33.3" dependencies = [ "bytemuck", "cint", @@ -1260,7 +1269,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.33.2" +version = "0.33.3" dependencies = [ "ahash", "bytemuck", @@ -1299,7 +1308,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.33.2" +version = "0.33.3" dependencies = [ "accesskit", "ahash", @@ -1319,7 +1328,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.33.2" +version = "0.33.3" dependencies = [ "ahash", "bytemuck", @@ -1337,7 +1346,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.33.2" +version = "0.33.3" dependencies = [ "accesskit_winit", "arboard", @@ -1360,7 +1369,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.33.2" +version = "0.33.3" dependencies = [ "accesskit", "accesskit_consumer", @@ -1390,7 +1399,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.33.2" +version = "0.33.3" dependencies = [ "chrono", "criterion", @@ -1407,7 +1416,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.33.2" +version = "0.33.3" dependencies = [ "ahash", "chrono", @@ -1426,7 +1435,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.33.2" +version = "0.33.3" dependencies = [ "bytemuck", "document-features", @@ -1445,7 +1454,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.33.2" +version = "0.33.3" dependencies = [ "dify", "document-features", @@ -1457,13 +1466,15 @@ dependencies = [ "kittest", "open", "pollster", + "serde", "tempfile", + "toml", "wgpu", ] [[package]] name = "egui_tests" -version = "0.33.2" +version = "0.33.3" dependencies = [ "egui", "egui_extras", @@ -1473,9 +1484,9 @@ dependencies = [ [[package]] name = "ehttp" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a81c221a1e4dad06cb9c9deb19aea1193a5eea084e8cd42d869068132bf876" +checksum = "04499d3c719edecfad5c9b46031726c8540905d73be6d7e4f9788c4a298da908" dependencies = [ "document-features", "js-sys", @@ -1493,7 +1504,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.33.2" +version = "0.33.3" dependencies = [ "bytemuck", "document-features", @@ -1591,9 +1602,8 @@ dependencies = [ [[package]] name = "epaint" -version = "0.33.2" +version = "0.33.3" dependencies = [ - "ab_glyph", "ahash", "bytemuck", "criterion", @@ -1607,13 +1617,16 @@ dependencies = [ "parking_lot", "profiling", "rayon", + "self_cell", "serde", "similar-asserts", + "skrifa", + "vello_cpu", ] [[package]] name = "epaint_default_fonts" -version = "0.33.2" +version = "0.33.3" [[package]] name = "equivalent" @@ -1637,6 +1650,15 @@ version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "5.3.1" @@ -1704,6 +1726,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fearless_simd" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb2907d1f08b2b316b9223ced5b0e89d87028ba8deae9764741dba8ff7f3903" +dependencies = [ + "bytemuck", +] + [[package]] name = "file_dialog" version = "0.1.0" @@ -1747,6 +1778,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "font-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" +dependencies = [ + "bytemuck", +] + [[package]] name = "fontconfig-parser" version = "0.5.7" @@ -2526,6 +2566,17 @@ dependencies = [ "smallvec", ] +[[package]] +name = "kurbo" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2575,6 +2626,12 @@ dependencies = [ "redox_syscall 0.5.7", ] +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -3251,6 +3308,19 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "peniko" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c76095c9a636173600478e0373218c7b955335048c2bcd12dc6a79657649d8" +dependencies = [ + "bytemuck", + "color", + "kurbo 0.12.0", + "linebender_resource_handle", + "smallvec", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -3381,9 +3451,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.14" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -3425,7 +3495,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.33.2" +version = "0.33.3" dependencies = [ "eframe", "env_logger", @@ -3690,6 +3760,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +dependencies = [ + "bytemuck", + "font-types", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -3983,6 +4063,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "self_cell" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" + [[package]] name = "serde" version = "1.0.228" @@ -4036,6 +4122,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serial_windows" version = "0.1.0" @@ -4101,6 +4196,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "skrifa" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.9" @@ -4222,7 +4327,7 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ - "kurbo", + "kurbo 0.11.1", "siphasher", ] @@ -4491,11 +4596,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -4504,6 +4624,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] @@ -4707,7 +4829,7 @@ dependencies = [ "flate2", "fontdb", "imagesize", - "kurbo", + "kurbo 0.11.1", "log", "pico-args", "roxmltree", @@ -4735,6 +4857,30 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "vello_common" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a235ba928b3109ad9e7696270edb09445a52ae1c7c08e6d31a19b1cdd6cbc24a" +dependencies = [ + "bytemuck", + "fearless_simd", + "log", + "peniko", + "skrifa", + "smallvec", +] + +[[package]] +name = "vello_cpu" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0bd1fcf9c1814f17a491e07113623d44e3ec1125a9f3401f5e047d6d326da21" +dependencies = [ + "bytemuck", + "vello_common", +] + [[package]] name = "version_check" version = "0.9.5" @@ -5645,9 +5791,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -5748,7 +5894,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.33.2" +version = "0.33.3" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index b33ca445c..b291cb36c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.88" -version = "0.33.2" +version = "0.33.3" [profile.release] @@ -55,23 +55,22 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.33.2", path = "crates/emath", default-features = false } -ecolor = { version = "0.33.2", path = "crates/ecolor", default-features = false } -epaint = { version = "0.33.2", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.33.2", path = "crates/epaint_default_fonts" } -egui = { version = "0.33.2", path = "crates/egui", default-features = false } -egui-winit = { version = "0.33.2", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.33.2", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.33.2", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.33.2", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.33.2", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.33.2", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.33.2", path = "crates/eframe", default-features = false } +emath = { version = "0.33.3", path = "crates/emath", default-features = false } +ecolor = { version = "0.33.3", path = "crates/ecolor", default-features = false } +epaint = { version = "0.33.3", path = "crates/epaint", default-features = false } +epaint_default_fonts = { version = "0.33.3", path = "crates/epaint_default_fonts" } +egui = { version = "0.33.3", path = "crates/egui", default-features = false } +egui-winit = { version = "0.33.3", path = "crates/egui-winit", default-features = false } +egui_extras = { version = "0.33.3", path = "crates/egui_extras", default-features = false } +egui-wgpu = { version = "0.33.3", path = "crates/egui-wgpu", default-features = false } +egui_demo_lib = { version = "0.33.3", path = "crates/egui_demo_lib", default-features = false } +egui_glow = { version = "0.33.3", path = "crates/egui_glow", default-features = false } +egui_kittest = { version = "0.33.3", path = "crates/egui_kittest", default-features = false } +eframe = { version = "0.33.3", path = "crates/eframe", default-features = false } accesskit = "0.21.1" accesskit_consumer = "0.30.1" accesskit_winit = "0.29.1" -ab_glyph = "0.2.32" ahash = { version = "0.8.12", default-features = false, features = [ "no-rng", # we don't need DOS-protection, so we let users opt-in to it instead "std", @@ -88,7 +87,7 @@ criterion = { version = "0.7.0", default-features = false } dify = { version = "0.7.4", default-features = false } directories = "6.0.0" document-features = "0.2.11" -ehttp = { version = "0.5.0", default-features = false } +ehttp = { version = "0.6.0", default-features = false } enum-map = "2.7.3" env_logger = { version = "0.11.8", default-features = false } glow = "0.16.0" @@ -122,8 +121,10 @@ rayon = "1.11.0" resvg = { version = "0.45.1", default-features = false } rfd = "0.15.4" ron = "0.11.0" +self_cell = "1.2.1" serde = { version = "1.0.228", features = ["derive"] } similar-asserts = "1.7.0" +skrifa = { version = "0.37.0", default-features = false, features = ["std", "autohint_shaping"] } smallvec = "1.15.1" smithay-clipboard = "0.7.2" static_assertions = "1.1.0" @@ -131,9 +132,11 @@ syntect = { version = "5.3.0", default-features = false } tempfile = "3.23.0" thiserror = "2.0.17" tokio = "1.47.1" +toml = "0.8" type-map = "0.5.1" unicode_names2 = { version = "2.0.0", default-features = false } unicode-segmentation = "1.12.0" +vello_cpu = { version = "0.0.4", default-features = false, features = ["std"] } wasm-bindgen = "0.2.100" # Keep wasm-bindgen version in sync in: setup_web.sh, Cargo.toml, Cargo.lock, rust.yml wasm-bindgen-futures = "0.4.0" wayland-cursor = { version = "0.31.11", default-features = false } @@ -277,6 +280,7 @@ non_zero_suggestions = "warn" nonstandard_macro_braces = "warn" option_as_ref_cloned = "warn" option_option = "warn" +or_fun_call = "warn" path_buf_push_overwrite = "warn" pathbuf_init_then_push = "warn" precedence_bits = "warn" diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 4dee81296..a4fe1f3de 100644 --- a/crates/ecolor/CHANGELOG.md +++ b/crates/ecolor/CHANGELOG.md @@ -6,6 +6,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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 Nothing new diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 11f1c8fc9..74a705251 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 * Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman) * Make sure `native_pixels_per_point` is set during app creation [#7683](https://github.com/emilk/egui/pull/7683) by [@emilk](https://github.com/emilk) diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index a7bcfd6ef..e7d64fcfb 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -135,6 +135,31 @@ impl CreationContext<'_> { /// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/main/crates/eframe). pub trait App { + /// Called once before each call to [`Self::ui`], + /// and additionally also called when the UI is hidden, but [`egui::Context::request_repaint`] was called. + /// + /// You may NOT show any ui or do any painting during the call to [`Self::logic`]. + /// + /// The [`egui::Context`] can be cloned and saved if you like. + /// + /// To force another call to [`Self::logic`], call [`egui::Context::request_repaint`] at any time (e.g. from another thread). + fn logic(&mut self, ctx: &egui::Context, frame: &mut Frame) { + _ = (ctx, frame); + } + + /// Called each time the UI needs repainting, which may be many times per second. + /// + /// The given [`egui::Ui`] has no margin or background color. + /// You can wrap your UI code in [`egui::CentralPanel`] or a [`egui::Frame::central_panel`] to remedy this. + /// + /// The [`egui::Ui::ctx`] can be cloned and saved if you like. + /// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread). + /// + /// This is called for the root viewport ([`egui::ViewportId::ROOT`]). + /// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows). + /// (A "viewport" in egui means an native OS window). + fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame); + /// Called each time the UI needs repainting, which may be many times per second. /// /// Put your widgets into a [`egui::Panel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. @@ -146,7 +171,10 @@ pub trait App { /// This is called for the root viewport ([`egui::ViewportId::ROOT`]). /// Use [`egui::Context::show_viewport_deferred`] to spawn additional viewports (windows). /// (A "viewport" in egui means an native OS window). - fn update(&mut self, ctx: &egui::Context, frame: &mut Frame); + #[deprecated = "Use Self::ui instead"] + fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { + _ = (ctx, frame); + } /// Get a handle to the app. /// diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index d803a5249..252320462 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -35,7 +35,7 @@ //! //! impl MyEguiApp { //! fn new(cc: &eframe::CreationContext<'_>) -> Self { -//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. +//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style. //! // Restore app state using cc.storage (requires the "persistence" feature). //! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use //! // for e.g. egui::PaintCallback. @@ -44,8 +44,8 @@ //! } //! //! impl eframe::App for MyEguiApp { -//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { -//! egui::CentralPanel::default().show(ctx, |ui| { +//! fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { +//! egui::CentralPanel::default().show_inside(ui, |ui| { //! ui.heading("Hello World!"); //! }); //! } @@ -232,7 +232,7 @@ pub mod icon_data; /// /// impl MyEguiApp { /// fn new(cc: &eframe::CreationContext<'_>) -> Self { -/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. +/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style. /// // Restore app state using cc.storage (requires the "persistence" feature). /// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use /// // for e.g. egui::PaintCallback. @@ -241,8 +241,8 @@ pub mod icon_data; /// } /// /// impl eframe::App for MyEguiApp { -/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { -/// egui::CentralPanel::default().show(ctx, |ui| { +/// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { +/// egui::CentralPanel::default().show_inside(ui, |ui| { /// ui.heading("Hello World!"); /// }); /// } @@ -312,8 +312,8 @@ pub fn run_native( /// } /// /// impl eframe::App for MyEguiApp { -/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { -/// egui::CentralPanel::default().show(ctx, |ui| { +/// fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { +/// egui::CentralPanel::default().show_inside(ui, |ui| { /// ui.heading("Hello World!"); /// }); /// } @@ -399,8 +399,9 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { /// let mut age = 42; /// /// let options = eframe::NativeOptions::default(); -/// eframe::run_simple_native("My egui App", options, move |ctx, _frame| { -/// egui::CentralPanel::default().show(ctx, |ui| { +/// eframe::run_ui_native("My egui App", options, move |ui, _frame| { +/// // Wrap everything in a CentralPanel so we get some margins and a background color: +/// egui::CentralPanel::default().show_inside(ui, |ui| { /// ui.heading("My egui Application"); /// ui.horizontal(|ui| { /// let name_label = ui.label("Your name: "); @@ -421,6 +422,65 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { /// This function can fail if we fail to set up a graphics context. #[cfg(not(target_arch = "wasm32"))] #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] +pub fn run_ui_native( + app_name: &str, + native_options: NativeOptions, + ui_fun: impl FnMut(&mut egui::Ui, &mut Frame) + 'static, +) -> Result { + struct SimpleApp { + ui_fun: U, + } + + impl App for SimpleApp { + fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame) { + (self.ui_fun)(ui, frame); + } + } + + run_native( + app_name, + native_options, + Box::new(|_cc| Ok(Box::new(SimpleApp { ui_fun }))), + ) +} + +/// The simplest way to get started when writing a native app. +/// +/// This does NOT support persistence of custom user data. For that you need to use [`run_native`]. +/// However, it DOES support persistence of egui data (window positions and sizes, how far the user has scrolled in a +/// [`ScrollArea`](egui::ScrollArea), etc.) if the persistence feature is enabled. +/// +/// # Example +/// ``` no_run +/// fn main() -> eframe::Result { +/// // Our application state: +/// let mut name = "Arthur".to_owned(); +/// let mut age = 42; +/// +/// let options = eframe::NativeOptions::default(); +/// eframe::run_simple_native("My egui App", options, move |ctx, _frame| { +/// egui::CentralPanel::default().show(ctx, |ui| { +/// ui.heading("My egui Application"); +/// ui.horizontal(|ui| { +/// let name_label = ui.label("Your name: "); +/// ui.text_edit_singleline(&mut name) +/// .labelled_by(name_label.id); +/// }); +/// ui.add(egui::Slider::new(&mut age, 0..=120).text("age")); +/// if ui.button("Increment").clicked() { +/// age += 1; +/// } +/// ui.label(format!("Hello '{name}', age {age}")); +/// }); +/// }) +/// } +/// ``` +/// +/// # Errors +/// This function can fail if we fail to set up a graphics context. +#[deprecated = "Use run_ui_native instead"] +#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub fn run_simple_native( app_name: &str, native_options: NativeOptions, @@ -431,6 +491,8 @@ pub fn run_simple_native( } impl App for SimpleApp { + fn ui(&mut self, _ui: &mut egui::Ui, _frame: &mut Frame) {} + fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) { (self.update_fun)(ctx, frame); } diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 447b3ea9d..6217aef43 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -272,14 +272,27 @@ impl EpiIntegration { app.raw_input_hook(&self.egui_ctx, &mut raw_input); - let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { + let full_output = self.egui_ctx.run_ui(raw_input, |ui| { if let Some(viewport_ui_cb) = viewport_ui_cb { // Child viewport profiling::scope!("viewport_callback"); - viewport_ui_cb(egui_ctx); + viewport_ui_cb(ui); } else { - profiling::scope!("App::update"); - app.update(egui_ctx, &mut self.frame); + { + profiling::scope!("App::logic"); + app.logic(ui.ctx(), &mut self.frame); + } + + { + profiling::scope!("App::update"); + #[expect(deprecated)] + app.update(ui.ctx(), &mut self.frame); + } + + { + profiling::scope!("App::ui"); + app.ui(ui, &mut self.frame); + } } }); diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index c5358527a..9306cf9cc 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -571,7 +571,7 @@ impl GlowWinitRunning<'_> { .options_mut(|opt| opt.begin_pass(&raw_input)); let clear_color = self .app - .clear_color(&self.integration.egui_ctx.style().visuals); + .clear_color(&self.integration.egui_ctx.global_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. @@ -719,11 +719,11 @@ impl GlowWinitRunning<'_> { // vsync - don't count as frame-time: frame_timer.pause(); profiling::scope!("swap_buffers"); - let context = current_gl_context - .as_ref() - .ok_or(egui_glow::PainterError::from( + let context = current_gl_context.as_ref().ok_or_else(|| { + egui_glow::PainterError::from( "failed to get current context to swap buffers".to_owned(), - ))?; + ) + })?; gl_surface.swap_buffers(context)?; frame_timer.resume(); @@ -1362,7 +1362,7 @@ fn initialize_or_update_viewport( ids: ViewportIdPair, class: ViewportClass, mut builder: ViewportBuilder, - viewport_ui_cb: Option>, + viewport_ui_cb: Option>, ) -> &mut Viewport { profiling::function_scope!(); @@ -1494,8 +1494,8 @@ fn render_immediate_viewport( shapes, pixels_per_point, viewport_output, - } = egui_ctx.run(input, |ctx| { - viewport_ui_cb(ctx); + } = egui_ctx.run_ui(input, |ui| { + viewport_ui_cb(ui); }); // --------------------------------------------------- diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index c6c715c8c..b7708f6f6 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -690,7 +690,7 @@ impl WgpuWinitRunning<'_> { let vsync_secs = painter.paint_and_update_textures( viewport_id, pixels_per_point, - app.clear_color(&egui_ctx.style().visuals), + app.clear_color(&egui_ctx.global_style().visuals), &clipped_primitives, &textures_delta, screenshot_commands, @@ -1027,8 +1027,8 @@ fn render_immediate_viewport( shapes, pixels_per_point, viewport_output, - } = egui_ctx.run(input, |ctx| { - viewport_ui_cb(ctx); + } = egui_ctx.run_ui(input, |ui| { + viewport_ui_cb(ui); }); // ------------------------------------------ @@ -1155,7 +1155,7 @@ fn initialize_or_update_viewport<'a>( ids: ViewportIdPair, class: ViewportClass, mut builder: ViewportBuilder, - viewport_ui_cb: Option>, + viewport_ui_cb: Option>, painter: &mut egui_wgpu::winit::Painter, ) -> &'a mut Viewport { use std::collections::btree_map::Entry; diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 8a10a90ef..83b2cb855 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -273,8 +273,13 @@ impl AppRunner { self.app.raw_input_hook(&self.egui_ctx, &mut raw_input); - let full_output = self.egui_ctx.run(raw_input, |egui_ctx| { - self.app.update(egui_ctx, &mut self.frame); + let full_output = self.egui_ctx.run_ui(raw_input, |ui| { + self.app.logic(ui.ctx(), &mut self.frame); + + #[expect(deprecated)] + self.app.update(ui.ctx(), &mut self.frame); + + self.app.ui(ui, &mut self.frame); }); let egui::FullOutput { platform_output, @@ -331,7 +336,7 @@ impl AppRunner { } if let Err(err) = self.painter.paint_and_update_textures( - self.app.clear_color(&self.egui_ctx.style().visuals), + self.app.clear_color(&self.egui_ctx.global_style().visuals), &clipped_primitives, self.egui_ctx.pixels_per_point(), &textures_delta, diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index c61a80012..88bedab35 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -196,11 +196,13 @@ pub(crate) fn on_keydown(event: web_sys::KeyboardEvent, runner: &mut AppRunner) let prevent_default = should_prevent_default_for_key(runner, &modifiers, egui_key); - // log::debug!( - // "On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}", - // event.key().as_str(), - // runner.egui_ctx().wants_keyboard_input() - // ); + if false { + log::debug!( + "On keydown {:?} {egui_key:?}, has_focus: {has_focus}, egui_wants_keyboard: {}, prevent_default: {prevent_default}", + event.key().as_str(), + runner.egui_ctx().egui_wants_keyboard_input() + ); + } if prevent_default { event.prevent_default(); diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index efecd12ee..387366e5a 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -243,13 +243,26 @@ impl WebPainter for WebPainterWgpu { depth_stencil_attachment: self.depth_texture_view.as_ref().map(|view| { wgpu::RenderPassDepthStencilAttachment { view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - // It is very unlikely that the depth buffer is needed after egui finished rendering - // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) - store: wgpu::StoreOp::Discard, - }), - stencil_ops: None, + depth_ops: self + .depth_stencil_format + .is_some_and(|depth_stencil_format| { + depth_stencil_format.has_depth_aspect() + }) + .then_some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + // It is very unlikely that the depth buffer is needed after egui finished rendering + // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) + store: wgpu::StoreOp::Discard, + }), + stencil_ops: self + .depth_stencil_format + .is_some_and(|depth_stencil_format| { + depth_stencil_format.has_stencil_aspect() + }) + .then_some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0), + store: wgpu::StoreOp::Discard, + }), } }), label: Some("egui_render"), diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index be00dd049..cef640184 100644 --- a/crates/egui-wgpu/CHANGELOG.md +++ b/crates/egui-wgpu/CHANGELOG.md @@ -6,6 +6,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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 * Fix jittering during window resize on MacOS for WGPU/Metal [#7641](https://github.com/emilk/egui/pull/7641) by [@aspcartman](https://github.com/aspcartman) diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 3a286cc9e..a35466493 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -526,13 +526,28 @@ impl Painter { depth_stencil_attachment: self.depth_texture_view.get(&viewport_id).map(|view| { wgpu::RenderPassDepthStencilAttachment { view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - // It is very unlikely that the depth buffer is needed after egui finished rendering - // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) - store: wgpu::StoreOp::Discard, - }), - stencil_ops: None, + depth_ops: self + .options + .depth_stencil_format + .is_some_and(|depth_stencil_format| { + depth_stencil_format.has_depth_aspect() + }) + .then_some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + // It is very unlikely that the depth buffer is needed after egui finished rendering + // so no need to store it. (this can improve performance on tiling GPUs like mobile chips or Apple Silicon) + store: wgpu::StoreOp::Discard, + }), + stencil_ops: self + .options + .depth_stencil_format + .is_some_and(|depth_stencil_format| { + depth_stencil_format.has_stencil_aspect() + }) + .then_some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0), + store: wgpu::StoreOp::Discard, + }), } }), timestamp_writes: None, diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index 8e555a7bc..f88a8c84a 100644 --- a/crates/egui-winit/CHANGELOG.md +++ b/crates/egui-winit/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.33.3 - 2025-12-11 +Nothing new + + ## 0.33.2 - 2025-11-13 * Don't enable `arboard` on iOS [#7663](https://github.com/emilk/egui/pull/7663) by [@irh](https://github.com/irh) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 7660e3cef..6663ba40f 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -309,21 +309,21 @@ impl State { self.on_mouse_button_input(*state, *button); EventResponse { repaint: true, - consumed: self.egui_ctx.wants_pointer_input(), + consumed: self.egui_ctx.egui_wants_pointer_input(), } } WindowEvent::MouseWheel { delta, phase, .. } => { self.on_mouse_wheel(window, *delta, *phase); EventResponse { repaint: true, - consumed: self.egui_ctx.wants_pointer_input(), + consumed: self.egui_ctx.egui_wants_pointer_input(), } } WindowEvent::CursorMoved { position, .. } => { self.on_cursor_moved(window, *position); EventResponse { repaint: true, - consumed: self.egui_ctx.is_using_pointer(), + consumed: self.egui_ctx.egui_is_using_pointer(), } } WindowEvent::CursorLeft { .. } => { @@ -340,8 +340,10 @@ impl State { let consumed = match touch.phase { winit::event::TouchPhase::Started | winit::event::TouchPhase::Ended - | winit::event::TouchPhase::Cancelled => self.egui_ctx.wants_pointer_input(), - winit::event::TouchPhase::Moved => self.egui_ctx.is_using_pointer(), + | winit::event::TouchPhase::Cancelled => { + self.egui_ctx.egui_wants_pointer_input() + } + winit::event::TouchPhase::Moved => self.egui_ctx.egui_is_using_pointer(), }; EventResponse { repaint: true, @@ -392,7 +394,7 @@ impl State { EventResponse { repaint: true, - consumed: self.egui_ctx.wants_keyboard_input(), + consumed: self.egui_ctx.egui_wants_keyboard_input(), } } WindowEvent::KeyboardInput { @@ -413,7 +415,7 @@ impl State { self.on_keyboard_input(event); // When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes. - let consumed = self.egui_ctx.wants_keyboard_input() + let consumed = self.egui_ctx.egui_wants_keyboard_input() || event.logical_key == winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab); EventResponse { @@ -528,7 +530,7 @@ impl State { self.egui_input.events.push(egui::Event::Zoom(zoom_factor)); EventResponse { repaint: true, - consumed: self.egui_ctx.wants_pointer_input(), + consumed: self.egui_ctx.egui_wants_pointer_input(), } } @@ -541,7 +543,7 @@ impl State { .push(egui::Event::Rotate(-delta.to_radians())); EventResponse { repaint: true, - consumed: self.egui_ctx.wants_pointer_input(), + consumed: self.egui_ctx.egui_wants_pointer_input(), } } @@ -556,7 +558,7 @@ impl State { }); EventResponse { repaint: true, - consumed: self.egui_ctx.wants_pointer_input(), + consumed: self.egui_ctx.egui_wants_pointer_input(), } } } diff --git a/crates/egui/src/atomics/atom_kind.rs b/crates/egui/src/atomics/atom_kind.rs index 3c54c496b..10ca3353b 100644 --- a/crates/egui/src/atomics/atom_kind.rs +++ b/crates/egui/src/atomics/atom_kind.rs @@ -82,7 +82,7 @@ impl<'a> AtomKind<'a> { ) -> (Vec2, SizedAtomKind<'a>) { match self { AtomKind::Text(text) => { - let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); + let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode()); let galley = text.into_galley(ui, Some(wrap_mode), available_size.x, fallback_font); (galley.intrinsic_size(), SizedAtomKind::Text(galley)) } diff --git a/crates/egui/src/atomics/atom_layout.rs b/crates/egui/src/atomics/atom_layout.rs index 1df890250..8132a7dc9 100644 --- a/crates/egui/src/atomics/atom_layout.rs +++ b/crates/egui/src/atomics/atom_layout.rs @@ -168,7 +168,7 @@ impl<'a> AtomLayout<'a> { let fallback_font = fallback_font.unwrap_or_default(); - let wrap_mode = wrap_mode.unwrap_or(ui.wrap_mode()); + let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode()); // If the TextWrapMode is not Extend, ensure there is some item marked as `shrink`. // If none is found, mark the first text item as `shrink`. @@ -188,7 +188,7 @@ impl<'a> AtomLayout<'a> { let fallback_text_color = fallback_text_color.unwrap_or_else(|| ui.style().visuals.text_color()); - let gap = gap.unwrap_or(ui.spacing().icon_spacing); + let gap = gap.unwrap_or_else(|| ui.spacing().icon_spacing); // The size available for the content let available_inner_size = ui.available_size() - frame.total_margin().sum(); diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 3ebac1d65..35afab35a 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -469,7 +469,7 @@ impl Area { // during the sizing pass we will use this as the max size let mut size = default_size; - let default_area_size = ctx.style().spacing.default_area_size; + let default_area_size = ctx.global_style().spacing.default_area_size; if size.x.is_nan() { size.x = default_area_size.x; } @@ -634,7 +634,8 @@ impl Prepared { { let age = ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0); - let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0); + let opacity = + crate::remap_clamp(age, 0.0..=ctx.global_style().animation_time, 0.0..=1.0); let opacity = emath::easing::quadratic_out(opacity); // slow fade-out = quick fade-in ui.multiply_opacity(opacity); if opacity < 1.0 { @@ -711,7 +712,7 @@ fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 { // NOTE: for the benefit of the egui demo, we position the windows so they don't // cover the side panels, which means we use `available_rect` here instead of `constrain_rect` or `screen_rect`. - let available_rect = ctx.available_rect(); + let available_rect = ctx.globally_available_rect(); let spacing = 16.0; let left = available_rect.left() + spacing; diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index 3afb77682..aca8ab138 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -69,7 +69,7 @@ impl CollapsingState { pub fn toggle(&mut self, ui: &Ui) { self.state.open = !self.state.open; - ui.ctx().request_repaint(); + ui.request_repaint(); } /// 0 for closed, 1 for open, with tweening diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index 756d68dd3..9af76d299 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -207,7 +207,7 @@ impl MenuState { /// egui::MenuBar::new().ui(ui, |ui| { /// ui.menu_button("File", |ui| { /// if ui.button("Quit").clicked() { -/// ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); +/// ui.send_viewport_cmd(egui::ViewportCommand::Close); /// } /// }); /// }); @@ -556,7 +556,7 @@ impl SubMenu { if is_moving_towards_rect { // We need to repaint while this is true, so we can detect when // the pointer is no longer moving towards the rect - ui.ctx().request_repaint(); + ui.request_repaint(); } let hovering_other_menu_entry = is_open && !is_hovered diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index eb60f5f21..6ed34fd7c 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -291,13 +291,13 @@ impl<'a> PanelSizer<'a> { /// See the [module level docs](crate::containers::panel) for more details. /// /// ``` -/// # egui::__run_test_ctx(|ctx| { -/// egui::Panel::left("my_left_panel").show(ctx, |ui| { +/// # egui::__run_test_ui(|ui| { +/// egui::Panel::left("my_left_panel").show_inside(ui, |ui| { /// ui.label("Hello World!"); /// }); /// # }); /// ``` -#[must_use = "You should call .show()"] +#[must_use = "You should call .show_inside()"] pub struct Panel { side: PanelSide, id: Id, @@ -518,6 +518,7 @@ impl Panel { } /// Show the panel at the top level. + #[deprecated = "Use show_inside() instead"] pub fn show( self, ctx: &Context, @@ -528,12 +529,15 @@ impl Panel { /// Show the panel if `is_expanded` is `true`, /// otherwise don't show it, but with a nice animation between collapsed and expanded. + #[deprecated = "Use show_animated_inside() instead"] pub fn show_animated( self, ctx: &Context, is_expanded: bool, add_contents: impl FnOnce(&mut Ui) -> R, ) -> Option> { + #![expect(deprecated)] + let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); let animated_panel = self.get_animated_panel(ctx, is_expanded)?; @@ -572,6 +576,7 @@ impl Panel { } /// Show either a collapsed or a expanded panel, with a nice animation between. + #[deprecated = "Use show_animated_between_inside() instead"] pub fn show_animated_between( ctx: &Context, is_expanded: bool, @@ -579,6 +584,8 @@ impl Panel { expanded_panel: Self, add_contents: impl FnOnce(&mut Ui, f32) -> R, ) -> Option> { + #![expect(deprecated)] + let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); // Get either the fake or the real panel to animate @@ -713,7 +720,7 @@ impl Panel { } if resize_hover || is_resizing { - ui.ctx().set_cursor_icon(self.cursor_icon(&panel_sizer)); + ui.set_cursor_icon(self.cursor_icon(&panel_sizer)); } PanelState { rect }.store(ui.ctx(), id); @@ -754,7 +761,7 @@ impl Panel { add_contents: Box R + 'c>, ) -> InnerResponse { let side = self.side; - let available_rect = ctx.available_rect(); + let available_rect = ctx.globally_available_rect(); let mut panel_ui = Ui::new( ctx.clone(), self.id, @@ -934,14 +941,14 @@ impl Panel { }; let get_spacing_size = || match panel.side { - PanelSide::Vertical(_) => ctx.style().spacing.interact_size.x, - PanelSide::Horizontal(_) => ctx.style().spacing.interact_size.y, + PanelSide::Vertical(_) => ctx.global_style().spacing.interact_size.x, + PanelSide::Horizontal(_) => ctx.global_style().spacing.interact_size.y, }; PanelState::load(ctx, panel.id) .map(get_rect_state_size) .or(panel.default_size) - .unwrap_or(get_spacing_size()) + .unwrap_or_else(get_spacing_size) } } @@ -950,6 +957,9 @@ impl Panel { /// A panel that covers the remainder of the screen, /// i.e. whatever area is left after adding other panels. /// +/// This acts very similar to [`Frame::central_panel`], but always expands +/// to use up all available space. +/// /// The order in which you add panels matter! /// The first panel you add will always be the outermost, and the last you add will always be the innermost. /// @@ -960,31 +970,41 @@ impl Panel { /// See the [module level docs](crate::containers::panel) for more details. /// /// ``` -/// # egui::__run_test_ctx(|ctx| { -/// egui::Panel::top("my_panel").show(ctx, |ui| { +/// # egui::__run_test_ui(|ui| { +/// egui::Panel::top("my_panel").show_inside(ui, |ui| { /// ui.label("Hello World! From `Panel`, that must be before `CentralPanel`!"); /// }); -/// egui::CentralPanel::default().show(ctx, |ui| { +/// egui::CentralPanel::default().show_inside(ui, |ui| { /// ui.label("Hello World!"); /// }); /// # }); /// ``` -#[must_use = "You should call .show()"] +#[must_use = "You should call .show_inside()"] #[derive(Default)] pub struct CentralPanel { frame: Option, } impl CentralPanel { + /// A central panel with no margin or background color + pub fn no_frame() -> Self { + Self { + frame: Some(Frame::NONE), + } + } + + /// A central panel with a background color and some inner margins + pub fn default_margins() -> Self { + Self { frame: None } + } + /// Change the background color, margins, etc. #[inline] pub fn frame(mut self, frame: Frame) -> Self { self.frame = Some(frame); self } -} -impl CentralPanel { /// Show the panel inside a [`Ui`]. pub fn show_inside( self, @@ -1012,10 +1032,15 @@ impl CentralPanel { panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475) let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style())); - frame.show(&mut panel_ui, |ui| { + let response = frame.show(&mut panel_ui, |ui| { ui.expand_to_include_rect(ui.max_rect()); // Expand frame to include it all add_contents(ui) - }) + }); + + // Use up space in the parent: + ui.advance_cursor_after_rect(response.response.rect); + + response } /// Show the panel at the top level. @@ -1040,7 +1065,7 @@ impl CentralPanel { id, UiBuilder::new() .layer_id(LayerId::background()) - .max_rect(ctx.available_rect().round_ui()), + .max_rect(ctx.globally_available_rect().round_ui()), ); panel_ui.set_clip_rect(ctx.content_rect()); diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index a9c00661d..0fb2a9f2a 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -465,7 +465,7 @@ impl<'a> Popup<'a> { pub fn get_best_align(&self) -> RectAlign { let expected_popup_size = self .get_expected_size() - .unwrap_or(vec2(self.width.unwrap_or(0.0), 0.0)); + .unwrap_or_else(|| vec2(self.width.unwrap_or(0.0), 0.0)); let Some(anchor_rect) = self.anchor.rect(self.id, &self.ctx) else { return self.rect_align; @@ -473,6 +473,7 @@ impl<'a> Popup<'a> { RectAlign::find_best_align( #[expect(clippy::iter_on_empty_collections)] + #[expect(clippy::or_fun_call)] once(self.rect_align).chain( self.alternative_aligns // Need the empty slice so the iters have the same type so we can unwrap_or diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 50cc28774..7ff943b3f 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -213,7 +213,7 @@ impl Resize { }); let mut state = State::load(ui.ctx(), id).unwrap_or_else(|| { - ui.ctx().request_repaint(); // counter frame delay + ui.request_repaint(); // counter frame delay let default_size = self .default_size @@ -362,20 +362,20 @@ impl Resize { paint_resize_corner(ui, &corner_response); if corner_response.hovered() || corner_response.dragged() { - ui.ctx().set_cursor_icon(CursorIcon::ResizeNwSe); + ui.set_cursor_icon(CursorIcon::ResizeNwSe); } } state.store(ui.ctx(), id); #[cfg(debug_assertions)] - if ui.ctx().style().debug.show_resize { - ui.ctx().debug_painter().debug_rect( + if ui.global_style().debug.show_resize { + ui.debug_painter().debug_rect( Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size), Color32::GREEN, "desired_size", ); - ui.ctx().debug_painter().debug_rect( + ui.debug_painter().debug_rect( Rect::from_min_size(content_ui.min_rect().left_top(), state.last_content_size), Color32::LIGHT_BLUE, "last_content_size", diff --git a/crates/egui/src/containers/scene.rs b/crates/egui/src/containers/scene.rs index 36222b138..093452289 100644 --- a/crates/egui/src/containers/scene.rs +++ b/crates/egui/src/containers/scene.rs @@ -244,8 +244,8 @@ impl Scene { && resp.contains_pointer() { let pointer_in_scene = to_global.inverse() * mouse_pos; - let zoom_delta = ui.ctx().input(|i| i.zoom_delta()); - let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta()); + let zoom_delta = ui.input(|i| i.zoom_delta()); + let pan_delta = ui.input(|i| i.smooth_scroll_delta()); // Most of the time we can return early. This is also important to // avoid `ui_from_scene` to change slightly due to floating point errors. diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 5ed4c31f3..6e6fb18b8 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -856,11 +856,11 @@ impl ScrollArea { if response.dragged() && let Some(cursor) = on_drag_cursor { - ui.ctx().set_cursor_icon(cursor); + ui.set_cursor_icon(cursor); } else if response.hovered() && let Some(cursor) = on_hover_cursor { - ui.ctx().set_cursor_icon(cursor); + ui.set_cursor_icon(cursor); } } @@ -1125,7 +1125,7 @@ impl Prepared { target_offset, }); } - ui.ctx().request_repaint(); + ui.request_repaint(); } } } @@ -1178,7 +1178,7 @@ impl Prepared { && direction_enabled[0] != direction_enabled[1]; for d in 0..2 { if direction_enabled[d] { - let scroll_delta = ui.ctx().input(|input| { + let scroll_delta = ui.input(|input| { if always_scroll_enabled_direction { // no bidirectional scrolling; allow horizontal scrolling without pressing shift input.smooth_scroll_delta()[0] + input.smooth_scroll_delta()[1] @@ -1195,7 +1195,7 @@ impl Prepared { state.offset[d] -= scroll_delta; // Clear scroll delta so no parent scroll will use it: - ui.ctx().input_mut(|input| { + ui.input_mut(|input| { if always_scroll_enabled_direction { input.smooth_scroll_delta()[0] = 0.0; input.smooth_scroll_delta()[1] = 0.0; @@ -1475,7 +1475,7 @@ impl Prepared { ui.advance_cursor_after_rect(outer_rect); if show_scroll_this_frame != state.show_scroll { - ui.ctx().request_repaint(); + ui.request_repaint(); } let available_offset = content_size - inner_rect.size(); diff --git a/crates/egui/src/containers/tooltip.rs b/crates/egui/src/containers/tooltip.rs index c46e21d57..78c5a726b 100644 --- a/crates/egui/src/containers/tooltip.rs +++ b/crates/egui/src/containers/tooltip.rs @@ -41,7 +41,7 @@ impl Tooltip<'_> { parent_widget: Id, anchor: impl Into, ) -> Self { - let width = ctx.style().spacing.tooltip_width; + let width = ctx.global_style().spacing.tooltip_width; Self { popup: Popup::new(parent_widget, ctx, anchor.into(), parent_layer) .kind(PopupKind::Tooltip) @@ -58,7 +58,7 @@ impl Tooltip<'_> { let popup = Popup::from_response(response) .kind(PopupKind::Tooltip) .gap(4.0) - .width(response.ctx.style().spacing.tooltip_width) + .width(response.ctx.global_style().spacing.tooltip_width) .sense(Sense::hover()); Self { popup, @@ -229,7 +229,7 @@ impl Tooltip<'_> { return false; } - let style = response.ctx.style(); + let style = response.ctx.global_style(); let tooltip_delay = style.interaction.tooltip_delay; let tooltip_grace_time = style.interaction.tooltip_grace_time; diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 8d7a95be7..2ea949e88 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -70,6 +70,41 @@ impl<'open> Window<'open> { } } + /// Construct a [`Window`] that follows the given viewport. + pub fn from_viewport(id: ViewportId, viewport: ViewportBuilder) -> Self { + let ViewportBuilder { + title, + app_id, + inner_size, + min_inner_size, + max_inner_size, + resizable, + decorations, + title_shown, + minimize_button, + .. // A lot of things not implemented yet + } = viewport; + + let mut window = Self::new(title.or(app_id).unwrap_or_else(String::new)).id(Id::new(id)); + + if let Some(inner_size) = inner_size { + window = window.default_size(inner_size); + } + if let Some(min_inner_size) = min_inner_size { + window = window.min_size(min_inner_size); + } + if let Some(max_inner_size) = max_inner_size { + window = window.max_size(max_inner_size); + } + if let Some(resizable) = resizable { + window = window.resizable(resizable); + } + window = window.title_bar(decorations.unwrap_or(true) && title_shown.unwrap_or(true)); + window = window.collapsible(minimize_button.unwrap_or(true)); + + window + } + /// Assign a unique id to the Window. Required if the title changes, or is shared with another window. #[inline] pub fn id(mut self, id: Id) -> Self { @@ -440,9 +475,11 @@ impl Window<'_> { fade_out, } = self; - let header_color = - frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill); - let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); + let header_color = frame.map_or_else( + || ctx.global_style().visuals.widgets.open.weak_bg_fill, + |f| f.fill, + ); + let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.global_style())); let is_explicitly_closed = matches!(open, Some(false)); let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); @@ -474,7 +511,7 @@ impl Window<'_> { // Calculate roughly how much larger the full window inner size is compared to the content rect let (title_bar_height_with_margin, title_content_spacing) = if with_title_bar { - let style = ctx.style(); + let style = ctx.global_style(); let title_bar_inner_height = ctx .fonts_mut(|fonts| title.font_height(fonts, &style)) .at_least(style.spacing.interact_size.y); @@ -930,8 +967,8 @@ fn resize_interaction( let id = Id::new(layer_id).with("edge_drag"); - let side_grab_radius = ctx.style().interaction.resize_grab_radius_side; - let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner; + let side_grab_radius = ctx.global_style().interaction.resize_grab_radius_side; + let corner_grab_radius = ctx.global_style().interaction.resize_grab_radius_corner; let vetrtical_rect = |a: Pos2, b: Pos2| { Rect::from_min_max(a, b).expand2(vec2(side_grab_radius, -corner_grab_radius)) @@ -1141,8 +1178,7 @@ impl TitleBar { title_bar_height_with_margin: f32, ) -> Self { if false { - ui.ctx() - .debug_painter() + ui.debug_painter() .debug_rect(ui.min_rect(), Color32::GREEN, "outer_min_rect"); } @@ -1170,8 +1206,7 @@ impl TitleBar { let min_rect = Rect::from_min_size(ui.min_rect().min, min_inner_size); if false { - ui.ctx() - .debug_painter() + ui.debug_painter() .debug_rect(min_rect, Color32::LIGHT_BLUE, "min_rect"); } @@ -1208,8 +1243,7 @@ impl TitleBar { let title_inner_rect = self.inner_rect; if false { - ui.ctx() - .debug_painter() + ui.debug_painter() .debug_rect(self.inner_rect, Color32::RED, "TitleBar"); } @@ -1248,8 +1282,7 @@ impl TitleBar { // Paint separator between title and content: let content_rect = content_response.rect; if false { - ui.ctx() - .debug_painter() + ui.debug_painter() .debug_rect(content_rect, Color32::RED, "content_rect"); } let y = title_inner_rect.bottom() + window_frame.stroke.width / 2.0; @@ -1263,11 +1296,8 @@ impl TitleBar { let double_click_rect = title_inner_rect.shrink2(vec2(32.0, 0.0)); if false { - ui.ctx().debug_painter().debug_rect( - double_click_rect, - Color32::GREEN, - "double_click_rect", - ); + ui.debug_painter() + .debug_rect(double_click_rect, Color32::GREEN, "double_click_rect"); } let id = ui.unique_id().with("__window_title_bar"); diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 2ca7af4fc..67abb0556 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -20,7 +20,7 @@ use crate::{ ModifierNames, Modifiers, NumExt as _, Order, Painter, RawInput, Response, RichText, SafeAreaInsets, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, - ViewportOutput, Widget as _, WidgetRect, WidgetText, + ViewportOutput, Visuals, Widget as _, WidgetRect, WidgetText, animation_manager::AnimationManager, containers::{self, area::AreaState}, data::output::PlatformOutput, @@ -34,8 +34,7 @@ use crate::{ os::OperatingSystem, output::FullOutput, pass_state::PassState, - plugin, - plugin::TypedPluginHandle, + plugin::{self, TypedPluginHandle}, resize, response, scroll_area, util::IdTypeMap, viewport::ViewportClass, @@ -194,7 +193,7 @@ impl ContextImpl { pub struct ViewportState { /// The type of viewport. /// - /// This will never be [`ViewportClass::Embedded`], + /// This will never be [`ViewportClass::EmbeddedWindow`], /// since those don't result in real viewports. pub class: ViewportClass, @@ -564,7 +563,10 @@ impl ContextImpl { log::trace!("Adding new fonts"); } - let text_alpha_from_coverage = self.memory.options.style().visuals.text_alpha_from_coverage; + let Visuals { + mut text_options, .. + } = self.memory.options.style().visuals; + text_options.max_texture_side = max_texture_side; let mut is_new = false; @@ -573,16 +575,12 @@ impl ContextImpl { is_new = true; profiling::scope!("Fonts::new"); - Fonts::new( - max_texture_side, - text_alpha_from_coverage, - self.font_definitions.clone(), - ) + Fonts::new(text_options, self.font_definitions.clone()) }); { profiling::scope!("Fonts::begin_pass"); - fonts.begin_pass(max_texture_side, text_alpha_from_coverage); + fonts.begin_pass(text_options); } } @@ -611,7 +609,7 @@ impl ContextImpl { } let parent_id = find_accesskit_parent(&state.parent_map, builders, id) - .unwrap_or(crate::accesskit_root_id()); + .unwrap_or_else(crate::accesskit_root_id); let parent_builder = builders.get_mut(&parent_id).unwrap(); parent_builder.push_child(id.accesskit_id()); @@ -758,6 +756,51 @@ impl Context { writer(&mut self.0.write()) } + /// Run the ui code for one frame. + /// + /// At most [`Options::max_passes`] calls will be issued to `run_ui`, + /// and only on the rare occasion that [`Context::request_discard`] is called. + /// Usually, it `run_ui` will only be called once. + /// + /// The [`Ui`] given to the callback will cover the entire [`Self::content_rect`], + /// with no margin or background color. Use [`crate::Frame`] to add that. + /// + /// You can organize your GUI using [`crate::Panel`]. + /// + /// Instead of calling `run_ui`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`]. + /// + /// ``` + /// // One egui context that you keep reusing: + /// let mut ctx = egui::Context::default(); + /// + /// // Each frame: + /// let input = egui::RawInput::default(); + /// let full_output = ctx.run_ui(input, |ui| { + /// ui.label("Hello egui!"); + /// }); + /// // handle full_output + /// ``` + /// + /// ## See also + /// * [`Self::run`] + #[must_use] + pub fn run_ui(&self, new_input: RawInput, mut run_ui: impl FnMut(&mut Ui)) -> FullOutput { + self.run_ui_dyn(new_input, &mut run_ui) + } + + #[must_use] + fn run_ui_dyn(&self, new_input: RawInput, run_ui: &mut dyn FnMut(&mut Ui)) -> FullOutput { + let plugins = self.read(|ctx| ctx.plugins.ordered_plugins()); + #[expect(deprecated)] + self.run(new_input, |ctx| { + crate::CentralPanel::no_frame().show(ctx, |ui| { + plugins.on_begin_pass(ui); + run_ui(ui); + plugins.on_end_pass(ui); + }); + }) + } + /// Run the ui code for one frame. /// /// At most [`Options::max_passes`] calls will be issued to `run_ui`, @@ -781,8 +824,17 @@ impl Context { /// }); /// // handle full_output /// ``` + /// + /// ## See also + /// * [`Self::run_ui`] #[must_use] - pub fn run(&self, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput { + #[deprecated = "Call run_ui instead"] + pub fn run(&self, new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput { + self.run_dyn(new_input, &mut run_ui) + } + + #[must_use] + fn run_dyn(&self, mut new_input: RawInput, run_ui: &mut dyn FnMut(&Self)) -> FullOutput { profiling::function_scope!(); let viewport_id = new_input.viewport_id; let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get()); @@ -877,9 +929,6 @@ impl Context { plugins.on_input(&mut new_input); self.write(|ctx| ctx.begin_pass(new_input)); - - // Plugins run just after the pass starts: - plugins.on_begin_pass(self); } /// See [`Self::begin_pass`]. @@ -1101,7 +1150,7 @@ impl Context { let content_rect = self.content_rect(); let text = format!("🔥 {text}"); - let color = self.style().visuals.error_fg_color; + let color = self.global_style().visuals.error_fg_color; let painter = self.debug_painter(); painter.rect_stroke(widget_rect, 0.0, (1.0, color), StrokeKind::Outside); @@ -1458,7 +1507,7 @@ impl Context { Painter::new(self.clone(), layer_id, content_rect) } - /// Paint on top of everything else + /// Paint on top of _everything_ else (even on top of tooltips and popups). pub fn debug_painter(&self) -> Painter { Self::layer_painter(self, LayerId::debug()) } @@ -1556,7 +1605,7 @@ impl Context { .. } = ModifierNames::SYMBOLS; - let font_id = TextStyle::Body.resolve(&self.style()); + let font_id = TextStyle::Body.resolve(&self.global_style()); self.fonts_mut(|f| { let mut font = f.fonts.font(&font_id.family); font.has_glyphs(alt) @@ -1957,15 +2006,12 @@ impl Context { pub fn set_fonts(&self, font_definitions: FontDefinitions) { profiling::function_scope!(); - let mut update_fonts = true; - - self.read(|ctx| { - if let Some(current_fonts) = ctx.fonts.as_ref() { - // NOTE: this comparison is expensive since it checks TTF data for equality - if current_fonts.definitions() == &font_definitions { - update_fonts = false; // no need to update - } - } + let update_fonts = self.read(|ctx| { + // NOTE: this comparison is expensive since it checks TTF data for equality + // TODO(valadaptive): add_font only checks the *names* for equality. Change this? + ctx.fonts + .as_ref() + .is_none_or(|fonts| fonts.definitions() != &font_definitions) }); if update_fonts { @@ -2008,13 +2054,13 @@ impl Context { } /// The [`Theme`] used to select the appropriate [`Style`] (dark or light) - /// used by all subsequent windows, panels etc. + /// used by all subsequent popups, menus, etc. pub fn theme(&self) -> Theme { self.options(|opt| opt.theme()) } /// The [`Theme`] used to select between dark and light [`Self::style`] - /// as the active style used by all subsequent windows, panels etc. + /// as the active style used by all subsequent popups, menus, etc. /// /// Example: /// ``` @@ -2025,37 +2071,70 @@ impl Context { self.options_mut(|opt| opt.theme_preference = theme_preference.into()); } - /// The currently active [`Style`] used by all subsequent windows, panels etc. + /// The currently active [`Style`] used by all subsequent popups, menus, etc. + pub fn global_style(&self) -> Arc