diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 85a059160..f71588545 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -46,7 +46,9 @@ jobs: - run: cargo clippy --locked --no-default-features --lib --all-targets - - run: cargo clippy --locked --no-default-features --features x11 --lib -p eframe + - run: cargo clippy --locked --no-default-features --lib -p eframe --features x11 + + - run: cargo clippy --locked --no-default-features --lib -p eframe --features x11,wgpu_no_default_features - run: cargo clippy --locked --no-default-features --lib -p egui_extras @@ -87,7 +89,7 @@ jobs: run: cargo clippy -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features - name: clippy wasm32 eframe - run: cargo clippy -p eframe --lib --no-default-features --features glow,persistence --target wasm32-unknown-unknown + run: cargo clippy -p eframe --lib --no-default-features --features wgpu,persistence --target wasm32-unknown-unknown - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 51d6d41d1..be98b3308 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -5,7 +5,7 @@ Also see [`CONTRIBUTING.md`](CONTRIBUTING.md) for what to do before opening a PR ## Crate overview -The crates in this repository are: `egui, emath, epaint, epaint_default_fonts, egui_extras, egui-winit, egui_glow, egui_demo_lib, egui_demo_app`. +The crates in this repository are: `egui, emath, epaint, epaint_default_fonts, egui_extras, egui-winit, egui_glow, egui-wgpu, egui_demo_lib, egui_demo_app`. ### `egui`: The main GUI library. Example code: `if ui.button("Click me").clicked() { … }` @@ -37,6 +37,9 @@ The library translates winit events to egui, handled copy/paste, updates the cur ### `egui_glow` Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glow](https://github.com/grovesNL/glow). +### `egui-wgpu` +Paints the triangles that egui outputs using [wgpu](https://github.com/grovesNL/wgpu). + ### `eframe` `eframe` is the official `egui` framework, built so you can compile the same app for either web or native. diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc31e57b..856fe09da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,22 @@ 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.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) +* Add `Response::total_drag_delta` and `PointerState::total_drag_delta` [#7708](https://github.com/emilk/egui/pull/7708) by [@emilk](https://github.com/emilk) + +### πŸ”§ Changed +* Improve accessibility and testability of `ComboBox` [#7658](https://github.com/emilk/egui/pull/7658) by [@lucasmerlin](https://github.com/lucasmerlin) + +### πŸ› Fixed +* Fix `profiling::scope` compile error when profiling using `tracing` backend [#7646](https://github.com/emilk/egui/pull/7646) by [@PPakalns](https://github.com/PPakalns) +* Fix edge cases in "smart aiming" in sliders [#7680](https://github.com/emilk/egui/pull/7680) by [@emilk](https://github.com/emilk) +* Hide scroll bars when dragging other things [#7689](https://github.com/emilk/egui/pull/7689) by [@emilk](https://github.com/emilk) +* Prevent widgets sometimes appearing to move relative to each other [#7710](https://github.com/emilk/egui/pull/7710) by [@emilk](https://github.com/emilk) +* Fix `ui.response().interact(Sense::click())` being flakey [#7713](https://github.com/emilk/egui/pull/7713) by [@lucasmerlin](https://github.com/lucasmerlin) + + ## 0.33.0 - 2025-10-09 - `egui::Plugin`, better kerning, kitdiff viewer Highlights from this release: - `egui::Plugin` a improved way to create and access egui plugins diff --git a/Cargo.lock b/Cargo.lock index cecaf6935..61bf459b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1248,7 +1248,7 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "ecolor" -version = "0.33.0" +version = "0.33.2" dependencies = [ "bytemuck", "cint", @@ -1260,7 +1260,7 @@ dependencies = [ [[package]] name = "eframe" -version = "0.33.0" +version = "0.33.2" dependencies = [ "ahash", "bytemuck", @@ -1299,7 +1299,7 @@ dependencies = [ [[package]] name = "egui" -version = "0.33.0" +version = "0.33.2" dependencies = [ "accesskit", "ahash", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.33.0" +version = "0.33.2" dependencies = [ "ahash", "bytemuck", @@ -1337,7 +1337,7 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.33.0" +version = "0.33.2" dependencies = [ "accesskit_winit", "arboard", @@ -1360,7 +1360,7 @@ dependencies = [ [[package]] name = "egui_demo_app" -version = "0.33.0" +version = "0.33.2" dependencies = [ "accesskit", "accesskit_consumer", @@ -1390,7 +1390,7 @@ dependencies = [ [[package]] name = "egui_demo_lib" -version = "0.33.0" +version = "0.33.2" dependencies = [ "chrono", "criterion", @@ -1407,7 +1407,7 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.33.0" +version = "0.33.2" dependencies = [ "ahash", "chrono", @@ -1426,7 +1426,7 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.33.0" +version = "0.33.2" dependencies = [ "bytemuck", "document-features", @@ -1445,7 +1445,7 @@ dependencies = [ [[package]] name = "egui_kittest" -version = "0.33.1" +version = "0.33.2" dependencies = [ "dify", "document-features", @@ -1463,7 +1463,7 @@ dependencies = [ [[package]] name = "egui_tests" -version = "0.33.0" +version = "0.33.2" dependencies = [ "egui", "egui_extras", @@ -1493,7 +1493,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "emath" -version = "0.33.0" +version = "0.33.2" dependencies = [ "bytemuck", "document-features", @@ -1591,7 +1591,7 @@ dependencies = [ [[package]] name = "epaint" -version = "0.33.0" +version = "0.33.2" dependencies = [ "ab_glyph", "ahash", @@ -1613,7 +1613,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.33.0" +version = "0.33.2" [[package]] name = "equivalent" @@ -3425,7 +3425,7 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "popups" -version = "0.33.0" +version = "0.33.2" dependencies = [ "eframe", "env_logger", @@ -5748,7 +5748,7 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "xtask" -version = "0.33.0" +version = "0.33.2" [[package]] name = "yaml-rust" diff --git a/Cargo.toml b/Cargo.toml index dfdb595c3..b33ca445c 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.0" +version = "0.33.2" [profile.release] @@ -55,18 +55,18 @@ opt-level = 2 [workspace.dependencies] -emath = { version = "0.33.0", path = "crates/emath", default-features = false } -ecolor = { version = "0.33.0", path = "crates/ecolor", default-features = false } -epaint = { version = "0.33.0", path = "crates/epaint", default-features = false } -epaint_default_fonts = { version = "0.33.0", path = "crates/epaint_default_fonts" } -egui = { version = "0.33.0", path = "crates/egui", default-features = false } -egui-winit = { version = "0.33.0", path = "crates/egui-winit", default-features = false } -egui_extras = { version = "0.33.0", path = "crates/egui_extras", default-features = false } -egui-wgpu = { version = "0.33.0", path = "crates/egui-wgpu", default-features = false } -egui_demo_lib = { version = "0.33.0", path = "crates/egui_demo_lib", default-features = false } -egui_glow = { version = "0.33.0", path = "crates/egui_glow", default-features = false } -egui_kittest = { version = "0.33.1", path = "crates/egui_kittest", default-features = false } -eframe = { version = "0.33.0", path = "crates/eframe", default-features = false } +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 } accesskit = "0.21.1" accesskit_consumer = "0.30.1" @@ -232,6 +232,7 @@ iter_on_single_items = "warn" iter_over_hash_type = "warn" iter_without_into_iter = "warn" large_digit_groups = "warn" +large_futures = "warn" large_include_file = "warn" large_stack_arrays = "warn" large_stack_frames = "warn" @@ -329,6 +330,7 @@ unnecessary_semicolon = "warn" unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" unnested_or_patterns = "warn" +unused_async = "warn" unused_peekable = "warn" unused_rounding = "warn" unused_self = "warn" diff --git a/README.md b/README.md index c1d25f913..f4a094465 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/di To test the demo app locally, run `cargo run --release -p egui_demo_app`. -The native backend is [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run: +The native backend is [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu) (using [`wgpu`](https://crates.io/crates/wgpu)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run: `sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev` diff --git a/crates/ecolor/CHANGELOG.md b/crates/ecolor/CHANGELOG.md index 6996d838f..4dee81296 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.2 - 2025-11-13 +Nothing new + + ## 0.33.0 - 2025-10-09 * Align `Color32` to 4 bytes [#7318](https://github.com/emilk/egui/pull/7318) by [@anti-social](https://github.com/anti-social) * Make the `hex_color` macro `const` [#7444](https://github.com/emilk/egui/pull/7444) by [@YgorSouza](https://github.com/YgorSouza) diff --git a/crates/eframe/CHANGELOG.md b/crates/eframe/CHANGELOG.md index 142634d02..11f1c8fc9 100644 --- a/crates/eframe/CHANGELOG.md +++ b/crates/eframe/CHANGELOG.md @@ -7,6 +7,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.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) + + ## 0.33.0 - 2025-10-09 ### ⭐ Added * Add an option to limit the repaint rate in the web runner [#7482](https://github.com/emilk/egui/pull/7482) by [@s-nie](https://github.com/s-nie) diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index ce2103cc8..6924633f1 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -28,19 +28,15 @@ workspace = true default = [ "accesskit", "default_fonts", - "glow", "wayland", # Required for Linux support (including CI!) "web_screen_reader", + "wgpu", "winit/default", "x11", - "egui-wgpu?/fragile-send-sync-non-atomic-wasm", - # Let's enable some backends so that users can use `eframe` out-of-the-box - # without having to explicitly opt-in to backends - "egui-wgpu?/default", ] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). -accesskit = ["egui/accesskit", "egui-winit/accesskit"] +accesskit = ["egui-winit/accesskit"] # Allow crates to choose an android-activity backend via Winit # - It's important that most applications should not have to depend on android-activity directly, and can @@ -56,7 +52,11 @@ android-native-activity = ["egui-winit/android-native-activity"] ## If you plan on specifying your own fonts you may disable this feature. default_fonts = ["egui/default_fonts"] -## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow). +## Enable [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow). +## +## There is generally no need to enable both the `wgpu` and `glow` features, +## but if you do you can pick the renderer to use with [`NativeOptions::renderer`] +## and `WebOptions::renderer`. glow = ["dep:egui_glow", "dep:glow", "dep:glutin-winit", "dep:glutin"] ## Enable saving app state to disk. @@ -78,20 +78,28 @@ wayland = [ ## For other platforms, use the `accesskit` feature instead. web_screen_reader = ["web-sys/SpeechSynthesis", "web-sys/SpeechSynthesisUtterance"] -## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu)). +## Enable [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu)). ## -## This overrides the `glow` feature. +## There is generally no need to enable both the `wgpu` and `glow` features, +## but if you do you can pick the renderer to use with [`NativeOptions::renderer`] +## and `WebOptions::renderer`. ## -## By default, only WebGPU is enabled on web. -## If you want to enable WebGL, you need to turn on the `webgl` feature of crate `wgpu`: -## -## ```toml -## wgpu = { version = "*", features = ["webgpu", "webgl"] } -## ``` +## Switching from `wgpu (the default)` to `glow` can significantly reduce your binary size +## (including the .wasm of a web app). +## See for more details. ## ## By default, eframe will prefer WebGPU over WebGL, but ## you can configure this at run-time with [`NativeOptions::wgpu_options`]. -wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"] +wgpu = ["wgpu_no_default_features", "egui-wgpu/default"] + +## This is exactly like the `wgpu` feature, but does NOT enable the default features of `wgpu` and `egui-wgpu`. +## +## This means that no `wgpu` backends are enabled. You will need to enable them yourself, e.g. like this: +## +## ```toml +## wgpu = { version = "*", features = ["dx12", "metal", "webgl"] } +## ``` +wgpu_no_default_features = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster"] ## Enables compiling for x11. x11 = [ @@ -137,6 +145,7 @@ winit = { workspace = true, default-features = false, features = ["rwh_06"] } # optional native: egui-wgpu = { workspace = true, optional = true, features = [ "winit", + "capture", ] } # if wgpu is used, use it with winit pollster = { workspace = true, optional = true } # needed for wgpu @@ -242,7 +251,7 @@ web-sys = { workspace = true, features = [ ] } # optional web: -egui-wgpu = { workspace = true, optional = true } # if wgpu is used, use it without (!) winit +egui-wgpu = { workspace = true, optional = true, features = ["capture"] } # if wgpu is used, use it without (!) winit wgpu = { workspace = true, optional = true } # Native dev dependencies for testing diff --git a/crates/eframe/README.md b/crates/eframe/README.md index 9dbf42caf..63d5872c6 100644 --- a/crates/eframe/README.md +++ b/crates/eframe/README.md @@ -16,7 +16,7 @@ For how to use `egui`, see [the egui docs](https://docs.rs/egui). --- -`eframe` uses [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow) for rendering, and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/main/crates/egui-winit). +`eframe` defaults to using [wgpu](https://crates.io/crates/wgpu) for rendering (with an option to change to [glow](https://crates.io/crates/glow)), and on native it uses [`egui-winit`](https://github.com/emilk/egui/tree/main/crates/egui-winit). To use on Linux, first run: @@ -26,7 +26,7 @@ sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev lib You need to either use `edition = "2024"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info. -You can opt-in to the using [`egui-wgpu`](https://github.com/emilk/egui/tree/main/crates/egui-wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`. +You can opt-in to the using [`egui_glow`](https://github.com/emilk/egui/tree/main/crates/egui_glow) for rendering by enabling the `glow` feature and setting `NativeOptions::renderer` to `Renderer::Glow`. ## Alternatives `eframe` is not the only way to write an app using `egui`! You can also try [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad), [`bevy_egui`](https://github.com/mvlabat/bevy_egui), [`egui_sdl2_gl`](https://github.com/ArjunNair/egui_sdl2_gl), and others. @@ -35,7 +35,7 @@ You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/win ## Limitations when running egui on the web -`eframe` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and Wasm, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides. +`eframe` and egui compiles to Wasm using either WebGPU (when available) or WebGL2 for rendering, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides. * Rendering: Getting pixel-perfect rendering right on the web is very difficult. * Search: you cannot search an egui web page like you would a normal web page. diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index 384b8e918..a7bcfd6ef 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -10,7 +10,7 @@ use std::any::Any; #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use crate::native::winit_integration::UserEvent; #[cfg(not(target_arch = "wasm32"))] @@ -22,7 +22,7 @@ use raw_window_handle::{ use static_assertions::assert_not_impl_any; #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes}; /// Hook into the building of an event loop before it is run @@ -30,7 +30,7 @@ pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes}; /// You can configure any platform specific details required on top of the default configuration /// done by `EFrame`. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub type EventLoopBuilderHook = Box)>; /// Hook into the building of a the native window. @@ -38,7 +38,7 @@ pub type EventLoopBuilderHook = Box) /// You can configure any platform specific details required on top of the default configuration /// done by `eframe`. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub type WindowBuilderHook = Box egui::ViewportBuilder>; type DynError = Box; @@ -79,7 +79,7 @@ pub struct CreationContext<'s> { /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. /// /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] pub wgpu_render_state: Option, /// Raw platform window handle @@ -121,7 +121,7 @@ impl CreationContext<'_> { gl: None, #[cfg(feature = "glow")] get_proc_address: None, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: None, #[cfg(not(target_arch = "wasm32"))] raw_window_handle: Err(HandleError::NotSupported), @@ -137,7 +137,7 @@ impl CreationContext<'_> { pub trait App { /// Called each time the UI needs repainting, which may be many times per second. /// - /// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. + /// Put your widgets into a [`egui::Panel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. /// /// The [`egui::Context`] can be cloned and saved if you like. /// @@ -317,7 +317,7 @@ pub struct NativeOptions { pub hardware_acceleration: HardwareAcceleration, /// What rendering backend to use. - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub renderer: Renderer, /// This controls what happens when you close the main eframe window. @@ -340,7 +340,7 @@ pub struct NativeOptions { /// event loop before it is run. /// /// Note: A [`NativeOptions`] clone will not include any `event_loop_builder` hook. - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub event_loop_builder: Option, /// Hook into the building of a window. @@ -349,7 +349,7 @@ pub struct NativeOptions { /// window appearance. /// /// Note: A [`NativeOptions`] clone will not include any `window_builder` hook. - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub window_builder: Option, #[cfg(feature = "glow")] @@ -367,7 +367,7 @@ pub struct NativeOptions { pub centered: bool, /// Configures wgpu instance/device/adapter/surface creation and renderloop. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] pub wgpu_options: egui_wgpu::WgpuConfiguration, /// Controls whether or not the native window position and size will be @@ -404,13 +404,13 @@ impl Clone for NativeOptions { Self { viewport: self.viewport.clone(), - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] event_loop_builder: None, // Skip any builder callbacks if cloning - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] window_builder: None, // Skip any builder callbacks if cloning - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_options: self.wgpu_options.clone(), persistence_path: self.persistence_path.clone(), @@ -435,15 +435,15 @@ impl Default for NativeOptions { stencil_buffer: 0, hardware_acceleration: HardwareAcceleration::Preferred, - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] renderer: Renderer::default(), run_and_return: true, - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] event_loop_builder: None, - #[cfg(any(feature = "glow", feature = "wgpu"))] + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] window_builder: None, #[cfg(feature = "glow")] @@ -451,7 +451,7 @@ impl Default for NativeOptions { centered: false, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), persist_window: true, @@ -471,6 +471,10 @@ impl Default for NativeOptions { /// Options when using `eframe` in a web page. #[cfg(target_arch = "wasm32")] pub struct WebOptions { + /// What rendering backend to use. + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] + pub renderer: Renderer, + /// Sets the number of bits in the depth buffer. /// /// `egui` doesn't need the depth buffer, so the default value is 0. @@ -484,7 +488,7 @@ pub struct WebOptions { pub webgl_context_option: WebGlContextOption, /// Configures wgpu instance/device/adapter/surface creation and renderloop. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] pub wgpu_options: egui_wgpu::WgpuConfiguration, /// Controls whether to apply dithering to minimize banding artifacts. @@ -519,12 +523,15 @@ pub struct WebOptions { impl Default for WebOptions { fn default() -> Self { Self { + #[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] + renderer: Renderer::default(), + depth_buffer: 0, #[cfg(feature = "glow")] webgl_context_option: WebGlContextOption::BestFirst, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_options: egui_wgpu::WgpuConfiguration::default(), dithering: true, @@ -561,7 +568,7 @@ pub enum WebGlContextOption { /// What rendering backend to use. /// /// You need to enable the "glow" and "wgpu" features to have a choice. -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] @@ -571,49 +578,49 @@ pub enum Renderer { Glow, /// Use [`egui_wgpu`] renderer for [`wgpu`](https://github.com/gfx-rs/wgpu). - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Wgpu, } -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] impl Default for Renderer { fn default() -> Self { #[cfg(not(feature = "glow"))] - #[cfg(not(feature = "wgpu"))] + #[cfg(not(feature = "wgpu_no_default_features"))] compile_error!( "eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'" ); #[cfg(feature = "glow")] - #[cfg(not(feature = "wgpu"))] + #[cfg(not(feature = "wgpu_no_default_features"))] return Self::Glow; #[cfg(not(feature = "glow"))] - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] return Self::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: + // It's weird that the user has enabled both glow and wgpu, + // but let's pick the better of the two (wgpu): #[cfg(feature = "glow")] - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] return Self::Wgpu; } } -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] impl std::fmt::Display for Renderer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { #[cfg(feature = "glow")] Self::Glow => "glow".fmt(f), - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Self::Wgpu => "wgpu".fmt(f), } } } -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] impl std::str::FromStr for Renderer { type Err = String; @@ -622,7 +629,7 @@ impl std::str::FromStr for Renderer { #[cfg(feature = "glow")] "glow" => Ok(Self::Glow), - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] "wgpu" => Ok(Self::Wgpu), _ => Err(format!( @@ -655,7 +662,7 @@ pub struct Frame { Option egui::TextureId>>, /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] #[doc(hidden)] pub wgpu_render_state: Option, @@ -705,7 +712,7 @@ impl Frame { #[cfg(not(target_arch = "wasm32"))] raw_window_handle: Err(HandleError::NotSupported), storage: None, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: None, } } @@ -764,7 +771,7 @@ impl Frame { /// Only available when compiling with the `wgpu` feature and using [`Renderer::Wgpu`]. /// /// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> { self.wgpu_render_state.as_ref() } diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index f0f392256..d803a5249 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -159,7 +159,7 @@ pub use {egui, egui::emath, egui::epaint}; #[cfg(feature = "glow")] pub use {egui_glow, glow}; -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] pub use {egui_wgpu, wgpu}; mod epi; @@ -188,19 +188,19 @@ pub use web::{WebLogger, WebRunner}; // When compiling natively #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] mod native; #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use native::run::EframeWinitApplication; #[cfg(not(any(target_arch = "wasm32", target_os = "ios")))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub use native::run::EframePumpStatus; #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] #[cfg(feature = "persistence")] pub use native::file_storage::storage_dir; @@ -252,7 +252,7 @@ pub mod icon_data; /// # Errors /// This function can fail if we fail to set up a graphics context. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] #[allow(clippy::needless_pass_by_value, clippy::allow_attributes)] pub fn run_native( app_name: &str, @@ -268,7 +268,7 @@ pub fn run_native( native::run::run_glow(app_name, native_options, app_creator) } - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Renderer::Wgpu => { log::debug!("Using the wgpu renderer"); native::run::run_wgpu(app_name, native_options, app_creator) @@ -322,7 +322,7 @@ pub fn run_native( /// /// See the `external_eventloop` example for a more complete example. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub fn create_native<'a>( app_name: &str, mut native_options: NativeOptions, @@ -343,7 +343,7 @@ pub fn create_native<'a>( )) } - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Renderer::Wgpu => { log::debug!("Using the wgpu renderer"); EframeWinitApplication::new(native::run::create_wgpu( @@ -357,7 +357,7 @@ pub fn create_native<'a>( } #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { #[cfg(not(feature = "__screenshot"))] assert!( @@ -371,7 +371,7 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { let renderer = native_options.renderer; - #[cfg(all(feature = "glow", feature = "wgpu"))] + #[cfg(all(feature = "glow", feature = "wgpu_no_default_features"))] { match native_options.renderer { Renderer::Glow => "glow", @@ -420,7 +420,7 @@ fn init_native(app_name: &str, native_options: &mut NativeOptions) -> Renderer { /// # Errors /// This function can fail if we fail to set up a graphics context. #[cfg(not(target_arch = "wasm32"))] -#[cfg(any(feature = "glow", feature = "wgpu"))] +#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))] pub fn run_simple_native( app_name: &str, native_options: NativeOptions, @@ -472,7 +472,7 @@ pub enum Error { OpenGL(egui_glow::PainterError), /// An error from [`wgpu`]. - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Wgpu(egui_wgpu::WgpuError), } @@ -510,7 +510,7 @@ impl From for Error { } } -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] impl From for Error { #[inline] fn from(err: egui_wgpu::WgpuError) -> Self { @@ -551,7 +551,7 @@ impl std::fmt::Display for Error { write!(f, "egui_glow: {err}") } - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] Self::Wgpu(err) => { write!(f, "WGPU error: {err}") } diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 1b4c4b664..447b3ea9d 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -179,7 +179,9 @@ impl EpiIntegration { #[cfg(feature = "glow")] glow_register_native_texture: Option< Box egui::TextureId>, >, - #[cfg(feature = "wgpu")] wgpu_render_state: Option, + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: Option< + egui_wgpu::RenderState, + >, ) -> Self { let frame = epi::Frame { info: epi::IntegrationInfo { cpu_usage: None }, @@ -188,7 +190,7 @@ impl EpiIntegration { gl, #[cfg(feature = "glow")] glow_register_native_texture, - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state, raw_display_handle: window.display_handle().map(|h| h.as_raw()), raw_window_handle: window.window_handle().map(|h| h.as_raw()), diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index b42674052..c5358527a 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -239,7 +239,7 @@ impl<'app> GlowWinitApp<'app> { let painter = painter.clone(); move |native| painter.borrow_mut().register_native_texture(native) })), - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] None, ); @@ -301,7 +301,7 @@ impl<'app> GlowWinitApp<'app> { storage: integration.frame.storage(), gl: Some(gl), get_proc_address: Some(&get_proc_address), - #[cfg(feature = "wgpu")] + #[cfg(feature = "wgpu_no_default_features")] wgpu_render_state: None, raw_display_handle: window.display_handle().map(|h| h.as_raw()), raw_window_handle: window.window_handle().map(|h| h.as_raw()), diff --git a/crates/eframe/src/native/mod.rs b/crates/eframe/src/native/mod.rs index cc0bfd7fc..eb9413717 100644 --- a/crates/eframe/src/native/mod.rs +++ b/crates/eframe/src/native/mod.rs @@ -12,5 +12,5 @@ pub(crate) mod winit_integration; #[cfg(feature = "glow")] mod glow_integration; -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] mod wgpu_integration; diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 6fdae7d3c..7a3a8b4c4 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -381,7 +381,7 @@ pub fn create_glow<'a>( // ---------------------------------------------------------------------------- -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] pub fn run_wgpu( app_name: &str, mut native_options: epi::NativeOptions, @@ -404,7 +404,7 @@ pub fn run_wgpu( run_and_exit(event_loop, wgpu_eframe) } -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] pub fn create_wgpu<'a>( app_name: &str, native_options: epi::NativeOptions, diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 4a97235aa..8a10a90ef 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -1,15 +1,15 @@ use egui::{TexturesDelta, UserData, ViewportCommand}; -use crate::{App, epi}; +use crate::{App, epi, web::web_painter::WebPainter}; -use super::{NeedRepaint, now_sec, text_agent::TextAgent, web_painter::WebPainter as _}; +use super::{NeedRepaint, now_sec, text_agent::TextAgent}; pub struct AppRunner { #[allow(dead_code, clippy::allow_attributes)] pub(crate) web_options: crate::WebOptions, pub(crate) frame: epi::Frame, egui_ctx: egui::Context, - painter: super::ActiveWebPainter, + painter: Box, pub(crate) input: super::WebInput, app: Box, pub(crate) needs_repaint: std::sync::Arc, @@ -34,6 +34,10 @@ impl Drop for AppRunner { impl AppRunner { /// # Errors /// Failure to initialize WebGL renderer, or failure to create app. + #[cfg_attr( + not(feature = "wgpu_no_default_features"), + expect(clippy::unused_async) + )] pub async fn new( canvas: web_sys::HtmlCanvasElement, web_options: crate::WebOptions, @@ -41,7 +45,41 @@ impl AppRunner { text_agent: TextAgent, ) -> Result { let egui_ctx = egui::Context::default(); - let painter = super::ActiveWebPainter::new(egui_ctx.clone(), canvas, &web_options).await?; + + #[allow(clippy::allow_attributes, unused_assignments)] + #[cfg(feature = "glow")] + let mut gl = None; + + #[allow(clippy::allow_attributes, unused_assignments)] + #[cfg(feature = "wgpu_no_default_features")] + let mut wgpu_render_state = None; + + let painter = match web_options.renderer { + #[cfg(feature = "glow")] + epi::Renderer::Glow => { + log::debug!("Using the glow renderer"); + let painter = super::web_painter_glow::WebPainterGlow::new( + egui_ctx.clone(), + canvas, + &web_options, + )?; + gl = Some(painter.gl().clone()); + Box::new(painter) as Box + } + + #[cfg(feature = "wgpu_no_default_features")] + epi::Renderer::Wgpu => { + log::debug!("Using the wgpu renderer"); + let painter = super::web_painter_wgpu::WebPainterWgpu::new( + egui_ctx.clone(), + canvas, + &web_options, + ) + .await?; + wgpu_render_state = painter.render_state(); + Box::new(painter) as Box + } + }; let info = epi::IntegrationInfo { web_info: epi::WebInfo { @@ -79,15 +117,13 @@ impl AppRunner { storage: Some(&storage), #[cfg(feature = "glow")] - gl: Some(painter.gl().clone()), + gl: gl.clone(), #[cfg(feature = "glow")] get_proc_address: None, - #[cfg(all(feature = "wgpu", not(feature = "glow")))] - wgpu_render_state: painter.render_state(), - #[cfg(all(feature = "wgpu", feature = "glow"))] - wgpu_render_state: None, + #[cfg(feature = "wgpu_no_default_features")] + wgpu_render_state: wgpu_render_state.clone(), }; let app = app_creator(&cc).map_err(|err| err.to_string())?; @@ -96,12 +132,10 @@ impl AppRunner { storage: Some(Box::new(storage)), #[cfg(feature = "glow")] - gl: Some(painter.gl().clone()), + gl, - #[cfg(all(feature = "wgpu", not(feature = "glow")))] - wgpu_render_state: painter.render_state(), - #[cfg(all(feature = "wgpu", feature = "glow"))] - wgpu_render_state: None, + #[cfg(feature = "wgpu_no_default_features")] + wgpu_render_state, }; let needs_repaint: std::sync::Arc = @@ -324,8 +358,7 @@ impl AppRunner { events: _, // already handled mutable_text_under_cursor: _, // TODO(#4569): https://github.com/emilk/egui/issues/4569 ime, - #[cfg(feature = "accesskit")] - accesskit_update: _, // not currently implemented + accesskit_update: _, // not currently implemented num_completed_passes: _, // handled by `Context::run` request_discard_reasons: _, // handled by `Context::run` } = platform_output; diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index fdc9d2123..ac4c637db 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -23,20 +23,16 @@ pub use panic_handler::{PanicHandler, PanicSummary}; pub use web_logger::WebLogger; pub use web_runner::WebRunner; -#[cfg(not(any(feature = "glow", feature = "wgpu")))] +#[cfg(not(any(feature = "glow", feature = "wgpu_no_default_features")))] compile_error!("You must enable either the 'glow' or 'wgpu' feature"); mod web_painter; #[cfg(feature = "glow")] mod web_painter_glow; -#[cfg(feature = "glow")] -pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow; -#[cfg(feature = "wgpu")] +#[cfg(feature = "wgpu_no_default_features")] mod web_painter_wgpu; -#[cfg(all(feature = "wgpu", not(feature = "glow")))] -pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu; pub use backend::*; diff --git a/crates/eframe/src/web/web_painter_glow.rs b/crates/eframe/src/web/web_painter_glow.rs index ca6e11bf5..e2fc4a6f2 100644 --- a/crates/eframe/src/web/web_painter_glow.rs +++ b/crates/eframe/src/web/web_painter_glow.rs @@ -20,7 +20,7 @@ impl WebPainterGlow { self.painter.gl() } - pub async fn new( + pub fn new( _ctx: egui::Context, canvas: HtmlCanvasElement, options: &WebOptions, diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index 9faba9dd7..efecd12ee 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -1,13 +1,15 @@ use std::sync::Arc; -use super::web_painter::WebPainter; -use crate::WebOptions; use egui::{Event, UserData, ViewportId}; -use egui_wgpu::capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}; -use egui_wgpu::{RenderState, SurfaceErrorAction}; +use egui_wgpu::{ + RenderState, SurfaceErrorAction, + capture::{CaptureReceiver, CaptureSender, CaptureState, capture_channel}, +}; use wasm_bindgen::JsValue; use web_sys::HtmlCanvasElement; +use super::web_painter::WebPainter; + pub(crate) struct WebPainterWgpu { canvas: HtmlCanvasElement, surface: wgpu::Surface<'static>, @@ -23,7 +25,6 @@ pub(crate) struct WebPainterWgpu { } impl WebPainterWgpu { - #[expect(unused)] // only used if `wgpu` is the only active feature. pub fn render_state(&self) -> Option { self.render_state.clone() } @@ -55,11 +56,10 @@ impl WebPainterWgpu { }) } - #[expect(unused)] // only used if `wgpu` is the only active feature. pub async fn new( ctx: egui::Context, canvas: web_sys::HtmlCanvasElement, - options: &WebOptions, + options: &crate::WebOptions, ) -> Result { log::debug!("Creating wgpu painter"); diff --git a/crates/egui-wgpu/CHANGELOG.md b/crates/egui-wgpu/CHANGELOG.md index 5f4fd78c1..be00dd049 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.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) + + ## 0.33.0 - 2025-10-09 ### πŸ”§ Changed * Update wgpu to 26 and wasm-bindgen to 0.2.100 [#7540](https://github.com/emilk/egui/pull/7540) by [@Kumpelinus](https://github.com/Kumpelinus) diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index cd897b63e..c514e0a49 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -27,8 +27,11 @@ rustdoc-args = ["--generate-link-to-definition"] [features] default = ["fragile-send-sync-non-atomic-wasm", "macos-window-resize-jitter-fix", "wgpu/default"] +## Enables the `capture` module for capturing screenshots. +capture = ["dep:egui"] + ## Enable [`winit`](https://docs.rs/winit) integration. On Linux, requires either `wayland` or `x11` -winit = ["dep:winit", "winit/rwh_06"] +winit = ["dep:winit", "winit/rwh_06", "dep:egui", "capture"] ## Enables Wayland support for winit. wayland = ["winit?/wayland"] @@ -47,7 +50,6 @@ fragile-send-sync-non-atomic-wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"] macos-window-resize-jitter-fix = ["wgpu/metal"] [dependencies] -egui = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false, features = ["bytemuck"] } ahash.workspace = true @@ -62,4 +64,5 @@ wgpu = { workspace = true, features = ["wgsl"] } # Optional dependencies: +egui = { workspace = true, optional = true, default-features = false } winit = { workspace = true, optional = true, default-features = false } diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 59e27e7ac..d340526af 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -29,6 +29,7 @@ pub use renderer::*; pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting}; /// Helpers for capturing screenshots of the UI. +#[cfg(feature = "capture")] pub mod capture; /// Module for painting [`egui`](https://github.com/emilk/egui) with [`wgpu`] on [`winit`]. diff --git a/crates/egui-winit/CHANGELOG.md b/crates/egui-winit/CHANGELOG.md index e6b094502..8e555a7bc 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.2 - 2025-11-13 +* Don't enable `arboard` on iOS [#7663](https://github.com/emilk/egui/pull/7663) by [@irh](https://github.com/irh) + + ## 0.33.0 - 2025-10-09 ### ⭐ Added * Add rotation gesture support for trackpad sources [#7453](https://github.com/emilk/egui/pull/7453) by [@thatcomputerguy0101](https://github.com/thatcomputerguy0101) diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index a4c84b05f..d1b2ab220 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -24,7 +24,7 @@ rustdoc-args = ["--generate-link-to-definition"] default = ["clipboard", "links", "wayland", "winit/default", "x11"] ## Enable platform accessibility API implementations through [AccessKit](https://accesskit.dev/). -accesskit = ["dep:accesskit_winit", "egui/accesskit"] +accesskit = ["dep:accesskit_winit"] # Allow crates to choose an android-activity backend via Winit # - It's important that most applications should not have to depend on android-activity directly, and can diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index d72c44245..7660e3cef 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -888,7 +888,6 @@ impl State { events: _, // handled elsewhere mutable_text_under_cursor: _, // only used in eframe web ime, - #[cfg(feature = "accesskit")] accesskit_update, num_completed_passes: _, // `egui::Context::run` handles this request_discard_reasons: _, // `egui::Context::run` handles this @@ -947,6 +946,9 @@ impl State { profiling::scope!("accesskit"); accesskit.update_if_active(|| update); } + + #[cfg(not(feature = "accesskit"))] + let _ = accesskit_update; } fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) { diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index c82ae5618..764d2401e 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -26,10 +26,6 @@ rustdoc-args = ["--generate-link-to-definition"] [features] default = ["default_fonts"] -## Exposes detailed accessibility implementation required by platform -## accessibility APIs. Also requires support in the egui integration. -accesskit = ["dep:accesskit"] - ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast [`epaint::Vertex`], [`emath::Vec2`] etc to `&[u8]`. bytemuck = ["epaint/bytemuck"] @@ -61,7 +57,7 @@ persistence = ["serde", "epaint/serde", "ron"] rayon = ["epaint/rayon"] ## Allow serialization using [`serde`](https://docs.rs/serde). -serde = ["dep:serde", "epaint/serde", "accesskit?/serde"] +serde = ["dep:serde", "epaint/serde", "accesskit/serde"] ## Change Vertex layout to be compatible with unity unity = ["epaint/unity"] @@ -75,6 +71,7 @@ _override_unity = ["epaint/_override_unity"] emath = { workspace = true, default-features = false } epaint = { workspace = true, default-features = false } +accesskit.workspace = true ahash.workspace = true bitflags.workspace = true log.workspace = true @@ -84,7 +81,6 @@ smallvec.workspace = true unicode-segmentation.workspace = true #! ### Optional dependencies -accesskit = { workspace = true, optional = true } backtrace = { workspace = true, optional = true } diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 1c3e058b3..3ebac1d65 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -525,11 +525,21 @@ impl Area { true, ); + // Used to prevent drift + let pivot_at_start_of_drag_id = id.with("pivot_at_drag_start"); + if movable && move_response.dragged() && let Some(pivot_pos) = &mut state.pivot_pos { - *pivot_pos += move_response.drag_delta(); + let pivot_at_start_of_drag = ctx.data_mut(|data| { + *data.get_temp_mut_or::(pivot_at_start_of_drag_id, *pivot_pos) + }); + + *pivot_pos = + pivot_at_start_of_drag + move_response.total_drag_delta().unwrap_or_default(); + } else { + ctx.data_mut(|data| data.remove::(pivot_at_start_of_drag_id)); } if (move_response.dragged() || move_response.clicked()) @@ -543,13 +553,14 @@ impl Area { move_response }; - if constrain { - state.set_left_top_pos( - Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min, - ); - } - - state.set_left_top_pos(state.left_top_pos()); + state.set_left_top_pos(round_area_position( + ctx, + if constrain { + Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min + } else { + state.left_top_pos() + }, + )); // Update response with possibly moved/constrained rect: move_response.rect = state.rect(); @@ -570,6 +581,16 @@ impl Area { } } +fn round_area_position(ctx: &Context, pos: Pos2) -> Pos2 { + // We round a lot of rendering to pixels, so we round the whole + // area positions to pixels too, so avoid widgets appearing to float + // around independently of each other when the area is dragged. + // But just in case pixels_per_point is irrational, + // we then also round to ui coordinates: + + pos.round_to_pixels(ctx.pixels_per_point()).round_ui() +} + impl Prepared { pub(crate) fn state(&self) -> &AreaState { &self.state diff --git a/crates/egui/src/containers/menu.rs b/crates/egui/src/containers/menu.rs index f2aaee046..756d68dd3 100644 --- a/crates/egui/src/containers/menu.rs +++ b/crates/egui/src/containers/menu.rs @@ -161,14 +161,13 @@ impl MenuState { if state.last_visible_pass + 1 < pass_nr { state.open_item = None; } - if let Some(item) = state.open_item { - if data + if let Some(item) = state.open_item + && data .get_temp(item.with(Self::ID)) .is_none_or(|item: Self| item.last_visible_pass + 1 < pass_nr) - { - // If the open item wasn't shown for at least a frame, reset the open item - state.open_item = None; - } + { + // If the open item wasn't shown for at least a frame, reset the open item + state.open_item = None; } let r = f(&mut state); data.insert_temp(state_id, state); diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index 4312385da..a8f3306e9 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -1,4 +1,4 @@ -//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`SidePanel`], etc. +//! Containers are pieces of the UI which wraps other pieces of UI. Examples: [`Window`], [`ScrollArea`], [`Resize`], [`Panel`], etc. //! //! For instance, a [`Frame`] adds a frame and background to some contained UI. @@ -27,7 +27,7 @@ pub use { frame::Frame, modal::{Modal, ModalResponse}, old_popup::*, - panel::{CentralPanel, SidePanel, TopBottomPanel}, + panel::*, popup::*, resize::Resize, scene::{DragPanButtons, Scene}, diff --git a/crates/egui/src/containers/modal.rs b/crates/egui/src/containers/modal.rs index 6b846ab5e..23190ddf6 100644 --- a/crates/egui/src/containers/modal.rs +++ b/crates/egui/src/containers/modal.rs @@ -11,7 +11,7 @@ use crate::{ /// /// You can show multiple modals on top of each other. The topmost modal will always be /// the most recently shown one. -/// If multiple modals are newly shown in the same frame, the order of the modals not undefined +/// If multiple modals are newly shown in the same frame, the order of the modals is undefined /// (either first or second could be top). pub struct Modal { pub area: Area, diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 6e582b428..eb60f5f21 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -15,7 +15,7 @@ //! //! Add your [`crate::Window`]:s after any top-level panels. -use emath::GuiRounding as _; +use emath::{GuiRounding as _, Pos2}; use crate::{ Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef, @@ -51,21 +51,25 @@ impl PanelState { // ---------------------------------------------------------------------------- -/// [`Left`](Side::Left) or [`Right`](Side::Right) +/// [`Left`](VerticalSide::Left) or [`Right`](VerticalSide::Right) #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Side { +enum VerticalSide { Left, Right, } -impl Side { - fn opposite(self) -> Self { +impl VerticalSide { + pub fn opposite(self) -> Self { match self { Self::Left => Self::Right, Self::Right => Self::Left, } } + /// `self` is the _fixed_ side. + /// + /// * Left panels are resized on their right side + /// * Right panels are resized on their left side fn set_rect_width(self, rect: &mut Rect, width: f32) { match self { Self::Left => rect.max.x = rect.min.x + width, @@ -73,22 +77,211 @@ impl Side { } } - fn side_x(self, rect: Rect) -> f32 { - match self { - Self::Left => rect.left(), - Self::Right => rect.right(), - } - } - fn sign(self) -> f32 { match self { Self::Left => -1.0, Self::Right => 1.0, } } + + fn side_x(self, rect: Rect) -> f32 { + match self { + Self::Left => rect.left(), + Self::Right => rect.right(), + } + } } -/// A panel that covers the entire left or right side of a [`Ui`] or screen. +/// [`Top`](HorizontalSide::Top) or [`Bottom`](HorizontalSide::Bottom) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum HorizontalSide { + Top, + Bottom, +} + +impl HorizontalSide { + pub fn opposite(self) -> Self { + match self { + Self::Top => Self::Bottom, + Self::Bottom => Self::Top, + } + } + + /// `self` is the _fixed_ side. + /// + /// * Top panels are resized on their bottom side + /// * Bottom panels are resized upwards + fn set_rect_height(self, rect: &mut Rect, height: f32) { + match self { + Self::Top => rect.max.y = rect.min.y + height, + Self::Bottom => rect.min.y = rect.max.y - height, + } + } + + fn sign(self) -> f32 { + match self { + Self::Top => -1.0, + Self::Bottom => 1.0, + } + } + + fn side_y(self, rect: Rect) -> f32 { + match self { + Self::Top => rect.top(), + Self::Bottom => rect.bottom(), + } + } +} + +// Intentionally private because I'm not sure of the naming. +// TODO(emilk): decide on good names and make public. +// "VerticalSide" and "HorizontalSide" feels inverted to me. +/// [`Horizontal`](PanelSide::Horizontal) or [`Vertical`](PanelSide::Vertical) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum PanelSide { + /// Left or right. + Vertical(VerticalSide), + + /// Top or bottom + Horizontal(HorizontalSide), +} + +impl From for PanelSide { + fn from(side: HorizontalSide) -> Self { + Self::Horizontal(side) + } +} + +impl From for PanelSide { + fn from(side: VerticalSide) -> Self { + Self::Vertical(side) + } +} + +impl PanelSide { + pub const LEFT: Self = Self::Vertical(VerticalSide::Left); + pub const RIGHT: Self = Self::Vertical(VerticalSide::Right); + pub const TOP: Self = Self::Horizontal(HorizontalSide::Top); + pub const BOTTOM: Self = Self::Horizontal(HorizontalSide::Bottom); + + /// Resize by keeping the [`self`] side fixed, and moving the opposite side. + fn set_rect_size(self, rect: &mut Rect, size: f32) { + match self { + Self::Vertical(side) => side.set_rect_width(rect, size), + Self::Horizontal(side) => side.set_rect_height(rect, size), + } + } + + fn ui_kind(self) -> UiKind { + match self { + Self::Vertical(side) => match side { + VerticalSide::Left => UiKind::LeftPanel, + VerticalSide::Right => UiKind::RightPanel, + }, + Self::Horizontal(side) => match side { + HorizontalSide::Top => UiKind::TopPanel, + HorizontalSide::Bottom => UiKind::BottomPanel, + }, + } + } +} + +// ---------------------------------------------------------------------------- + +/// Intermediate structure to abstract some portion of [`Panel::show_inside`](Panel::show_inside). +struct PanelSizer<'a> { + panel: &'a Panel, + frame: Frame, + available_rect: Rect, + size: f32, + panel_rect: Rect, +} + +impl<'a> PanelSizer<'a> { + fn new(panel: &'a Panel, ui: &Ui) -> Self { + let frame = panel + .frame + .unwrap_or_else(|| Frame::side_top_panel(ui.style())); + let available_rect = ui.available_rect_before_wrap(); + let size = PanelSizer::get_size_from_state_or_default(panel, ui, frame); + let panel_rect = PanelSizer::panel_rect(panel, available_rect, size); + + Self { + panel, + frame, + available_rect, + size, + panel_rect, + } + } + + fn get_size_from_state_or_default(panel: &Panel, ui: &Ui, frame: Frame) -> f32 { + if let Some(state) = PanelState::load(ui.ctx(), panel.id) { + match panel.side { + PanelSide::Vertical(_) => state.rect.width(), + PanelSide::Horizontal(_) => state.rect.height(), + } + } else { + match panel.side { + PanelSide::Vertical(_) => panel.default_size.unwrap_or_else(|| { + ui.style().spacing.interact_size.x + frame.inner_margin.sum().x + }), + PanelSide::Horizontal(_) => panel.default_size.unwrap_or_else(|| { + ui.style().spacing.interact_size.y + frame.inner_margin.sum().y + }), + } + } + } + + fn panel_rect(panel: &Panel, available_rect: Rect, mut size: f32) -> Rect { + let side = panel.side; + let size_range = panel.size_range; + + let mut panel_rect = available_rect; + + match side { + PanelSide::Vertical(_) => { + size = clamp_to_range(size, size_range).at_most(available_rect.width()); + } + PanelSide::Horizontal(_) => { + size = clamp_to_range(size, size_range).at_most(available_rect.height()); + } + } + side.set_rect_size(&mut panel_rect, size); + panel_rect + } + + fn prepare_resizing_response(&mut self, is_resizing: bool, pointer: Option) { + let side = self.panel.side; + let size_range = self.panel.size_range; + + if is_resizing && pointer.is_some() { + let pointer = pointer.unwrap(); + + match side { + PanelSide::Vertical(side) => { + self.size = (pointer.x - side.side_x(self.panel_rect)).abs(); + self.size = + clamp_to_range(self.size, size_range).at_most(self.available_rect.width()); + } + PanelSide::Horizontal(side) => { + self.size = (pointer.y - side.side_y(self.panel_rect)).abs(); + self.size = + clamp_to_range(self.size, size_range).at_most(self.available_rect.height()); + } + } + + side.set_rect_size(&mut self.panel_rect, self.size); + } + } +} + +// ---------------------------------------------------------------------------- + +/// A panel that covers an entire side +/// ([`left`](Panel::left), [`right`](Panel::right), +/// [`top`](Panel::top) or [`bottom`](Panel::bottom)) +/// of a [`Ui`] or screen. /// /// 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. @@ -99,45 +292,83 @@ impl Side { /// /// ``` /// # egui::__run_test_ctx(|ctx| { -/// egui::SidePanel::left("my_left_panel").show(ctx, |ui| { +/// egui::Panel::left("my_left_panel").show(ctx, |ui| { /// ui.label("Hello World!"); /// }); /// # }); /// ``` -/// -/// See also [`TopBottomPanel`]. #[must_use = "You should call .show()"] -pub struct SidePanel { - side: Side, +pub struct Panel { + side: PanelSide, id: Id, frame: Option, resizable: bool, show_separator_line: bool, - default_width: f32, - width_range: Rangef, + + /// The size is defined as being either the width for a Vertical Panel + /// or the height for a Horizontal Panel. + default_size: Option, + + /// The size is defined as being either the width for a Vertical Panel + /// or the height for a Horizontal Panel. + size_range: Rangef, } -impl SidePanel { +impl Panel { + /// Create a left panel. + /// /// The id should be globally unique, e.g. `Id::new("my_left_panel")`. pub fn left(id: impl Into) -> Self { - Self::new(Side::Left, id) + Self::new(PanelSide::LEFT, id) } + /// Create a right panel. + /// /// The id should be globally unique, e.g. `Id::new("my_right_panel")`. pub fn right(id: impl Into) -> Self { - Self::new(Side::Right, id) + Self::new(PanelSide::RIGHT, id) } + /// Create a top panel. + /// + /// The id should be globally unique, e.g. `Id::new("my_top_panel")`. + /// + /// By default this is NOT resizable. + pub fn top(id: impl Into) -> Self { + Self::new(PanelSide::TOP, id).resizable(false) + } + + /// Create a bottom panel. + /// + /// The id should be globally unique, e.g. `Id::new("my_bottom_panel")`. + /// + /// By default this is NOT resizable. + pub fn bottom(id: impl Into) -> Self { + Self::new(PanelSide::BOTTOM, id).resizable(false) + } + + /// Create a panel. + /// /// The id should be globally unique, e.g. `Id::new("my_panel")`. - pub fn new(side: Side, id: impl Into) -> Self { + fn new(side: PanelSide, id: impl Into) -> Self { + let default_size: Option = match side { + PanelSide::Vertical(_) => Some(200.0), + PanelSide::Horizontal(_) => None, + }; + + let size_range: Rangef = match side { + PanelSide::Vertical(_) => Rangef::new(96.0, f32::INFINITY), + PanelSide::Horizontal(_) => Rangef::new(20.0, f32::INFINITY), + }; + Self { side, id: id.into(), frame: None, resizable: true, show_separator_line: true, - default_width: 200.0, - width_range: Rangef::new(96.0, f32::INFINITY), + default_size, + size_range, } } @@ -170,45 +401,47 @@ impl SidePanel { self } - /// The initial wrapping width of the [`SidePanel`], including margins. + /// The initial wrapping width of the [`Panel`], including margins. #[inline] - pub fn default_width(mut self, default_width: f32) -> Self { - self.default_width = default_width; - self.width_range = Rangef::new( - self.width_range.min.at_most(default_width), - self.width_range.max.at_least(default_width), + pub fn default_size(mut self, default_size: f32) -> Self { + self.default_size = Some(default_size); + self.size_range = Rangef::new( + self.size_range.min.at_most(default_size), + self.size_range.max.at_least(default_size), ); self } - /// Minimum width of the panel, including margins. + /// Minimum size of the panel, including margins. #[inline] - pub fn min_width(mut self, min_width: f32) -> Self { - self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width)); + pub fn min_size(mut self, min_size: f32) -> Self { + self.size_range = Rangef::new(min_size, self.size_range.max.at_least(min_size)); self } - /// Maximum width of the panel, including margins. + /// Maximum size of the panel, including margins. #[inline] - pub fn max_width(mut self, max_width: f32) -> Self { - self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width); + pub fn max_size(mut self, max_size: f32) -> Self { + self.size_range = Rangef::new(self.size_range.min.at_most(max_size), max_size); self } - /// The allowable width range for the panel, including margins. + /// The allowable size range for the panel, including margins. #[inline] - pub fn width_range(mut self, width_range: impl Into) -> Self { - let width_range = width_range.into(); - self.default_width = clamp_to_range(self.default_width, width_range); - self.width_range = width_range; + pub fn size_range(mut self, size_range: impl Into) -> Self { + let size_range = size_range.into(); + self.default_size = self + .default_size + .map(|default_size| clamp_to_range(default_size, size_range)); + self.size_range = size_range; self } - /// Enforce this exact width, including margins. + /// Enforce this exact size, including margins. #[inline] - pub fn exact_width(mut self, width: f32) -> Self { - self.default_width = width; - self.width_range = Rangef::point(width); + pub fn exact_size(mut self, size: f32) -> Self { + self.default_size = Some(size); + self.size_range = Rangef::point(size); self } @@ -220,7 +453,61 @@ impl SidePanel { } } -impl SidePanel { +// Deprecated +impl Panel { + #[deprecated = "Renamed default_size"] + pub fn default_width(self, default_size: f32) -> Self { + self.default_size(default_size) + } + + #[deprecated = "Renamed min_size"] + pub fn min_width(self, min_size: f32) -> Self { + self.min_size(min_size) + } + + #[deprecated = "Renamed max_size"] + pub fn max_width(self, max_size: f32) -> Self { + self.max_size(max_size) + } + + #[deprecated = "Renamed size_range"] + pub fn width_range(self, size_range: impl Into) -> Self { + self.size_range(size_range) + } + + #[deprecated = "Renamed exact_size"] + pub fn exact_width(self, size: f32) -> Self { + self.exact_size(size) + } + + #[deprecated = "Renamed default_size"] + pub fn default_height(self, default_size: f32) -> Self { + self.default_size(default_size) + } + + #[deprecated = "Renamed min_size"] + pub fn min_height(self, min_size: f32) -> Self { + self.min_size(min_size) + } + + #[deprecated = "Renamed max_size"] + pub fn max_height(self, max_size: f32) -> Self { + self.max_size(max_size) + } + + #[deprecated = "Renamed size_range"] + pub fn height_range(self, size_range: impl Into) -> Self { + self.size_range(size_range) + } + + #[deprecated = "Renamed exact_size"] + pub fn exact_height(self, size: f32) -> Self { + self.exact_size(size) + } +} + +// Public showing methods +impl Panel { /// Show the panel inside a [`Ui`]. pub fn show_inside( self, @@ -230,70 +517,170 @@ impl SidePanel { self.show_inside_dyn(ui, Box::new(add_contents)) } + /// Show the panel at the top level. + pub fn show( + self, + ctx: &Context, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + self.show_dyn(ctx, Box::new(add_contents)) + } + + /// Show the panel if `is_expanded` is `true`, + /// otherwise don't show it, but with a nice animation between collapsed and expanded. + pub fn show_animated( + self, + ctx: &Context, + is_expanded: bool, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); + + let animated_panel = self.get_animated_panel(ctx, is_expanded)?; + + if how_expanded < 1.0 { + // Show a fake panel in this in-between animation state: + animated_panel.show(ctx, |_ui| {}); + None + } else { + // Show the real panel: + Some(animated_panel.show(ctx, add_contents)) + } + } + + /// Show the panel if `is_expanded` is `true`, + /// otherwise don't show it, but with a nice animation between collapsed and expanded. + pub fn show_animated_inside( + self, + ui: &mut Ui, + is_expanded: bool, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> Option> { + let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded); + + // Get either the fake or the real panel to animate + let animated_panel = self.get_animated_panel(ui.ctx(), is_expanded)?; + + if how_expanded < 1.0 { + // Show a fake panel in this in-between animation state: + animated_panel.show_inside(ui, |_ui| {}); + None + } else { + // Show the real panel: + Some(animated_panel.show_inside(ui, add_contents)) + } + } + + /// Show either a collapsed or a expanded panel, with a nice animation between. + pub fn show_animated_between( + ctx: &Context, + is_expanded: bool, + collapsed_panel: Self, + expanded_panel: Self, + add_contents: impl FnOnce(&mut Ui, f32) -> R, + ) -> Option> { + let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); + + // Get either the fake or the real panel to animate + let animated_between_panel = + Self::get_animated_between_panel(ctx, is_expanded, collapsed_panel, expanded_panel); + + if 0.0 == how_expanded { + Some(animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + } else if how_expanded < 1.0 { + // Show animation: + animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded)); + None + } else { + Some(animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + } + } + + /// Show either a collapsed or a expanded panel, with a nice animation between. + pub fn show_animated_between_inside( + ui: &mut Ui, + is_expanded: bool, + collapsed_panel: Self, + expanded_panel: Self, + add_contents: impl FnOnce(&mut Ui, f32) -> R, + ) -> InnerResponse { + let how_expanded = + animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded); + + let animated_between_panel = Self::get_animated_between_panel( + ui.ctx(), + is_expanded, + collapsed_panel, + expanded_panel, + ); + + if 0.0 == how_expanded { + animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } else if how_expanded < 1.0 { + // Show animation: + animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } else { + animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) + } + } +} + +// Private methods to support the various show methods +impl Panel { /// Show the panel inside a [`Ui`]. fn show_inside_dyn<'c, R>( self, ui: &mut Ui, add_contents: Box R + 'c>, ) -> InnerResponse { - let Self { - side, - id, - frame, - resizable, - show_separator_line, - default_width, - width_range, - } = self; + let side = self.side; + let id = self.id; + let resizable = self.resizable; + let show_separator_line = self.show_separator_line; + let size_range = self.size_range; - let available_rect = ui.available_rect_before_wrap(); - let mut panel_rect = available_rect; - let mut width = default_width; - { - if let Some(state) = PanelState::load(ui.ctx(), id) { - width = state.rect.width(); - } - width = clamp_to_range(width, width_range).at_most(available_rect.width()); - side.set_rect_width(&mut panel_rect, width); - ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel"); + // Define the sizing of the panel. + let mut panel_sizer = PanelSizer::new(&self, ui); + + // Check for duplicate id + ui.ctx() + .check_for_id_clash(id, panel_sizer.panel_rect, "Panel"); + + if self.resizable { + // Prepare the resizable panel to avoid frame latency in the resize + self.prepare_resizable_panel(&mut panel_sizer, ui); } - let resize_id = id.with("__resize"); - let mut resize_hover = false; - let mut is_resizing = false; - if resizable { - // First we read the resize interaction results, to avoid frame latency in the resize: - if let Some(resize_response) = ui.ctx().read_response(resize_id) { - resize_hover = resize_response.hovered(); - is_resizing = resize_response.dragged(); - - if is_resizing && let Some(pointer) = resize_response.interact_pointer_pos() { - width = (pointer.x - side.side_x(panel_rect)).abs(); - width = clamp_to_range(width, width_range).at_most(available_rect.width()); - side.set_rect_width(&mut panel_rect, width); - } - } - } - - panel_rect = panel_rect.round_ui(); + // NOTE(shark98): This must be **after** the resizable preparation, as the size + // may change and round_ui() uses the size. + panel_sizer.panel_rect = panel_sizer.panel_rect.round_ui(); let mut panel_ui = ui.new_child( UiBuilder::new() .id_salt(id) - .ui_stack_info(UiStackInfo::new(match side { - Side::Left => UiKind::LeftPanel, - Side::Right => UiKind::RightPanel, - })) - .max_rect(panel_rect) + .ui_stack_info(UiStackInfo::new(side.ui_kind())) + .max_rect(panel_sizer.panel_rect) .layout(Layout::top_down(Align::Min)), ); - panel_ui.expand_to_include_rect(panel_rect); - panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475) + panel_ui.expand_to_include_rect(panel_sizer.panel_rect); + panel_ui.set_clip_rect(panel_sizer.panel_rect); // If we overflow, don't do so visibly (#4475) + + let inner_response = panel_sizer.frame.show(&mut panel_ui, |ui| { + match side { + PanelSide::Vertical(_) => { + ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height + ui.set_min_width( + (size_range.min - panel_sizer.frame.inner_margin.sum().x).at_least(0.0), + ); + } + PanelSide::Horizontal(_) => { + ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width + ui.set_min_height( + (size_range.min - panel_sizer.frame.inner_margin.sum().y).at_least(0.0), + ); + } + } - let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); - let inner_response = frame.show(&mut panel_ui, |ui| { - ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height - ui.set_min_width((width_range.min - frame.inner_margin.sum().x).at_least(0.0)); add_contents(ui) }); @@ -302,44 +689,31 @@ impl SidePanel { { let mut cursor = ui.cursor(); match side { - Side::Left => { - cursor.min.x = rect.max.x; - } - Side::Right => { - cursor.max.x = rect.min.x; - } + PanelSide::Vertical(side) => match side { + VerticalSide::Left => cursor.min.x = rect.max.x, + VerticalSide::Right => cursor.max.x = rect.min.x, + }, + PanelSide::Horizontal(side) => match side { + HorizontalSide::Top => cursor.min.y = rect.max.y, + HorizontalSide::Bottom => cursor.max.y = rect.min.y, + }, } ui.set_cursor(cursor); } + ui.expand_to_include_rect(rect); + let mut resize_hover = false; + let mut is_resizing = false; if resizable { - // Now we do the actual resize interaction, on top of all the contents. - // Otherwise its input could be eaten by the contents, e.g. a + // Now we do the actual resize interaction, on top of all the contents, + // otherwise its input could be eaten by the contents, e.g. a // `ScrollArea` on either side of the panel boundary. - let resize_x = side.opposite().side_x(panel_rect); - let resize_rect = Rect::from_x_y_ranges(resize_x..=resize_x, panel_rect.y_range()) - .expand2(vec2(ui.style().interaction.resize_grab_radius_side, 0.0)); - let resize_response = ui.interact(resize_rect, resize_id, Sense::drag()); - resize_hover = resize_response.hovered(); - is_resizing = resize_response.dragged(); + (resize_hover, is_resizing) = self.resize_panel(&panel_sizer, ui); } if resize_hover || is_resizing { - let cursor_icon = if width <= width_range.min { - match self.side { - Side::Left => CursorIcon::ResizeEast, - Side::Right => CursorIcon::ResizeWest, - } - } else if width < width_range.max { - CursorIcon::ResizeHorizontal - } else { - match self.side { - Side::Left => CursorIcon::ResizeWest, - Side::Right => CursorIcon::ResizeEast, - } - }; - ui.ctx().set_cursor_icon(cursor_icon); + ui.ctx().set_cursor_icon(self.cursor_icon(&panel_sizer)); } PanelState { rect }.store(ui.ctx(), id); @@ -356,25 +730,23 @@ impl SidePanel { Stroke::NONE }; // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done - let resize_x = side.opposite().side_x(rect); - - // Make sure the line is on the inside of the panel: - let resize_x = resize_x + 0.5 * side.sign() * stroke.width; - ui.painter().vline(resize_x, panel_rect.y_range(), stroke); + match side { + PanelSide::Vertical(side) => { + let x = side.opposite().side_x(rect) + 0.5 * side.sign() * stroke.width; + ui.painter() + .vline(x, panel_sizer.panel_rect.y_range(), stroke); + } + PanelSide::Horizontal(side) => { + let y = side.opposite().side_y(rect) + 0.5 * side.sign() * stroke.width; + ui.painter() + .hline(panel_sizer.panel_rect.x_range(), y, stroke); + } + } } inner_response } - /// Show the panel at the top level. - pub fn show( - self, - ctx: &Context, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - self.show_dyn(ctx, Box::new(add_contents)) - } - /// Show the panel at the top level. fn show_dyn<'c, R>( self, @@ -399,663 +771,177 @@ impl SidePanel { let rect = inner_response.response.rect; match side { - Side::Left => ctx.pass_state_mut(|state| { - state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max)); - }), - Side::Right => ctx.pass_state_mut(|state| { - state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)); - }), + PanelSide::Vertical(side) => match side { + VerticalSide::Left => ctx.pass_state_mut(|state| { + state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max)); + }), + VerticalSide::Right => ctx.pass_state_mut(|state| { + state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)); + }), + }, + PanelSide::Horizontal(side) => match side { + HorizontalSide::Top => { + ctx.pass_state_mut(|state| { + state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max)); + }); + } + HorizontalSide::Bottom => { + ctx.pass_state_mut(|state| { + state.allocate_bottom_panel(Rect::from_min_max( + rect.min, + available_rect.max, + )); + }); + } + }, } inner_response } - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - pub fn show_animated( - self, - ctx: &Context, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { - let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); + fn prepare_resizable_panel(&self, panel_sizer: &mut PanelSizer<'_>, ui: &Ui) { + let resize_id = self.id.with("__resize"); + let resize_response = ui.ctx().read_response(resize_id); - if 0.0 == how_expanded { - None - } else if how_expanded < 1.0 { - // Show a fake panel in this in-between animation state: - // TODO(emilk): move the panel out-of-screen instead of changing its width. - // Then we can actually paint it as it animates. - let expanded_width = PanelState::load(ctx, self.id) - .map_or(self.default_width, |state| state.rect.width()); - let fake_width = how_expanded * expanded_width; - Self { - id: self.id.with("animating_panel"), - ..self + if resize_response.is_some() { + let resize_response = resize_response.unwrap(); + + // NOTE(sharky98): The original code was initializing to + // false first, but it doesn't seem necessary. + let is_resizing = resize_response.dragged(); + let pointer = resize_response.interact_pointer_pos(); + panel_sizer.prepare_resizing_response(is_resizing, pointer); + } + } + + fn resize_panel(&self, panel_sizer: &PanelSizer<'_>, ui: &Ui) -> (bool, bool) { + let (resize_x, resize_y, amount): (Rangef, Rangef, Vec2) = match self.side { + PanelSide::Vertical(side) => { + let resize_x = side.opposite().side_x(panel_sizer.panel_rect); + let resize_y = panel_sizer.panel_rect.y_range(); + ( + Rangef::from(resize_x..=resize_x), + resize_y, + vec2(ui.style().interaction.resize_grab_radius_side, 0.0), + ) } - .resizable(false) - .exact_width(fake_width) - .show(ctx, |_ui| {}); - None - } else { - // Show the real panel: - Some(self.show(ctx, add_contents)) - } - } - - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - pub fn show_animated_inside( - self, - ui: &mut Ui, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { - let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded); - - if 0.0 == how_expanded { - None - } else if how_expanded < 1.0 { - // Show a fake panel in this in-between animation state: - // TODO(emilk): move the panel out-of-screen instead of changing its width. - // Then we can actually paint it as it animates. - let expanded_width = PanelState::load(ui.ctx(), self.id) - .map_or(self.default_width, |state| state.rect.width()); - let fake_width = how_expanded * expanded_width; - Self { - id: self.id.with("animating_panel"), - ..self + PanelSide::Horizontal(side) => { + let resize_x = panel_sizer.panel_rect.x_range(); + let resize_y = side.opposite().side_y(panel_sizer.panel_rect); + ( + resize_x, + Rangef::from(resize_y..=resize_y), + vec2(0.0, ui.style().interaction.resize_grab_radius_side), + ) } - .resizable(false) - .exact_width(fake_width) - .show_inside(ui, |_ui| {}); - None - } else { - // Show the real panel: - Some(self.show_inside(ui, add_contents)) - } - } - - /// Show either a collapsed or a expanded panel, with a nice animation between. - pub fn show_animated_between( - ctx: &Context, - is_expanded: bool, - collapsed_panel: Self, - expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> Option> { - let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); - - if 0.0 == how_expanded { - Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded))) - } else if how_expanded < 1.0 { - // Show animation: - let collapsed_width = PanelState::load(ctx, collapsed_panel.id) - .map_or(collapsed_panel.default_width, |state| state.rect.width()); - let expanded_width = PanelState::load(ctx, expanded_panel.id) - .map_or(expanded_panel.default_width, |state| state.rect.width()); - let fake_width = lerp(collapsed_width..=expanded_width, how_expanded); - Self { - id: expanded_panel.id.with("animating_panel"), - ..expanded_panel - } - .resizable(false) - .exact_width(fake_width) - .show(ctx, |ui| add_contents(ui, how_expanded)); - None - } else { - Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded))) - } - } - - /// Show either a collapsed or a expanded panel, with a nice animation between. - pub fn show_animated_between_inside( - ui: &mut Ui, - is_expanded: bool, - collapsed_panel: Self, - expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> InnerResponse { - let how_expanded = - animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded); - - if 0.0 == how_expanded { - collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) - } else if how_expanded < 1.0 { - // Show animation: - let collapsed_width = PanelState::load(ui.ctx(), collapsed_panel.id) - .map_or(collapsed_panel.default_width, |state| state.rect.width()); - let expanded_width = PanelState::load(ui.ctx(), expanded_panel.id) - .map_or(expanded_panel.default_width, |state| state.rect.width()); - let fake_width = lerp(collapsed_width..=expanded_width, how_expanded); - Self { - id: expanded_panel.id.with("animating_panel"), - ..expanded_panel - } - .resizable(false) - .exact_width(fake_width) - .show_inside(ui, |ui| add_contents(ui, how_expanded)) - } else { - expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) - } - } -} - -// ---------------------------------------------------------------------------- - -/// [`Top`](TopBottomSide::Top) or [`Bottom`](TopBottomSide::Bottom) -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum TopBottomSide { - Top, - Bottom, -} - -impl TopBottomSide { - fn opposite(self) -> Self { - match self { - Self::Top => Self::Bottom, - Self::Bottom => Self::Top, - } - } - - fn set_rect_height(self, rect: &mut Rect, height: f32) { - match self { - Self::Top => rect.max.y = rect.min.y + height, - Self::Bottom => rect.min.y = rect.max.y - height, - } - } - - fn side_y(self, rect: Rect) -> f32 { - match self { - Self::Top => rect.top(), - Self::Bottom => rect.bottom(), - } - } - - fn sign(self) -> f32 { - match self { - Self::Top => -1.0, - Self::Bottom => 1.0, - } - } -} - -/// A panel that covers the entire top or bottom of a [`Ui`] or screen. -/// -/// 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. -/// -/// ⚠ Always add any [`CentralPanel`] last. -/// -/// See the [module level docs](crate::containers::panel) for more details. -/// -/// ``` -/// # egui::__run_test_ctx(|ctx| { -/// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| { -/// ui.label("Hello World!"); -/// }); -/// # }); -/// ``` -/// -/// See also [`SidePanel`]. -#[must_use = "You should call .show()"] -pub struct TopBottomPanel { - side: TopBottomSide, - id: Id, - frame: Option, - resizable: bool, - show_separator_line: bool, - default_height: Option, - height_range: Rangef, -} - -impl TopBottomPanel { - /// The id should be globally unique, e.g. `Id::new("my_top_panel")`. - pub fn top(id: impl Into) -> Self { - Self::new(TopBottomSide::Top, id) - } - - /// The id should be globally unique, e.g. `Id::new("my_bottom_panel")`. - pub fn bottom(id: impl Into) -> Self { - Self::new(TopBottomSide::Bottom, id) - } - - /// The id should be globally unique, e.g. `Id::new("my_panel")`. - pub fn new(side: TopBottomSide, id: impl Into) -> Self { - Self { - side, - id: id.into(), - frame: None, - resizable: false, - show_separator_line: true, - default_height: None, - height_range: Rangef::new(20.0, f32::INFINITY), - } - } - - /// Can panel be resized by dragging the edge of it? - /// - /// Default is `false`. - /// - /// If you want your panel to be resizable you also need to make the ui use - /// the available space. - /// - /// This can be done by using [`Ui::take_available_space`], or using a - /// widget in it that takes up more space as you resize it, such as: - /// * Wrapping text ([`Ui::horizontal_wrapped`]). - /// * A [`crate::ScrollArea`]. - /// * A [`crate::Separator`]. - /// * A [`crate::TextEdit`]. - /// * … - #[inline] - pub fn resizable(mut self, resizable: bool) -> Self { - self.resizable = resizable; - self - } - - /// Show a separator line, even when not interacting with it? - /// - /// Default: `true`. - #[inline] - pub fn show_separator_line(mut self, show_separator_line: bool) -> Self { - self.show_separator_line = show_separator_line; - self - } - - /// The initial height of the [`TopBottomPanel`], including margins. - /// Defaults to [`crate::style::Spacing::interact_size`].y, plus frame margins. - #[inline] - pub fn default_height(mut self, default_height: f32) -> Self { - self.default_height = Some(default_height); - self.height_range = Rangef::new( - self.height_range.min.at_most(default_height), - self.height_range.max.at_least(default_height), - ); - self - } - - /// Minimum height of the panel, including margins. - #[inline] - pub fn min_height(mut self, min_height: f32) -> Self { - self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height)); - self - } - - /// Maximum height of the panel, including margins. - #[inline] - pub fn max_height(mut self, max_height: f32) -> Self { - self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height); - self - } - - /// The allowable height range for the panel, including margins. - #[inline] - pub fn height_range(mut self, height_range: impl Into) -> Self { - let height_range = height_range.into(); - self.default_height = self - .default_height - .map(|default_height| clamp_to_range(default_height, height_range)); - self.height_range = height_range; - self - } - - /// Enforce this exact height, including margins. - #[inline] - pub fn exact_height(mut self, height: f32) -> Self { - self.default_height = Some(height); - self.height_range = Rangef::point(height); - self - } - - /// Change the background color, margins, etc. - #[inline] - pub fn frame(mut self, frame: Frame) -> Self { - self.frame = Some(frame); - self - } -} - -impl TopBottomPanel { - /// Show the panel inside a [`Ui`]. - pub fn show_inside( - self, - ui: &mut Ui, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - self.show_inside_dyn(ui, Box::new(add_contents)) - } - - /// Show the panel inside a [`Ui`]. - fn show_inside_dyn<'c, R>( - self, - ui: &mut Ui, - add_contents: Box R + 'c>, - ) -> InnerResponse { - let Self { - side, - id, - frame, - resizable, - show_separator_line, - default_height, - height_range, - } = self; - - let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style())); - - let available_rect = ui.available_rect_before_wrap(); - let mut panel_rect = available_rect; - - let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) { - state.rect.height() - } else { - default_height - .unwrap_or_else(|| ui.style().spacing.interact_size.y + frame.inner_margin.sum().y) }; - { - height = clamp_to_range(height, height_range).at_most(available_rect.height()); - side.set_rect_height(&mut panel_rect, height); - ui.ctx() - .check_for_id_clash(id, panel_rect, "TopBottomPanel"); - } - let resize_id = id.with("__resize"); - let mut resize_hover = false; - let mut is_resizing = false; - if resizable { - // First we read the resize interaction results, to avoid frame latency in the resize: - if let Some(resize_response) = ui.ctx().read_response(resize_id) { - resize_hover = resize_response.hovered(); - is_resizing = resize_response.dragged(); + let resize_id = self.id.with("__resize"); + let resize_rect = Rect::from_x_y_ranges(resize_x, resize_y).expand2(amount); + let resize_response = ui.interact(resize_rect, resize_id, Sense::drag()); - if is_resizing && let Some(pointer) = resize_response.interact_pointer_pos() { - height = (pointer.y - side.side_y(panel_rect)).abs(); - height = clamp_to_range(height, height_range).at_most(available_rect.height()); - side.set_rect_height(&mut panel_rect, height); - } - } - } - - panel_rect = panel_rect.round_ui(); - - let mut panel_ui = ui.new_child( - UiBuilder::new() - .id_salt(id) - .ui_stack_info(UiStackInfo::new(match side { - TopBottomSide::Top => UiKind::TopPanel, - TopBottomSide::Bottom => UiKind::BottomPanel, - })) - .max_rect(panel_rect) - .layout(Layout::top_down(Align::Min)), - ); - panel_ui.expand_to_include_rect(panel_rect); - panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475) - - let inner_response = frame.show(&mut panel_ui, |ui| { - ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width - ui.set_min_height((height_range.min - frame.inner_margin.sum().y).at_least(0.0)); - add_contents(ui) - }); - - let rect = inner_response.response.rect; - - { - let mut cursor = ui.cursor(); - match side { - TopBottomSide::Top => { - cursor.min.y = rect.max.y; - } - TopBottomSide::Bottom => { - cursor.max.y = rect.min.y; - } - } - ui.set_cursor(cursor); - } - ui.expand_to_include_rect(rect); - - if resizable { - // Now we do the actual resize interaction, on top of all the contents. - // Otherwise its input could be eaten by the contents, e.g. a - // `ScrollArea` on either side of the panel boundary. - - let resize_y = side.opposite().side_y(panel_rect); - let resize_rect = Rect::from_x_y_ranges(panel_rect.x_range(), resize_y..=resize_y) - .expand2(vec2(0.0, ui.style().interaction.resize_grab_radius_side)); - let resize_response = ui.interact(resize_rect, resize_id, Sense::drag()); - resize_hover = resize_response.hovered(); - is_resizing = resize_response.dragged(); - } - - if resize_hover || is_resizing { - let cursor_icon = if height <= height_range.min { - match self.side { - TopBottomSide::Top => CursorIcon::ResizeSouth, - TopBottomSide::Bottom => CursorIcon::ResizeNorth, - } - } else if height < height_range.max { - CursorIcon::ResizeVertical - } else { - match self.side { - TopBottomSide::Top => CursorIcon::ResizeNorth, - TopBottomSide::Bottom => CursorIcon::ResizeSouth, - } - }; - ui.ctx().set_cursor_icon(cursor_icon); - } - - PanelState { rect }.store(ui.ctx(), id); - - { - let stroke = if is_resizing { - ui.style().visuals.widgets.active.fg_stroke // highly visible - } else if resize_hover { - ui.style().visuals.widgets.hovered.fg_stroke // highly visible - } else if show_separator_line { - // TODO(emilk): distinguish resizable from non-resizable - ui.style().visuals.widgets.noninteractive.bg_stroke // dim - } else { - Stroke::NONE - }; - // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done - let resize_y = side.opposite().side_y(rect); - - // Make sure the line is on the inside of the panel: - let resize_y = resize_y + 0.5 * side.sign() * stroke.width; - ui.painter().hline(panel_rect.x_range(), resize_y, stroke); - } - - inner_response + (resize_response.hovered(), resize_response.dragged()) } - /// Show the panel at the top level. - pub fn show( - self, - ctx: &Context, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> InnerResponse { - self.show_dyn(ctx, Box::new(add_contents)) - } - - /// Show the panel at the top level. - fn show_dyn<'c, R>( - self, - ctx: &Context, - add_contents: Box R + 'c>, - ) -> InnerResponse { - let available_rect = ctx.available_rect(); - let side = self.side; - - let mut panel_ui = Ui::new( - ctx.clone(), - self.id, - UiBuilder::new() - .layer_id(LayerId::background()) - .max_rect(available_rect), - ); - panel_ui.set_clip_rect(ctx.content_rect()); - - let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); - let rect = inner_response.response.rect; - - match side { - TopBottomSide::Top => { - ctx.pass_state_mut(|state| { - state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max)); - }); + fn cursor_icon(&self, panel_sizer: &PanelSizer<'_>) -> CursorIcon { + if panel_sizer.size <= self.size_range.min { + match self.side { + PanelSide::Vertical(side) => match side { + VerticalSide::Left => CursorIcon::ResizeEast, + VerticalSide::Right => CursorIcon::ResizeWest, + }, + PanelSide::Horizontal(side) => match side { + HorizontalSide::Top => CursorIcon::ResizeSouth, + HorizontalSide::Bottom => CursorIcon::ResizeNorth, + }, } - TopBottomSide::Bottom => { - ctx.pass_state_mut(|state| { - state.allocate_bottom_panel(Rect::from_min_max(rect.min, available_rect.max)); - }); + } else if panel_sizer.size < self.size_range.max { + match self.side { + PanelSide::Vertical(_) => CursorIcon::ResizeHorizontal, + PanelSide::Horizontal(_) => CursorIcon::ResizeVertical, + } + } else { + match self.side { + PanelSide::Vertical(side) => match side { + VerticalSide::Left => CursorIcon::ResizeWest, + VerticalSide::Right => CursorIcon::ResizeEast, + }, + PanelSide::Horizontal(side) => match side { + HorizontalSide::Top => CursorIcon::ResizeNorth, + HorizontalSide::Bottom => CursorIcon::ResizeSouth, + }, } } - - inner_response } - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - pub fn show_animated( - self, - ctx: &Context, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { + /// Get the real or fake panel to animate if `is_expanded` is `true`. + fn get_animated_panel(self, ctx: &Context, is_expanded: bool) -> Option { let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded); if 0.0 == how_expanded { None } else if how_expanded < 1.0 { // Show a fake panel in this in-between animation state: - // TODO(emilk): move the panel out-of-screen instead of changing its height. + // TODO(emilk): move the panel out-of-screen instead of changing its width. // Then we can actually paint it as it animates. - let expanded_height = PanelState::load(ctx, self.id) - .map(|state| state.rect.height()) - .or(self.default_height) - .unwrap_or_else(|| ctx.style().spacing.interact_size.y); - let fake_height = how_expanded * expanded_height; - Self { - id: self.id.with("animating_panel"), - ..self - } - .resizable(false) - .exact_height(fake_height) - .show(ctx, |_ui| {}); - None + let expanded_size = Self::animated_size(ctx, &self); + let fake_size = how_expanded * expanded_size; + Some( + Self { + id: self.id.with("animating_panel"), + ..self + } + .resizable(false) + .exact_size(fake_size), + ) } else { // Show the real panel: - Some(self.show(ctx, add_contents)) + Some(self) } } - /// Show the panel if `is_expanded` is `true`, - /// otherwise don't show it, but with a nice animation between collapsed and expanded. - pub fn show_animated_inside( - self, - ui: &mut Ui, - is_expanded: bool, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> Option> { - let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded); - - if 0.0 == how_expanded { - None - } else if how_expanded < 1.0 { - // Show a fake panel in this in-between animation state: - // TODO(emilk): move the panel out-of-screen instead of changing its height. - // Then we can actually paint it as it animates. - let expanded_height = PanelState::load(ui.ctx(), self.id) - .map(|state| state.rect.height()) - .or(self.default_height) - .unwrap_or_else(|| ui.style().spacing.interact_size.y); - let fake_height = how_expanded * expanded_height; - Self { - id: self.id.with("animating_panel"), - ..self - } - .resizable(false) - .exact_height(fake_height) - .show_inside(ui, |_ui| {}); - None - } else { - // Show the real panel: - Some(self.show_inside(ui, add_contents)) - } - } - - /// Show either a collapsed or a expanded panel, with a nice animation between. - pub fn show_animated_between( + /// Get either the collapsed or expended panel to animate. + fn get_animated_between_panel( ctx: &Context, is_expanded: bool, collapsed_panel: Self, expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> Option> { + ) -> Self { let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded); if 0.0 == how_expanded { - Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + collapsed_panel } else if how_expanded < 1.0 { - // Show animation: - let collapsed_height = PanelState::load(ctx, collapsed_panel.id) - .map(|state| state.rect.height()) - .or(collapsed_panel.default_height) - .unwrap_or_else(|| ctx.style().spacing.interact_size.y); + let collapsed_size = Self::animated_size(ctx, &collapsed_panel); + let expanded_size = Self::animated_size(ctx, &expanded_panel); - let expanded_height = PanelState::load(ctx, expanded_panel.id) - .map(|state| state.rect.height()) - .or(expanded_panel.default_height) - .unwrap_or_else(|| ctx.style().spacing.interact_size.y); + let fake_size = lerp(collapsed_size..=expanded_size, how_expanded); - let fake_height = lerp(collapsed_height..=expanded_height, how_expanded); Self { id: expanded_panel.id.with("animating_panel"), ..expanded_panel } .resizable(false) - .exact_height(fake_height) - .show(ctx, |ui| add_contents(ui, how_expanded)); - None + .exact_size(fake_size) } else { - Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded))) + expanded_panel } } - /// Show either a collapsed or a expanded panel, with a nice animation between. - pub fn show_animated_between_inside( - ui: &mut Ui, - is_expanded: bool, - collapsed_panel: Self, - expanded_panel: Self, - add_contents: impl FnOnce(&mut Ui, f32) -> R, - ) -> InnerResponse { - let how_expanded = - animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded); + fn animated_size(ctx: &Context, panel: &Self) -> f32 { + let get_rect_state_size = |state: PanelState| match panel.side { + PanelSide::Vertical(_) => state.rect.width(), + PanelSide::Horizontal(_) => state.rect.height(), + }; - if 0.0 == how_expanded { - collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) - } else if how_expanded < 1.0 { - // Show animation: - let collapsed_height = PanelState::load(ui.ctx(), collapsed_panel.id) - .map(|state| state.rect.height()) - .or(collapsed_panel.default_height) - .unwrap_or_else(|| ui.style().spacing.interact_size.y); + let get_spacing_size = || match panel.side { + PanelSide::Vertical(_) => ctx.style().spacing.interact_size.x, + PanelSide::Horizontal(_) => ctx.style().spacing.interact_size.y, + }; - let expanded_height = PanelState::load(ui.ctx(), expanded_panel.id) - .map(|state| state.rect.height()) - .or(expanded_panel.default_height) - .unwrap_or_else(|| ui.style().spacing.interact_size.y); - - let fake_height = lerp(collapsed_height..=expanded_height, how_expanded); - Self { - id: expanded_panel.id.with("animating_panel"), - ..expanded_panel - } - .resizable(false) - .exact_height(fake_height) - .show_inside(ui, |ui| add_contents(ui, how_expanded)) - } else { - expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded)) - } + PanelState::load(ctx, panel.id) + .map(get_rect_state_size) + .or(panel.default_size) + .unwrap_or(get_spacing_size()) } } @@ -1075,8 +961,8 @@ impl TopBottomPanel { /// /// ``` /// # egui::__run_test_ctx(|ctx| { -/// egui::TopBottomPanel::top("my_panel").show(ctx, |ui| { -/// ui.label("Hello World! From `TopBottomPanel`, that must be before `CentralPanel`!"); +/// egui::Panel::top("my_panel").show(ctx, |ui| { +/// ui.label("Hello World! From `Panel`, that must be before `CentralPanel`!"); /// }); /// egui::CentralPanel::default().show(ctx, |ui| { /// ui.label("Hello World!"); @@ -1158,6 +1044,13 @@ impl CentralPanel { ); panel_ui.set_clip_rect(ctx.content_rect()); + if false { + // TODO(emilk): @lucasmerlin shouldn't we enable this? + panel_ui + .response() + .widget_info(|| WidgetInfo::new(WidgetType::Panel)); + } + let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); // Only inform ctx about what we actually used, so we can shrink the native window to fit. @@ -1171,3 +1064,11 @@ fn clamp_to_range(x: f32, range: Rangef) -> f32 { let range = range.as_positive(); x.clamp(range.min, range.max) } + +// ---------------------------------------------------------------------------- + +#[deprecated = "Use Panel::left or Panel::right instead"] +pub type SidePanel = super::Panel; + +#[deprecated = "Use Panel::top or Panel::bottom instead"] +pub type TopBottomPanel = super::Panel; diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index d63a2ab59..5ed4c31f3 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,7 +1,12 @@ +//! See [`ScrollArea`] for docs. + #![allow(clippy::needless_range_loop)] use std::ops::{Add, AddAssign, BitOr, BitOrAssign}; +use emath::GuiRounding as _; +use epaint::Margin; + use crate::{ Context, CursorIcon, Id, NumExt as _, Pos2, Rangef, Rect, Response, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, emath, epaint, lerp, pass_state, pos2, remap, remap_clamp, @@ -256,7 +261,7 @@ impl AddAssign for ScrollSource { /// ### Coordinate system /// * content: size of contents (generally large; that's why we want scroll bars) /// * outer: size of scroll area including scroll bar(s) -/// * inner: excluding scroll bar(s). The area we clip the contents to. +/// * inner: excluding scroll bar(s). The area we clip the contents to. Includes `content_margin`. /// /// If the floating scroll bars settings is turned on then `inner == outer`. /// @@ -292,6 +297,8 @@ pub struct ScrollArea { scroll_source: ScrollSource, wheel_scroll_multiplier: Vec2, + content_margin: Option, + /// If true for vertical or horizontal the scroll wheel will stick to the /// end position until user manually changes position. It will become true /// again once scroll handle makes contact with end. @@ -344,6 +351,7 @@ impl ScrollArea { on_drag_cursor: None, scroll_source: ScrollSource::default(), wheel_scroll_multiplier: Vec2::splat(1.0), + content_margin: None, stick_to_end: Vec2b::FALSE, animated: true, } @@ -591,6 +599,18 @@ impl ScrollArea { self.direction_enabled[0] || self.direction_enabled[1] } + /// Extra margin added around the contents. + /// + /// The scroll bars will be either on top of this margin, or outside of it, + /// depending on the value of [`crate::style::ScrollStyle::floating`]. + /// + /// Default: [`crate::style::ScrollStyle::content_margin`]. + #[inline] + pub fn content_margin(mut self, margin: impl Into) -> Self { + self.content_margin = Some(margin.into()); + self + } + /// The scroll handle will stick to the rightmost position even while the content size /// changes dynamically. This can be useful to simulate text scrollers coming in from right /// hand side. The scroll handle remains stuck until user manually changes position. Once "unstuck" @@ -642,7 +662,7 @@ struct Prepared { scroll_bar_visibility: ScrollBarVisibility, scroll_bar_rect: Option, - /// Where on the screen the content is (excludes scroll bars). + /// Where on the screen the content is (excludes scroll bars; includes `content_margin`). inner_rect: Rect, content_ui: Ui, @@ -681,6 +701,7 @@ impl ScrollArea { on_drag_cursor, scroll_source, wheel_scroll_multiplier, + content_margin: _, // Used elsewhere stick_to_end, animated, } = self; @@ -748,6 +769,12 @@ impl ScrollArea { } let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size); + + // Round to pixels to avoid widgets appearing to "float" when scrolling fractional amounts: + let content_max_rect = content_max_rect + .round_to_pixels(ui.pixels_per_point()) + .round_ui(); + let mut content_ui = ui.new_child( UiBuilder::new() .ui_stack_info(UiStackInfo::new(UiKind::ScrollArea)) @@ -975,10 +1002,21 @@ impl ScrollArea { ui: &mut Ui, add_contents: Box R + 'c>, ) -> ScrollAreaOutput { + let margin = self + .content_margin + .unwrap_or_else(|| ui.spacing().scroll.content_margin); + let mut prepared = self.begin(ui); let id = prepared.id; let inner_rect = prepared.inner_rect; - let inner = add_contents(&mut prepared.content_ui, prepared.viewport); + + let inner = crate::Frame::NONE + .inner_margin(margin) + .show(&mut prepared.content_ui, |ui| { + add_contents(ui, prepared.viewport) + }) + .inner; + let (content_size, state) = prepared.end(ui); ScrollAreaOutput { inner, diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index da7e65c1b..8d7a95be7 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -825,7 +825,7 @@ fn resize_response( area: &mut area::Prepared, resize_id: Id, ) { - let Some(mut new_rect) = move_and_resize_window(ctx, &resize_interaction) else { + let Some(mut new_rect) = move_and_resize_window(ctx, resize_id, &resize_interaction) else { return; }; @@ -847,27 +847,38 @@ fn resize_response( } /// Acts on outer rect (outside the stroke) -fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Option { +fn move_and_resize_window(ctx: &Context, id: Id, interaction: &ResizeInteraction) -> Option { + // Used to prevent drift + let rect_at_start_of_drag_id = id.with("window_rect_at_drag_start"); + if !interaction.any_dragged() { + ctx.data_mut(|data| { + data.remove::(rect_at_start_of_drag_id); + }); return None; } - let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?; - let mut rect = interaction.outer_rect; // prevent drift + let total_drag_delta = ctx.input(|i| i.pointer.total_drag_delta())?; + + let rect_at_start_of_drag = ctx.data_mut(|data| { + *data.get_temp_mut_or::(rect_at_start_of_drag_id, interaction.outer_rect) + }); + + let mut rect = rect_at_start_of_drag; // prevent drift // Put the rect in the center of the stroke: rect = rect.shrink(interaction.window_frame.stroke.width / 2.0); if interaction.left.drag { - rect.min.x = pointer_pos.x; + rect.min.x += total_drag_delta.x; } else if interaction.right.drag { - rect.max.x = pointer_pos.x; + rect.max.x += total_drag_delta.x; } if interaction.top.drag { - rect.min.y = pointer_pos.y; + rect.min.y += total_drag_delta.y; } else if interaction.bottom.drag { - rect.max.y = pointer_pos.y; + rect.max.y += total_drag_delta.y; } // Return to having the rect outside the stroke: @@ -899,7 +910,6 @@ fn resize_interaction( let rect = outer_rect.shrink(window_frame.stroke.width / 2.0); let side_response = |rect, id| { - #[cfg(feature = "accesskit")] ctx.register_accesskit_parent(id, _accessibility_parent); let response = ctx.create_widget( WidgetRect { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 5a868dd75..2ca7af4fc 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -41,7 +41,6 @@ use crate::{ viewport::ViewportClass, }; -#[cfg(feature = "accesskit")] use crate::IdMap; /// Information given to the backend about when it is time to repaint the ui. @@ -404,7 +403,6 @@ struct ContextImpl { embed_viewports: bool, - #[cfg(feature = "accesskit")] is_accesskit_enabled: bool, loaders: Arc, @@ -507,7 +505,6 @@ impl ContextImpl { }, ); - #[cfg(feature = "accesskit")] if self.is_accesskit_enabled { profiling::scope!("accesskit"); use crate::pass_state::AccessKitPassState; @@ -589,10 +586,10 @@ impl ContextImpl { } } - #[cfg(feature = "accesskit")] fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::Node { let state = self.viewport().this_pass.accesskit_state.as_mut().unwrap(); let builders = &mut state.nodes; + if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) { entry.insert(Default::default()); @@ -619,6 +616,7 @@ impl ContextImpl { let parent_builder = builders.get_mut(&parent_id).unwrap(); parent_builder.push_child(id.accesskit_id()); } + builders.get_mut(&id).unwrap() } @@ -766,7 +764,7 @@ impl Context { /// and only on the rare occasion that [`Context::request_discard`] is called. /// Usually, it `run_ui` will only be called once. /// - /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. + /// Put your widgets into a [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. /// /// Instead of calling `run`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`]. /// @@ -1204,7 +1202,6 @@ impl Context { plugins.on_widget_under_pointer(self, &w); } - #[cfg(feature = "accesskit")] if allow_focus && w.sense.is_focusable() { // Make sure anything that can receive focus has an AccessKit node. // TODO(mwcampbell): For nodes that are filled from widget info, @@ -1212,7 +1209,6 @@ impl Context { self.accesskit_node_builder(w.id, |builder| res.fill_accesskit_node_common(builder)); } - #[cfg(feature = "accesskit")] self.write(|ctx| { use crate::{Align, pass_state::ScrollTarget, style::ScrollAnimation}; let viewport = ctx.viewport_for(ctx.viewport_id()); @@ -1220,12 +1216,14 @@ impl Context { viewport .input .consume_accesskit_action_requests(res.id, |request| { + use accesskit::Action; + // TODO(lucasmerlin): Correctly handle the scroll unit: // https://github.com/AccessKit/accesskit/blob/e639c0e0d8ccbfd9dff302d972fa06f9766d608e/common/src/lib.rs#L2621 const DISTANCE: f32 = 100.0; match &request.action { - accesskit::Action::ScrollIntoView => { + Action::ScrollIntoView => { viewport.this_pass.scroll_target = [ Some(ScrollTarget::new( res.rect.x_range(), @@ -1239,16 +1237,16 @@ impl Context { )), ]; } - accesskit::Action::ScrollDown => { + Action::ScrollDown => { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::UP; } - accesskit::Action::ScrollUp => { + Action::ScrollUp => { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::DOWN; } - accesskit::Action::ScrollLeft => { + Action::ScrollLeft => { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::LEFT; } - accesskit::Action::ScrollRight => { + Action::ScrollRight => { viewport.this_pass.scroll_delta.0 += DISTANCE * Vec2::RIGHT; } _ => return false, @@ -1341,7 +1339,6 @@ impl Context { res.flags.set(Flags::FAKE_PRIMARY_CLICKED, true); } - #[cfg(feature = "accesskit")] if enabled && sense.senses_click() && input.has_accesskit_action_request(id, accesskit::Action::Click) @@ -2498,7 +2495,6 @@ impl ContextImpl { let mut platform_output: PlatformOutput = std::mem::take(&mut viewport.output); - #[cfg(feature = "accesskit")] { profiling::scope!("accesskit"); let state = viewport.this_pass.accesskit_state.take(); @@ -3497,9 +3493,8 @@ impl Context { /// /// The `Context` lock is held while the given closure is called! /// - /// Returns `None` if acesskit is off. + /// Returns `None` if accesskit is off. // TODO(emilk): consider making both read-only and read-write versions - #[cfg(feature = "accesskit")] pub fn accesskit_node_builder( &self, id: Id, @@ -3515,7 +3510,6 @@ impl Context { }) } - #[cfg(feature = "accesskit")] pub(crate) fn register_accesskit_parent(&self, id: Id, parent_id: Id) { self.write(|ctx| { if let Some(state) = ctx.viewport().this_pass.accesskit_state.as_mut() { @@ -3525,13 +3519,11 @@ impl Context { } /// Enable generation of AccessKit tree updates in all future frames. - #[cfg(feature = "accesskit")] pub fn enable_accesskit(&self) { self.write(|ctx| ctx.is_accesskit_enabled = true); } /// Disable generation of AccessKit tree updates in all future frames. - #[cfg(feature = "accesskit")] pub fn disable_accesskit(&self) { self.write(|ctx| ctx.is_accesskit_enabled = false); } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 869945ece..61f6ae00c 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -548,7 +548,6 @@ pub enum Event { WindowFocused(bool), /// An assistive technology (e.g. screen reader) requested an action. - #[cfg(feature = "accesskit")] AccessKitActionRequest(accesskit::ActionRequest), /// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`]. diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index deec5162d..2c6edba84 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -128,7 +128,6 @@ pub struct PlatformOutput { /// The difference in the widget tree since last frame. /// /// NOTE: this needs to be per-viewport. - #[cfg(feature = "accesskit")] pub accesskit_update: Option, /// How many ui passes is this the sum of? @@ -175,7 +174,6 @@ impl PlatformOutput { mut events, mutable_text_under_cursor, ime, - #[cfg(feature = "accesskit")] accesskit_update, num_completed_passes, mut request_discard_reasons, @@ -190,12 +188,8 @@ impl PlatformOutput { self.request_discard_reasons .append(&mut request_discard_reasons); - #[cfg(feature = "accesskit")] - { - // egui produces a complete AccessKit tree for each frame, - // so overwrite rather than appending. - self.accesskit_update = accesskit_update; - } + // egui produces a complete AccessKit tree for each frame, so overwrite rather than append: + self.accesskit_update = accesskit_update; } /// Take everything ephemeral (everything except `cursor_icon` currently) diff --git a/crates/egui/src/id.rs b/crates/egui/src/id.rs index 0565dc567..7484930c8 100644 --- a/crates/egui/src/id.rs +++ b/crates/egui/src/id.rs @@ -79,7 +79,6 @@ impl Id { self.0.get() } - #[cfg(feature = "accesskit")] pub(crate) fn accesskit_id(&self) -> accesskit::NodeId { self.value().into() } diff --git a/crates/egui/src/input_state/mod.rs b/crates/egui/src/input_state/mod.rs index 7b163a90f..0c99b6ac3 100644 --- a/crates/egui/src/input_state/mod.rs +++ b/crates/egui/src/input_state/mod.rs @@ -855,7 +855,6 @@ impl InputState { } } - #[cfg(feature = "accesskit")] pub fn accesskit_action_requests( &self, id: crate::Id, @@ -873,7 +872,6 @@ impl InputState { }) } - #[cfg(feature = "accesskit")] pub fn consume_accesskit_action_requests( &mut self, id: crate::Id, @@ -890,12 +888,10 @@ impl InputState { }); } - #[cfg(feature = "accesskit")] pub fn has_accesskit_action_request(&self, id: crate::Id, action: accesskit::Action) -> bool { self.accesskit_action_requests(id, action).next().is_some() } - #[cfg(feature = "accesskit")] pub fn num_accesskit_action_requests(&self, id: crate::Id, action: accesskit::Action) -> usize { self.accesskit_action_requests(id, action).count() } @@ -1267,6 +1263,11 @@ impl PointerState { self.press_origin } + /// How far has the pointer moved since the start of the drag (if any)? + pub fn total_drag_delta(&self) -> Option { + Some(self.latest_pos? - self.press_origin?) + } + /// When did the current click/drag originate? /// `None` if no mouse button is down. #[inline(always)] diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 960480b23..bd4319f0b 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -9,7 +9,7 @@ //! which uses [`eframe`](https://docs.rs/eframe). //! //! To create a GUI using egui you first need a [`Context`] (by convention referred to by `ctx`). -//! Then you add a [`Window`] or a [`SidePanel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need. +//! Then you add a [`Window`] or a [`Panel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need. //! //! //! ## Feature flags @@ -45,7 +45,7 @@ //! //! ### Getting a [`Ui`] //! -//! Use one of [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`] to +//! Use one of [`Panel`], [`CentralPanel`], [`Window`] or [`Area`] to //! get access to an [`Ui`] where you can put widgets. For example: //! //! ``` @@ -322,7 +322,7 @@ //! when you release the panel/window shrinks again. //! This is an artifact of immediate mode, and here are some alternatives on how to avoid it: //! -//! 1. Turn off resizing with [`Window::resizable`], [`SidePanel::resizable`], [`TopBottomPanel::resizable`]. +//! 1. Turn off resizing with [`Window::resizable`], [`Panel::resizable`]. //! 2. Wrap your panel contents in a [`ScrollArea`], or use [`Window::vscroll`] and [`Window::hscroll`]. //! 3. Use a justified layout: //! @@ -448,7 +448,6 @@ pub mod widgets; #[cfg(debug_assertions)] mod callstack; -#[cfg(feature = "accesskit")] pub use accesskit; #[deprecated = "Use the ahash crate directly."] @@ -708,7 +707,6 @@ pub fn __run_test_ui(add_contents: impl Fn(&mut Ui)) { }); } -#[cfg(feature = "accesskit")] pub fn accesskit_root_id() -> Id { Id::new("accesskit_root") } diff --git a/crates/egui/src/memory/mod.rs b/crates/egui/src/memory/mod.rs index ddc5a9ffe..d215a3bec 100644 --- a/crates/egui/src/memory/mod.rs +++ b/crates/egui/src/memory/mod.rs @@ -470,7 +470,6 @@ pub(crate) struct Focus { /// The ID of a widget to give the focus to in the next frame. id_next_frame: Option, - #[cfg(feature = "accesskit")] id_requested_by_accesskit: Option, /// If set, the next widget that is interested in focus will automatically get it. @@ -529,10 +528,7 @@ impl Focus { } let event_filter = self.focused_widget.map(|w| w.filter).unwrap_or_default(); - #[cfg(feature = "accesskit")] - { - self.id_requested_by_accesskit = None; - } + self.id_requested_by_accesskit = None; self.focus_direction = FocusDirection::None; @@ -567,16 +563,13 @@ impl Focus { self.focus_direction = cardinality; } - #[cfg(feature = "accesskit")] + if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest { + action: accesskit::Action::Focus, + target, + data: None, + }) = event { - if let crate::Event::AccessKitActionRequest(accesskit::ActionRequest { - action: accesskit::Action::Focus, - target, - data: None, - }) = event - { - self.id_requested_by_accesskit = Some(*target); - } + self.id_requested_by_accesskit = Some(*target); } } } @@ -606,14 +599,11 @@ impl Focus { } fn interested_in_focus(&mut self, id: Id) { - #[cfg(feature = "accesskit")] - { - if self.id_requested_by_accesskit == Some(id.accesskit_id()) { - self.focused_widget = Some(FocusWidget::new(id)); - self.id_requested_by_accesskit = None; - self.give_to_next = false; - self.reset_focus(); - } + if self.id_requested_by_accesskit == Some(id.accesskit_id()) { + self.focused_widget = Some(FocusWidget::new(id)); + self.id_requested_by_accesskit = None; + self.give_to_next = false; + self.reset_focus(); } // The rect is updated at the end of the frame. @@ -1282,8 +1272,7 @@ impl Areas { pub fn top_layer_id(&self, order: Order) -> Option { self.order .iter() - .filter(|layer| layer.order == order && !self.is_sublayer(layer)) - .next_back() + .rfind(|layer| layer.order == order && !self.is_sublayer(layer)) .copied() } diff --git a/crates/egui/src/menu.rs b/crates/egui/src/menu.rs index 348f42c21..e5fb04b0d 100644 --- a/crates/egui/src/menu.rs +++ b/crates/egui/src/menu.rs @@ -84,7 +84,7 @@ fn set_menu_style(style: &mut Style) { } } -/// The menu bar goes well in a [`crate::TopBottomPanel::top`], +/// The menu bar goes well in a [`crate::Panel::top`], /// but can also be placed in a [`crate::Window`]. /// In the latter case you may want to wrap it in [`Frame`]. #[deprecated = "Use `egui::MenuBar::new().ui(` instead"] @@ -634,7 +634,7 @@ impl SubMenu { /// Usually you don't need to use it directly. pub struct MenuState { /// The opened sub-menu and its [`Id`] - sub_menu: Option<(Id, Arc>)>, + sub_menu: Option<(Id, Arc>)>, /// Bounding box of this menu (without the sub-menu), /// including the frame and everything. diff --git a/crates/egui/src/pass_state.rs b/crates/egui/src/pass_state.rs index 2be7e5098..9b323bfa0 100644 --- a/crates/egui/src/pass_state.rs +++ b/crates/egui/src/pass_state.rs @@ -67,7 +67,6 @@ impl ScrollTarget { } } -#[cfg(feature = "accesskit")] #[derive(Clone)] pub struct AccessKitPassState { pub nodes: IdMap, @@ -225,7 +224,6 @@ pub struct PassState { /// as when swiping down on a touch-screen or track-pad with natural scrolling. pub scroll_delta: (Vec2, style::ScrollAnimation), - #[cfg(feature = "accesskit")] pub accesskit_state: Option, /// Highlight these widgets the next pass. @@ -247,7 +245,6 @@ impl Default for PassState { used_by_panels: Rect::NAN, scroll_target: [None, None], scroll_delta: (Vec2::default(), style::ScrollAnimation::none()), - #[cfg(feature = "accesskit")] accesskit_state: None, highlight_next_pass: Default::default(), @@ -270,7 +267,6 @@ impl PassState { used_by_panels, scroll_target, scroll_delta, - #[cfg(feature = "accesskit")] accesskit_state, highlight_next_pass, @@ -293,10 +289,7 @@ impl PassState { *debug_rect = None; } - #[cfg(feature = "accesskit")] - { - *accesskit_state = None; - } + *accesskit_state = None; highlight_next_pass.clear(); } diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index e17c1aff5..e89cb5252 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -396,7 +396,7 @@ impl Response { self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button)) } - /// If dragged, how many points were we dragged and in what direction? + /// If dragged, how many points were we dragged in since last frame? #[inline] pub fn drag_delta(&self) -> Vec2 { if self.dragged() { @@ -410,7 +410,22 @@ impl Response { } } - /// If dragged, how far did the mouse move? + /// If dragged, how many points have we been dragged since the start of the drag? + #[inline] + pub fn total_drag_delta(&self) -> Option { + if self.dragged() { + let mut delta = self.ctx.input(|i| i.pointer.total_drag_delta())?; + if let Some(from_global) = self.ctx.layer_transform_from_global(self.layer_id) { + delta *= from_global.scaling; + } + Some(delta) + } else { + None + } + } + + /// If dragged, how far did the mouse move since last frame? + /// /// This will use raw mouse movement if provided by the integration, otherwise will fall back to [`Response::drag_delta`] /// Raw mouse movement is unaccelerated and unclamped by screen boundaries, and does not relate to any position on the screen. /// This may be useful in certain situations such as draggable values and 3D cameras, where screen position does not matter. @@ -709,10 +724,9 @@ impl Response { /// ``` #[must_use] pub fn interact(&self, sense: Sense) -> Self { - if (self.sense | sense) == self.sense { - // Early-out: we already sense everything we need to sense. - return self.clone(); - } + // We could check here if the new Sense equals the old one to avoid the extra create_widget + // call. But that would break calling `interact` on a response from `Context::read_response` + // or `Ui::response`. (See https://github.com/emilk/egui/pull/7713 for more details.) self.ctx.create_widget( WidgetRect { @@ -793,7 +807,6 @@ impl Response { if let Some(event) = event { self.output_event(event); } else { - #[cfg(feature = "accesskit")] self.ctx.accesskit_node_builder(self.id, |builder| { self.fill_accesskit_node_from_widget_info(builder, make_info()); }); @@ -803,7 +816,6 @@ impl Response { } pub fn output_event(&self, event: crate::output::OutputEvent) { - #[cfg(feature = "accesskit")] self.ctx.accesskit_node_builder(self.id, |builder| { self.fill_accesskit_node_from_widget_info(builder, event.widget_info().clone()); }); @@ -814,7 +826,6 @@ impl Response { self.ctx.output_mut(|o| o.events.push(event)); } - #[cfg(feature = "accesskit")] pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::Node) { if !self.enabled() { builder.set_disabled(); @@ -833,7 +844,6 @@ impl Response { } } - #[cfg(feature = "accesskit")] fn fill_accesskit_node_from_widget_info( &self, builder: &mut accesskit::Node, @@ -908,14 +918,9 @@ impl Response { /// # }); /// ``` pub fn labelled_by(self, id: Id) -> Self { - #[cfg(feature = "accesskit")] self.ctx.accesskit_node_builder(self.id, |builder| { builder.push_labelled_by(id.accesskit_id()); }); - #[cfg(not(feature = "accesskit"))] - { - let _ = id; - } self } diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index 9982c05bb..b77536002 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -508,6 +508,12 @@ pub struct ScrollStyle { /// it more promiment. pub floating: bool, + /// Extra margin added around the contents of a [`crate::ScrollArea`]. + /// + /// The scroll bars will be either on top of this margin, or outside of it, + /// depending on the value of [`Self::floating`]. + pub content_margin: Margin, + /// The width of the scroll bars at it largest. pub bar_width: f32, @@ -591,6 +597,7 @@ impl ScrollStyle { pub fn solid() -> Self { Self { floating: false, + content_margin: Margin::ZERO, bar_width: 6.0, handle_min_length: 12.0, bar_inner_margin: 4.0, @@ -672,6 +679,9 @@ impl ScrollStyle { pub fn details_ui(&mut self, ui: &mut Ui) { let Self { floating, + + content_margin, + bar_width, handle_min_length, bar_inner_margin, @@ -695,6 +705,11 @@ impl ScrollStyle { ui.selectable_value(floating, true, "Floating"); }); + ui.horizontal(|ui| { + ui.label("Content margin:"); + content_margin.ui(ui); + }); + ui.horizontal(|ui| { ui.add(DragValue::new(bar_width).range(0.0..=32.0)); ui.label("Full bar width"); @@ -1824,6 +1839,10 @@ impl Spacing { ui.add(window_margin); ui.end_row(); + ui.label("ScrollArea margin"); + scroll.content_margin.ui(ui); + ui.end_row(); + ui.label("Menu margin"); ui.add(menu_margin); ui.end_row(); diff --git a/crates/egui/src/text_selection/accesskit_text.rs b/crates/egui/src/text_selection/accesskit_text.rs index 4d64229c5..974a334d0 100644 --- a/crates/egui/src/text_selection/accesskit_text.rs +++ b/crates/egui/src/text_selection/accesskit_text.rs @@ -42,8 +42,9 @@ pub fn update_accesskit_for_text_widget( for (row_index, row) in galley.rows.iter().enumerate() { let row_id = parent_id.with(row_index); - #[cfg(feature = "accesskit")] + ctx.register_accesskit_parent(row_id, parent_id); + ctx.accesskit_node_builder(row_id, |builder| { builder.set_role(accesskit::Role::TextRun); let rect = global_from_galley * row.rect_without_leading_space(); diff --git a/crates/egui/src/text_selection/cursor_range.rs b/crates/egui/src/text_selection/cursor_range.rs index 10980c581..a816f5f26 100644 --- a/crates/egui/src/text_selection/cursor_range.rs +++ b/crates/egui/src/text_selection/cursor_range.rs @@ -190,7 +190,6 @@ impl CCursorRange { .. } => self.on_key_press(os, galley, modifiers, *key), - #[cfg(feature = "accesskit")] Event::AccessKitActionRequest(accesskit::ActionRequest { action: accesskit::Action::SetTextSelection, target, @@ -220,7 +219,6 @@ impl CCursorRange { // ---------------------------------------------------------------------------- -#[cfg(feature = "accesskit")] fn ccursor_from_accesskit_text_position( id: Id, galley: &Galley, diff --git a/crates/egui/src/text_selection/label_text_selection.rs b/crates/egui/src/text_selection/label_text_selection.rs index 0405ca5da..bc2884441 100644 --- a/crates/egui/src/text_selection/label_text_selection.rs +++ b/crates/egui/src/text_selection/label_text_selection.rs @@ -624,7 +624,6 @@ impl LabelSelectionState { ); } - #[cfg(feature = "accesskit")] super::accesskit_text::update_accesskit_for_text_widget( ui.ctx(), response.id, diff --git a/crates/egui/src/text_selection/mod.rs b/crates/egui/src/text_selection/mod.rs index 8d0943d60..cbd51c31a 100644 --- a/crates/egui/src/text_selection/mod.rs +++ b/crates/egui/src/text_selection/mod.rs @@ -1,6 +1,5 @@ //! Helpers regarding text selection for labels and text edit. -#[cfg(feature = "accesskit")] pub mod accesskit_text; mod cursor_range; diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index d746b8fec..3c7fca2f3 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -119,7 +119,7 @@ impl Ui { /// Create a new top-level [`Ui`]. /// /// Normally you would not use this directly, but instead use - /// [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. + /// [`crate::Panel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. pub fn new(ctx: Context, id: Id, ui_builder: UiBuilder) -> Self { let UiBuilder { id_salt, @@ -133,7 +133,6 @@ impl Ui { sizing_pass, style, sense, - #[cfg(feature = "accesskit")] accessibility_parent, } = ui_builder; @@ -175,7 +174,6 @@ impl Ui { min_rect_already_remembered: false, }; - #[cfg(feature = "accesskit")] if let Some(accessibility_parent) = accessibility_parent { ui.ctx() .register_accesskit_parent(ui.unique_id, accessibility_parent); @@ -202,7 +200,6 @@ impl Ui { ui.set_invisible(); } - #[cfg(feature = "accesskit")] ui.ctx().accesskit_node_builder(ui.unique_id, |node| { node.set_role(accesskit::Role::GenericContainer); }); @@ -273,7 +270,6 @@ impl Ui { sizing_pass, style, sense, - #[cfg(feature = "accesskit")] accessibility_parent, } = ui_builder; @@ -343,7 +339,6 @@ impl Ui { child_ui.disable(); } - #[cfg(feature = "accesskit")] child_ui.ctx().register_accesskit_parent( child_ui.unique_id, accessibility_parent.unwrap_or(self.unique_id), @@ -363,7 +358,6 @@ impl Ui { true, ); - #[cfg(feature = "accesskit")] child_ui .ctx() .accesskit_node_builder(child_ui.unique_id, |node| { @@ -1129,7 +1123,6 @@ impl Ui { impl Ui { /// Check for clicks, drags and/or hover on a specific region of this [`Ui`]. pub fn interact(&self, rect: Rect, id: Id, sense: Sense) -> Response { - #[cfg(feature = "accesskit")] self.ctx().register_accesskit_parent(id, self.unique_id); self.ctx().create_widget( diff --git a/crates/egui/src/ui_builder.rs b/crates/egui/src/ui_builder.rs index 51b8ec8a5..686fdcb47 100644 --- a/crates/egui/src/ui_builder.rs +++ b/crates/egui/src/ui_builder.rs @@ -24,7 +24,6 @@ pub struct UiBuilder { pub sizing_pass: bool, pub style: Option>, pub sense: Option, - #[cfg(feature = "accesskit")] pub accessibility_parent: Option, } @@ -187,15 +186,9 @@ impl UiBuilder { /// /// This will override the automatic parent assignment for accessibility purposes. /// If not set, the parent [`Ui`]'s ID will be used as the accessibility parent. - /// - /// This does nothing if the `accesskit` feature is not enabled. - #[cfg_attr(not(feature = "accesskit"), expect(unused_mut, unused_variables))] #[inline] pub fn accessibility_parent(mut self, parent_id: Id) -> Self { - #[cfg(feature = "accesskit")] - { - self.accessibility_parent = Some(parent_id); - } + self.accessibility_parent = Some(parent_id); self } } diff --git a/crates/egui/src/ui_stack.rs b/crates/egui/src/ui_stack.rs index 0122f5681..07026c45b 100644 --- a/crates/egui/src/ui_stack.rs +++ b/crates/egui/src/ui_stack.rs @@ -12,16 +12,16 @@ pub enum UiKind { /// A [`crate::CentralPanel`]. CentralPanel, - /// A left [`crate::SidePanel`]. + /// A left [`crate::Panel`]. LeftPanel, - /// A right [`crate::SidePanel`]. + /// A right [`crate::Panel`]. RightPanel, - /// A top [`crate::TopBottomPanel`]. + /// A top [`crate::Panel`]. TopPanel, - /// A bottom [`crate::TopBottomPanel`]. + /// A bottom [`crate::Panel`]. BottomPanel, /// A modal [`crate::Modal`]. @@ -209,7 +209,7 @@ pub struct UiStack { pub layout_direction: Direction, pub min_rect: Rect, pub max_rect: Rect, - pub parent: Option>, + pub parent: Option>, } // these methods act on this specific node diff --git a/crates/egui/src/widgets/drag_value.rs b/crates/egui/src/widgets/drag_value.rs index 9515726c2..29d596201 100644 --- a/crates/egui/src/widgets/drag_value.rs +++ b/crates/egui/src/widgets/drag_value.rs @@ -489,27 +489,21 @@ impl Widget for DragValue<'_> { - input.count_and_consume_key(Modifiers::NONE, Key::ArrowDown) as f64; } - #[cfg(feature = "accesskit")] - { - use accesskit::Action; - change += input.num_accesskit_action_requests(id, Action::Increment) as f64 - - input.num_accesskit_action_requests(id, Action::Decrement) as f64; - } + use accesskit::Action; + change += input.num_accesskit_action_requests(id, Action::Increment) as f64 + - input.num_accesskit_action_requests(id, Action::Decrement) as f64; change }); - #[cfg(feature = "accesskit")] - { + ui.input(|input| { use accesskit::{Action, ActionData}; - ui.input(|input| { - for request in input.accesskit_action_requests(id, Action::SetValue) { - if let Some(ActionData::NumericValue(new_value)) = request.data { - value = new_value; - } + for request in input.accesskit_action_requests(id, Action::SetValue) { + if let Some(ActionData::NumericValue(new_value)) = request.data { + value = new_value; } - }); - } + } + }); if clamp_existing_to_range { value = clamp_value_to_range(value, range.clone()); @@ -669,7 +663,6 @@ impl Widget for DragValue<'_> { response.widget_info(|| WidgetInfo::drag_value(ui.is_enabled(), value)); - #[cfg(feature = "accesskit")] ui.ctx().accesskit_node_builder(response.id, |builder| { use accesskit::Action; // If either end of the range is unbounded, it's better diff --git a/crates/egui/src/widgets/slider.rs b/crates/egui/src/widgets/slider.rs index 7937e5897..129c41c3b 100644 --- a/crates/egui/src/widgets/slider.rs +++ b/crates/egui/src/widgets/slider.rs @@ -716,14 +716,11 @@ impl Slider<'_> { }); } - #[cfg(feature = "accesskit")] - { + ui.input(|input| { use accesskit::Action; - ui.input(|input| { - decrement += input.num_accesskit_action_requests(response.id, Action::Decrement); - increment += input.num_accesskit_action_requests(response.id, Action::Increment); - }); - } + decrement += input.num_accesskit_action_requests(response.id, Action::Decrement); + increment += input.num_accesskit_action_requests(response.id, Action::Increment); + }); let kb_step = increment as f32 - decrement as f32; @@ -759,17 +756,14 @@ impl Slider<'_> { self.set_value(new_value); } - #[cfg(feature = "accesskit")] - { + ui.input(|input| { use accesskit::{Action, ActionData}; - ui.input(|input| { - for request in input.accesskit_action_requests(response.id, Action::SetValue) { - if let Some(ActionData::NumericValue(new_value)) = request.data { - self.set_value(new_value); - } + for request in input.accesskit_action_requests(response.id, Action::SetValue) { + if let Some(ActionData::NumericValue(new_value)) = request.data { + self.set_value(new_value); } - }); - } + } + }); // Paint it: if ui.is_rect_visible(response.rect) { @@ -978,7 +972,6 @@ impl Slider<'_> { } response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text())); - #[cfg(feature = "accesskit")] ui.ctx().accesskit_node_builder(response.id, |builder| { use accesskit::Action; builder.set_min_numeric_value(*self.range.start()); diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index c0364e7ee..6f2da1baa 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -844,7 +844,6 @@ impl TextEdit<'_> { }); } - #[cfg(feature = "accesskit")] { let role = if password { accesskit::Role::PasswordInput diff --git a/crates/egui_demo_app/Cargo.toml b/crates/egui_demo_app/Cargo.toml index 8eb441ac5..7cde46383 100644 --- a/crates/egui_demo_app/Cargo.toml +++ b/crates/egui_demo_app/Cargo.toml @@ -23,7 +23,7 @@ crate-type = ["cdylib", "rlib"] [features] -default = ["glow", "persistence"] +default = ["wgpu", "persistence"] # image_viewer adds about 0.9 MB of WASM web_app = ["http", "persistence"] diff --git a/crates/egui_demo_app/src/accessibility_inspector.rs b/crates/egui_demo_app/src/accessibility_inspector.rs index f721b710c..9ba3a8082 100644 --- a/crates/egui_demo_app/src/accessibility_inspector.rs +++ b/crates/egui_demo_app/src/accessibility_inspector.rs @@ -1,12 +1,13 @@ +use std::mem; + use accesskit::{Action, ActionRequest, NodeId}; use accesskit_consumer::{FilterResult, Node, Tree, TreeChangeHandler}; + use eframe::epaint::text::TextWrapMode; -use egui::collapsing_header::CollapsingState; use egui::{ Button, Color32, Context, Event, Frame, FullOutput, Id, Key, KeyboardShortcut, Label, - Modifiers, RawInput, RichText, ScrollArea, SidePanel, TopBottomPanel, Ui, + Modifiers, Panel, RawInput, RichText, ScrollArea, Ui, collapsing_header::CollapsingState, }; -use std::mem; /// This [`egui::Plugin`] adds an inspector Panel. /// @@ -86,10 +87,10 @@ impl egui::Plugin for AccessibilityInspectorPlugin { ctx.enable_accesskit(); - SidePanel::right(Self::id()).show(ctx, |ui| { + Panel::right(Self::id()).show(ctx, |ui| { ui.heading("πŸ”Ž AccessKit Inspector"); if let Some(selected_node) = self.selected_node { - TopBottomPanel::bottom(Self::id().with("details_panel")) + Panel::bottom(Self::id().with("details_panel")) .frame(Frame::new()) .show_separator_line(false) .show_inside(ui, |ui| { diff --git a/crates/egui_demo_app/src/apps/http_app.rs b/crates/egui_demo_app/src/apps/http_app.rs index f16aa5969..2630fa862 100644 --- a/crates/egui_demo_app/src/apps/http_app.rs +++ b/crates/egui_demo_app/src/apps/http_app.rs @@ -61,7 +61,7 @@ impl Default for HttpApp { impl eframe::App for HttpApp { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| { + egui::Panel::bottom("http_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { ui.add(egui_demo_lib::egui_github_link_file!()) diff --git a/crates/egui_demo_app/src/apps/image_viewer.rs b/crates/egui_demo_app/src/apps/image_viewer.rs index c341d2385..052996eef 100644 --- a/crates/egui_demo_app/src/apps/image_viewer.rs +++ b/crates/egui_demo_app/src/apps/image_viewer.rs @@ -2,8 +2,6 @@ use egui::ImageFit; use egui::Slider; use egui::Vec2; use egui::emath::Rot2; -use egui::panel::Side; -use egui::panel::TopBottomSide; #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ImageViewer { @@ -52,7 +50,7 @@ impl Default for ImageViewer { impl eframe::App for ImageViewer { fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { - egui::TopBottomPanel::new(TopBottomSide::Top, "url bar").show(ctx, |ui| { + egui::Panel::top("url bar").show(ctx, |ui| { ui.horizontal_centered(|ui| { let label = ui.label("URI:"); ui.text_edit_singleline(&mut self.uri_edit_text) @@ -73,7 +71,7 @@ impl eframe::App for ImageViewer { }); }); - egui::SidePanel::new(Side::Left, "controls").show(ctx, |ui| { + egui::Panel::left("controls").show(ctx, |ui| { // uv ui.label("UV"); ui.add(Slider::new(&mut self.image_options.uv.min.x, 0.0..=1.0).text("min x")); diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index bec0aacf3..c118f280c 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -295,7 +295,7 @@ impl eframe::App for WrapApp { } let mut cmd = Command::Nothing; - egui::TopBottomPanel::top("wrap_app_top_bar") + egui::Panel::top("wrap_app_top_bar") .frame(egui::Frame::new().inner_margin(4)) .show(ctx, |ui| { ui.horizontal_wrapped(|ui| { @@ -341,7 +341,7 @@ impl WrapApp { let mut cmd = Command::Nothing; - egui::SidePanel::left("backend_panel") + egui::Panel::left("backend_panel") .resizable(false) .show_animated(ctx, is_open, |ui| { ui.add_space(4.0); diff --git a/crates/egui_demo_app/tests/snapshots/clock.png b/crates/egui_demo_app/tests/snapshots/clock.png index 39d9bb5ce..ec50255fd 100644 --- a/crates/egui_demo_app/tests/snapshots/clock.png +++ b/crates/egui_demo_app/tests/snapshots/clock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44a68dc4d3aeebeb2d296c5c8e03aac330e1e4552364084347b710326c88f70c -size 335794 +oid sha256:784cbcdfd8deaf61e7b663f9416d67724e6a6a189a20ba3351908aa5c5f2deff +size 336159 diff --git a/crates/egui_demo_app/tests/snapshots/custom3d.png b/crates/egui_demo_app/tests/snapshots/custom3d.png index deed497b1..3fbf0ab56 100644 --- a/crates/egui_demo_app/tests/snapshots/custom3d.png +++ b/crates/egui_demo_app/tests/snapshots/custom3d.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9a760fe4a695e6321f00e40bfa76fd0195bee7157a1217572765e3f146ea2cc -size 93640 +oid sha256:4cdde1dda0e64f584c769c72f5910a7035e6a4a86a074b590e88365f12570109 +size 94062 diff --git a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png index a039b8c24..b9d8b2f22 100644 --- a/crates/egui_demo_app/tests/snapshots/easymarkeditor.png +++ b/crates/egui_demo_app/tests/snapshots/easymarkeditor.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1670bbfc1f0a71e20cbbeb73625c148b680963bc503d9b48e9cc43e704d7c54 -size 181671 +oid sha256:824d941ea538fd44fc374f5df1893eee2309004c0ee5e69a97f1c84a74b2b423 +size 182128 diff --git a/crates/egui_demo_app/tests/snapshots/imageviewer.png b/crates/egui_demo_app/tests/snapshots/imageviewer.png index e1d518a96..fee7ad891 100644 --- a/crates/egui_demo_app/tests/snapshots/imageviewer.png +++ b/crates/egui_demo_app/tests/snapshots/imageviewer.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc9c22567b76193a7f6753c4217adb3c92afa921c488ba1cf2e14b403814e7ac -size 99841 +oid sha256:44ea7ac8c8e22eb51fbcb63f00c8510de0e6ae126d19ab44c5d708d979b5362b +size 100345 diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 8033539dd..179543680 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -236,7 +236,7 @@ impl DemoWindows { } fn mobile_top_bar(&mut self, ctx: &Context) { - egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { + egui::Panel::top("menu_bar").show(ctx, |ui| { menu::MenuBar::new() .config(menu::MenuConfig::new().style(StyleModifier::default())) .ui(ui, |ui| { @@ -262,10 +262,10 @@ impl DemoWindows { } fn desktop_ui(&mut self, ctx: &Context) { - egui::SidePanel::right("egui_demo_panel") + egui::Panel::right("egui_demo_panel") .resizable(false) - .default_width(160.0) - .min_width(160.0) + .default_size(160.0) + .min_size(160.0) .show(ctx, |ui| { ui.add_space(4.0); ui.vertical_centered(|ui| { @@ -289,7 +289,7 @@ impl DemoWindows { self.demo_list_ui(ui); }); - egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { + egui::Panel::top("menu_bar").show(ctx, |ui| { menu::MenuBar::new().ui(ui, |ui| { file_menu_button(ui); }); diff --git a/crates/egui_demo_lib/src/demo/misc_demo_window.rs b/crates/egui_demo_lib/src/demo/misc_demo_window.rs index bb62f1822..b502fa767 100644 --- a/crates/egui_demo_lib/src/demo/misc_demo_window.rs +++ b/crates/egui_demo_lib/src/demo/misc_demo_window.rs @@ -451,7 +451,7 @@ enum Action { #[derive(Clone, Default)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -struct Tree(Vec); +struct Tree(Vec); impl Tree { pub fn demo() -> Self { diff --git a/crates/egui_demo_lib/src/demo/panels.rs b/crates/egui_demo_lib/src/demo/panels.rs index f94513866..55771c1a1 100644 --- a/crates/egui_demo_lib/src/demo/panels.rs +++ b/crates/egui_demo_lib/src/demo/panels.rs @@ -22,9 +22,9 @@ impl crate::View for Panels { fn ui(&mut self, ui: &mut egui::Ui) { // Note that the order we add the panels is very important! - egui::TopBottomPanel::top("top_panel") + egui::Panel::top("top_panel") .resizable(true) - .min_height(32.0) + .min_size(32.0) .show_inside(ui, |ui| { egui::ScrollArea::vertical().show(ui, |ui| { ui.vertical_centered(|ui| { @@ -34,10 +34,10 @@ impl crate::View for Panels { }); }); - egui::SidePanel::left("left_panel") + egui::Panel::left("left_panel") .resizable(true) - .default_width(150.0) - .width_range(80.0..=200.0) + .default_size(150.0) + .size_range(80.0..=200.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Left Panel"); @@ -47,10 +47,10 @@ impl crate::View for Panels { }); }); - egui::SidePanel::right("right_panel") + egui::Panel::right("right_panel") .resizable(true) - .default_width(150.0) - .width_range(80.0..=200.0) + .default_size(150.0) + .size_range(80.0..=200.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Right Panel"); @@ -60,9 +60,9 @@ impl crate::View for Panels { }); }); - egui::TopBottomPanel::bottom("bottom_panel") + egui::Panel::bottom("bottom_panel") .resizable(false) - .min_height(0.0) + .min_size(0.0) .show_inside(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Bottom Panel"); diff --git a/crates/egui_demo_lib/src/demo/tooltips.rs b/crates/egui_demo_lib/src/demo/tooltips.rs index 0e391c553..cc474bd4d 100644 --- a/crates/egui_demo_lib/src/demo/tooltips.rs +++ b/crates/egui_demo_lib/src/demo/tooltips.rs @@ -35,7 +35,7 @@ impl crate::View for Tooltips { ui.add(crate::egui_github_link_file_line!()); }); - egui::SidePanel::right("scroll_test").show_inside(ui, |ui| { + egui::Panel::right("scroll_test").show_inside(ui, |ui| { ui.label( "The scroll area below has many labels with interactive tooltips. \ The purpose is to test that the tooltips close when you scroll.", diff --git a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs index 9a66b8bc5..28e12630e 100644 --- a/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs +++ b/crates/egui_demo_lib/src/easy_mark/easy_mark_editor.rs @@ -33,7 +33,7 @@ impl Default for EasyMarkEditor { impl EasyMarkEditor { pub fn panels(&mut self, ctx: &egui::Context) { - egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| { + egui::Panel::bottom("easy_mark_bottom").show(ctx, |ui| { let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true); ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| { ui.add(crate::egui_github_link_file!()) diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png b/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png index 58cc9f94b..dd2d414a4 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Input Test.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:aff927596be5db77349ec0bbdcc852a0b1467e94c2a553a740a383ae318bad18 +oid sha256:bb3f7b5f790830b46d1410c2bbb5e19c6beb403f8fe979eb8d250fba4f89be3e size 51670 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png index 396d83508..919bdc66d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Misc Demos.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d6f055247034fa13ab55c9ec1fca275e6c23999c9a7e01c87af1fcc930faac6 -size 66777 +oid sha256:170cee9d72a4ab59aa2faf1b77aff4a9eee64f3380aa3f1b256340d88b1dabc2 +size 66525 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png index 22daed0ed..ca9bacfca 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Panels.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Panels.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c91f592571ba654d0a96791662ae7530a1db4c1630b57c795d1c006ea6e46f19 -size 256975 +oid sha256:f7a7d0e2618b852b5966073438c95cb62901d5410c1473639920b0b0bf2ec59b +size 256913 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Table.png b/crates/egui_demo_lib/tests/snapshots/demos/Table.png index cf728bf3f..188c548d8 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Table.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Table.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4fbcca2b13c94769a62b44853b19f7e841bbb60c9197b3d0bf6e83ef9f8f76d1 -size 77815 +oid sha256:72f4c6fe4f5ec243506152027e1150f3069caf98511ceef92b8fea4f6a1563d5 +size 77614 diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png index 97fa6cebc..e782c983a 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Window Options.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a31f0c12bb70449136443f9086103bd5b46356eedc2bb93ae1b6b10684ab69ca -size 36285 +oid sha256:611a2d6c793a85eebe807b2ddd4446cc0bc21e4284343dd756e64f0232fb6815 +size 35991 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_2.png b/crates/egui_demo_lib/tests/snapshots/modals_2.png index c8e7cb55a..0aa16858a 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_2.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:53c1509f7be264ed2947cd4ec0f10b555e9f710e949ed6fd8a73ca8ade53abd4 -size 48570 +oid sha256:e7bc441559ff2d8723cf344113ce5ff8158e41179e4c93abcacbe7b1b13b3723 +size 48998 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_3.png b/crates/egui_demo_lib/tests/snapshots/modals_3.png index 777b700c2..eaae2a758 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_3.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20eecafb998f69c2384afabc27eec1f97f413d603ece944adae9a99139be0b58 -size 44689 +oid sha256:3e092be54efaeb700a63d9b679894647159f39a0d3062692ac7056e98242cbee +size 45364 diff --git a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png index 700eaf46b..8b54bf99f 100644 --- a/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png +++ b/crates/egui_demo_lib/tests/snapshots/modals_backdrop_should_prevent_focusing_lower_area.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a7f8282946761e6ab40193267e47a9421f5642bae67458a9aadb71ac1231c8f -size 44581 +oid sha256:88930779ac199e42fcc9ee25f29bd120478c129807713218370b617905340087 +size 45366 diff --git a/crates/egui_extras/CHANGELOG.md b/crates/egui_extras/CHANGELOG.md index 726a1759c..9dbeb5879 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.33.2 - 2025-11-13 +Nothing new + + ## 0.33.0 - 2025-10-09 * Fix: use unique id for resize columns in `Table` [#7414](https://github.com/emilk/egui/pull/7414) by [@zezic](https://github.com/zezic) * Feat: Add serde serialization to SyntectSettings [#7506](https://github.com/emilk/egui/pull/7506) by [@bircni](https://github.com/bircni) diff --git a/crates/egui_glow/CHANGELOG.md b/crates/egui_glow/CHANGELOG.md index 34c2133e9..4dd90a359 100644 --- a/crates/egui_glow/CHANGELOG.md +++ b/crates/egui_glow/CHANGELOG.md @@ -6,6 +6,10 @@ Changes since the last release can be found at for GlowApp { self.egui_glow.as_mut().unwrap().run( self.gl_window.as_mut().unwrap().window(), |egui_ctx| { - egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| { + egui::Panel::left("my_side_panel").show(egui_ctx, |ui| { ui.heading("Hello World!"); if ui.button("Quit").clicked() { quit = true; diff --git a/crates/egui_kittest/CHANGELOG.md b/crates/egui_kittest/CHANGELOG.md index aab4d9d27..09fe5c4f1 100644 --- a/crates/egui_kittest/CHANGELOG.md +++ b/crates/egui_kittest/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.2 - 2025-11-13 +Nothing new + + ## 0.33.1 - 2025-10-15 * Add `egui_kittest::HarnessBuilder::with_options` [#7638](https://github.com/emilk/egui/pull/7638) by [@emilk](https://github.com/emilk) diff --git a/crates/egui_kittest/Cargo.toml b/crates/egui_kittest/Cargo.toml index a81413840..1de8ce7ac 100644 --- a/crates/egui_kittest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "egui_kittest" -version = "0.33.1" +version.workspace = true authors = ["Lucas Meurer ", "Emil Ernerfeldt "] description = "Testing library for egui based on kittest and AccessKit" edition.workspace = true @@ -35,7 +35,7 @@ x11 = ["eframe?/x11"] [dependencies] kittest.workspace = true -egui = { workspace = true, features = ["accesskit"] } +egui.workspace = true eframe = { workspace = true, optional = true } # wgpu dependencies diff --git a/crates/egui_kittest/src/builder.rs b/crates/egui_kittest/src/builder.rs index cc686914f..09b91d26d 100644 --- a/crates/egui_kittest/src/builder.rs +++ b/crates/egui_kittest/src/builder.rs @@ -4,6 +4,7 @@ use egui::{Pos2, Rect, Vec2}; use std::marker::PhantomData; /// Builder for [`Harness`]. +#[must_use] pub struct HarnessBuilder { pub(crate) screen_rect: Rect, pub(crate) pixels_per_point: f32, @@ -64,8 +65,10 @@ impl HarnessBuilder { /// Set the default options used for snapshot tests on this harness. #[cfg(feature = "snapshot")] - pub fn with_options(&mut self, options: crate::SnapshotOptions) { + #[inline] + pub fn with_options(mut self, options: crate::SnapshotOptions) -> Self { self.default_snapshot_options = options; + self } /// Override the [`egui::os::OperatingSystem`] reported to egui. diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index 33a188ea4..fc8b8efbc 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -657,7 +657,28 @@ impl<'a, State> Harness<'a, State> { /// Returns an error if the rendering fails. #[cfg(any(feature = "wgpu", feature = "snapshot"))] pub fn render(&mut self) -> Result { - self.renderer.render(&self.ctx, &self.output) + let mut output = self.output.clone(); + + if let Some(mouse_pos) = self.ctx.input(|i| i.pointer.hover_pos()) { + // Paint a mouse cursor: + let triangle = vec![ + mouse_pos, + mouse_pos + egui::vec2(16.0, 8.0), + mouse_pos + egui::vec2(8.0, 16.0), + ]; + + output.shapes.push(ClippedShape { + clip_rect: self.ctx.content_rect(), + shape: egui::epaint::PathShape::convex_polygon( + triangle, + Color32::WHITE, + egui::Stroke::new(1.0, Color32::BLACK), + ) + .into(), + }); + } + + self.renderer.render(&self.ctx, &output) } /// Get the root viewport output diff --git a/crates/egui_kittest/tests/snapshots/combobox_opened.png b/crates/egui_kittest/tests/snapshots/combobox_opened.png index aaa7198ce..e45a4aed3 100644 --- a/crates/egui_kittest/tests/snapshots/combobox_opened.png +++ b/crates/egui_kittest/tests/snapshots/combobox_opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f70ef032c241cd63675a246de07886c5c822e6fe21525b3a6d3fee106a589c9 -size 7501 +oid sha256:42911cbb500fa49170aac0da8e4167641c5d7c9724a6accd4d400258fc74e2d7 +size 8061 diff --git a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png index d2969adee..c30b3fdd1 100644 --- a/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png +++ b/crates/egui_kittest/tests/snapshots/menu/closed_hovered.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd6e159a462dde10240c4ca51da5ca5badfb7fc170bad97a59106babb72f8ae3 -size 10795 +oid sha256:94ba2e648c981bf4afbd9b9d01eef0708f7067be6e4cefbdfacc13aa219c6289 +size 11253 diff --git a/crates/egui_kittest/tests/snapshots/menu/opened.png b/crates/egui_kittest/tests/snapshots/menu/opened.png index 30f26b446..7a2750454 100644 --- a/crates/egui_kittest/tests/snapshots/menu/opened.png +++ b/crates/egui_kittest/tests/snapshots/menu/opened.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8f2a5873350f85457d599c1fd165ac756ed69758e7647e160c64f44d2f35c804 -size 21812 +oid sha256:436999f511dce318f29172f0b7e2007e1f0fedae58f5e0e85e19f1d8e0bee361 +size 22273 diff --git a/crates/egui_kittest/tests/snapshots/menu/submenu.png b/crates/egui_kittest/tests/snapshots/menu/submenu.png index 96ffaf97c..25453c8d9 100644 --- a/crates/egui_kittest/tests/snapshots/menu/submenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/submenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:facc05c499745594ac286f15645e40447633a176058337cad9edcb850ad578c7 -size 29379 +oid sha256:28435faf5c8c6d880cd50d52050c9f4cd6b992d0c621f01ca28fb5502eed16a1 +size 29863 diff --git a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png index d1d0b4cd3..c22c2b9b6 100644 --- a/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png +++ b/crates/egui_kittest/tests/snapshots/menu/subsubmenu.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f23ff8c6782befdbe7bd5f076dcdda15c38555f8e505282369bf52e43938c1b -size 34194 +oid sha256:f9a364b4b8c4ad3e78a80b0c6825d9de28c0e0d2e18dcfcd0ff18652ca86c859 +size 34750 diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png index 2c8565718..f58e6faec 100644 --- a/crates/egui_kittest/tests/snapshots/readme_example.png +++ b/crates/egui_kittest/tests/snapshots/readme_example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bb8d702361987803995c0f557ce94552a87b97dcd25bed5ee39af4c0e6090700 -size 1904 +oid sha256:87c76a9d07174e4e24ad3d08585c1df7bf3628bdc8f183d11beb6f9e14c4b2ec +size 2325 diff --git a/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png index 8ff6bba67..4d00c924a 100644 --- a/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png +++ b/crates/egui_kittest/tests/snapshots/test_tooltip_shown.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c267530452adb4f1ed1440df476d576ef4c2d96e6c58068bb57fed4615f5e113 -size 4453 +oid sha256:e269ede9c0784d00c153d51a13566d9c8f0d61ce11565997691fa63be06ec889 +size 5075 diff --git a/crates/emath/CHANGELOG.md b/crates/emath/CHANGELOG.md index b7e0fec1f..334af345f 100644 --- a/crates/emath/CHANGELOG.md +++ b/crates/emath/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to the `emath` crate will be noted in this file. This file is updated upon each release. Changes since the last release can be found at or by running the `scripts/generate_changelog.py` script. + +## 0.33.2 - 2025-11-13 +* Fix edge cases in "smart aiming" in sliders [#7680](https://github.com/emilk/egui/pull/7680) by [@emilk](https://github.com/emilk) + + ## 0.33.0 - 2025-10-09 * Add `emath::fast_midpoint` [#7435](https://github.com/emilk/egui/pull/7435) by [@emilk](https://github.com/emilk) * Generate changelogs for emath [#7513](https://github.com/emilk/egui/pull/7513) by [@lucasmerlin](https://github.com/lucasmerlin) diff --git a/crates/epaint/CHANGELOG.md b/crates/epaint/CHANGELOG.md index 0524b87c0..b23c454b4 100644 --- a/crates/epaint/CHANGELOG.md +++ b/crates/epaint/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.2 - 2025-11-13 +Nothing new + + ## 0.33.0 - 2025-10-09 * Remove the `deadlock_detection` feature [#7497](https://github.com/emilk/egui/pull/7497) by [@lucasmerlin](https://github.com/lucasmerlin) * More even text kerning [#7431](https://github.com/emilk/egui/pull/7431) by [@valadaptive](https://github.com/valadaptive) diff --git a/crates/epaint/src/shapes/shape.rs b/crates/epaint/src/shapes/shape.rs index 8ee852c61..fa8a3e75c 100644 --- a/crates/epaint/src/shapes/shape.rs +++ b/crates/epaint/src/shapes/shape.rs @@ -30,7 +30,7 @@ pub enum Shape { /// Recursively nest more shapes - sometimes a convenience to be able to do. /// For performance reasons it is better to avoid it. - Vec(Vec), + Vec(Vec), /// Circle with optional outline and fill. Circle(CircleShape), diff --git a/crates/epaint_default_fonts/CHANGELOG.md b/crates/epaint_default_fonts/CHANGELOG.md index bb39e4784..3f131cb55 100644 --- a/crates/epaint_default_fonts/CHANGELOG.md +++ b/crates/epaint_default_fonts/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.2 - 2025-11-13 +Nothing new + + ## 0.33.0 - 2025-10-09 Nothing new diff --git a/examples/hello_android/src/lib.rs b/examples/hello_android/src/lib.rs index c138b97e1..1de7684f5 100644 --- a/examples/hello_android/src/lib.rs +++ b/examples/hello_android/src/lib.rs @@ -41,7 +41,7 @@ impl eframe::App for MyApp { // TODO(lucasmerlin): This is a pretty big hack, should be fixed once safe_area implemented // for android: // https://github.com/rust-windowing/winit/issues/3910 - egui::TopBottomPanel::top("status_bar_space").show(ctx, |ui| { + egui::Panel::top("status_bar_space").show(ctx, |ui| { ui.set_height(32.0); }); diff --git a/lychee.toml b/lychee.toml index 71f49b5e4..21e91f2ef 100644 --- a/lychee.toml +++ b/lychee.toml @@ -42,4 +42,5 @@ accept = [ # Exclude URLs and mail addresses from checking (supports regex). exclude = [ "https://creativecommons.org/.*", # They don't like bots + "https://www.unicode.org/.*", ] diff --git a/scripts/build_demo_web.sh b/scripts/build_demo_web.sh index b6eb7197a..4ba3846db 100755 --- a/scripts/build_demo_web.sh +++ b/scripts/build_demo_web.sh @@ -13,13 +13,13 @@ OPEN=false OPTIMIZE=false BUILD=debug BUILD_FLAGS="" -WGPU=false +GLOW=false WASM_OPT_FLAGS="-O2 --fast-math" while test $# -gt 0; do case "$1" in -h|--help) - echo "build_demo_web.sh [--release] [--wgpu] [--open]" + echo "build_demo_web.sh [--release] [--glow] [--open]" echo "" echo " -g: Keep debug symbols even with --release." echo " These are useful profiling and size trimming." @@ -29,9 +29,7 @@ while test $# -gt 0; do echo " --release: Build with --release, and then run wasm-opt." echo " NOTE: --release also removes debug symbols, unless you also use -g." echo "" - echo " --wgpu: Build a binary using wgpu instead of glow/webgl." - echo " The resulting binary will automatically use WebGPU if available and" - echo " fall back to a WebGL emulation layer otherwise." + echo " --glow: Build a binary using glow instead of wgpu." exit 0 ;; @@ -52,9 +50,9 @@ while test $# -gt 0; do BUILD_FLAGS="--release" ;; - --wgpu) + --glow) shift - WGPU=true + GLOW=true ;; *) @@ -66,10 +64,10 @@ done OUT_FILE_NAME="egui_demo_app" -if [[ "${WGPU}" == true ]]; then - FEATURES="${FEATURES},wgpu" -else +if [[ "${GLOW}" == true ]]; then FEATURES="${FEATURES},glow" +else + FEATURES="${FEATURES},wgpu" fi FINAL_WASM_PATH=web_demo/${OUT_FILE_NAME}_bg.wasm diff --git a/scripts/wasm_bindgen_check.sh b/scripts/wasm_bindgen_check.sh index 5f90c99c6..5043d98e0 100755 --- a/scripts/wasm_bindgen_check.sh +++ b/scripts/wasm_bindgen_check.sh @@ -12,7 +12,7 @@ else fi CRATE_NAME="egui_demo_app" -FEATURES="glow,http,persistence" +FEATURES="wgpu,http,persistence" echo "Building rust…" BUILD=debug # debug builds are faster diff --git a/tests/egui_tests/tests/regression_tests.rs b/tests/egui_tests/tests/regression_tests.rs index 1ee197cb5..9e76394cb 100644 --- a/tests/egui_tests/tests/regression_tests.rs +++ b/tests/egui_tests/tests/regression_tests.rs @@ -1,5 +1,5 @@ use egui::accesskit::Role; -use egui::{Align, Color32, Image, Label, Layout, RichText, TextWrapMode, include_image}; +use egui::{Align, Color32, Image, Label, Layout, RichText, Sense, TextWrapMode, include_image}; use egui_kittest::Harness; use egui_kittest::kittest::Queryable as _; @@ -75,3 +75,46 @@ fn combobox_should_have_value() { Some("Option 1") ); } + +/// This test ensures that `ui.response().interact(...)` works correctly. +/// +/// This was broken, because there was an optimization in [`egui::Response::interact`] +/// which caused the [`Sense`] of the original response to flip-flop between `click` and `hover` +/// between frames. +/// +/// See for more details. +#[test] +fn interact_on_ui_response_should_be_stable() { + let mut first_frame = true; + let mut click_count = 0; + let mut harness = Harness::new_ui(|ui| { + let ui_response = ui.response(); + if !first_frame { + assert!( + ui_response.sense.contains(Sense::click()), + "ui.response() didn't have click sense even though we called interact(Sense::click()) last frame" + ); + } + + // Add a label so we have something to click with kittest + ui.add( + Label::new("senseless label") + .sense(Sense::hover()) + .selectable(false), + ); + + let click_response = ui_response.interact(Sense::click()); + if click_response.clicked() { + click_count += 1; + } + first_frame = false; + }); + + for i in 0..=10 { + harness.run_steps(i); + harness.get_by_label("senseless label").click(); + } + + drop(harness); + assert_eq!(click_count, 10, "We missed some clicks!"); +} diff --git a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png index 2b3ac7a50..038ce78db 100644 --- a/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png +++ b/tests/egui_tests/tests/snapshots/hovering_should_preserve_text_format.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cac533a01c65c8eef093efcd4c9036da50f898ea2436612990f4c2365c98ad83 -size 12126 +oid sha256:c83e094b1f0dede0195cc77f5caa3b7d13249364612b03c02f0ef5f2af5e28ad +size 12512 diff --git a/tests/egui_tests/tests/snapshots/visuals/button.png b/tests/egui_tests/tests/snapshots/visuals/button.png index 4c81f62fc..4204dd1d3 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button.png +++ b/tests/egui_tests/tests/snapshots/visuals/button.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c05992e16c1abf6d174fed73d19cad6bb2266e0adb87b8232e765d75fcf3f14 -size 10310 +oid sha256:6d0c3773bc3698fbd1bd1eb1aa1ed45938d5cb94696bfcec56e4e7e865871baf +size 11143 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image.png b/tests/egui_tests/tests/snapshots/visuals/button_image.png index 00582f3ae..5d1e74292 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7681d33a5a764187c084c966a4e47063136e2832094c44f62718447b1b3027ec -size 11292 +oid sha256:9764ab5549e0775380b1db3c9a9a1d47c6520bcd5b8781f922e97e3524c362aa +size 12133 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png index 1429bfd2d..b2f5646d3 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b99a82e9f3dfa24c079545272d680b55c4285c276befa0efc492fe273422f541 -size 14195 +oid sha256:4d0c7d4b161f7a1f9cadb3e285edcd08588b9e47e10c5579183c824ae4e7be1b +size 15170 diff --git a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png index c73effead..3f20c4379 100644 --- a/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png +++ b/tests/egui_tests/tests/snapshots/visuals/button_image_shortcut_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdf079228b762949dbc67308103f8fe1328b6c0175f312ccc492d4e86d42127b -size 13868 +oid sha256:65359fcb0f01627876e697684b185c60812dd1591b0f42174673712939e2f193 +size 14852 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox.png b/tests/egui_tests/tests/snapshots/visuals/checkbox.png index 16d88e546..2145ceee7 100644 --- a/tests/egui_tests/tests/snapshots/visuals/checkbox.png +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0bafe4c157696bfb52940b69501416d4da0b4eab52f34f52220d2e9ed01357cf -size 12901 +oid sha256:68347d7eb452a6f30fa93778f9ebd17f20c1425426472d3ebe4c8b55fc0ba8ea +size 13774 diff --git a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png index fd85297ac..ec012113b 100644 --- a/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png +++ b/tests/egui_tests/tests/snapshots/visuals/checkbox_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72175bf108135b422d978b701d29e6d9a5348c536e25abc924234bc11b6b7f21 -size 14016 +oid sha256:2c323b3b530be2c4ff195e369e86df49ef28de0696fb33a74361d9dbd95e37ae +size 14889 diff --git a/tests/egui_tests/tests/snapshots/visuals/drag_value.png b/tests/egui_tests/tests/snapshots/visuals/drag_value.png index 5411009f0..05f63136b 100644 --- a/tests/egui_tests/tests/snapshots/visuals/drag_value.png +++ b/tests/egui_tests/tests/snapshots/visuals/drag_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:129121534b5f1a2a668898ebb3560820fe50aa4d3546ef46cc764d5513787e9e -size 7529 +oid sha256:8a48d2014ed6295d61f3200389315662b89e7efba27a93fded255cce7bd21e05 +size 8675 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio.png b/tests/egui_tests/tests/snapshots/visuals/radio.png index e00b42d8c..298841d6a 100644 --- a/tests/egui_tests/tests/snapshots/visuals/radio.png +++ b/tests/egui_tests/tests/snapshots/visuals/radio.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9999c7921f8b277f456189ce0f1185120b4cde7c9a01485a5a7d83f12e95527 -size 11710 +oid sha256:cdaeee74db8c9527e6656b4a3026ed18cb58c4761f1155768a456d6d58dc79e2 +size 12549 diff --git a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png index 0021e7d0a..02590ce3d 100644 --- a/tests/egui_tests/tests/snapshots/visuals/radio_checked.png +++ b/tests/egui_tests/tests/snapshots/visuals/radio_checked.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8fec1fd9f80e5fa17b1cda690c0856e7e5fd674d113a10b1d60b14f5a6c6dd6b -size 12401 +oid sha256:3dfbfd35264e4d35a594c72ef0fb9575b090301e112a98228d3070fa85aa4e42 +size 13240 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png index a0b480be4..85cb2a451 100644 --- a/tests/egui_tests/tests/snapshots/visuals/selectable_value.png +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac18e2eef000a80858b2d0811f9ee31304c6ff96f7a91dc60cc1a404ae28ce38 -size 13246 +oid sha256:cbaa88e2769bd9dbffa9b3ced36585c00b4ad6ca91ae61a6becc63a495a812fc +size 14116 diff --git a/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png b/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png index 291263c44..8f0cfb4a9 100644 --- a/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png +++ b/tests/egui_tests/tests/snapshots/visuals/selectable_value_selected.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c11fe0399c85db5a618580ec4c1f2fe76176c6ea0ead3710a430d9a2bf8acc5d -size 13352 +oid sha256:b1bac7bec0c22e9530ef2428c4233be7a1c3554c653b6344a2d7b981c5455920 +size 14142 diff --git a/tests/egui_tests/tests/snapshots/visuals/slider.png b/tests/egui_tests/tests/snapshots/visuals/slider.png index 67b0b365b..fd9b15b73 100644 --- a/tests/egui_tests/tests/snapshots/visuals/slider.png +++ b/tests/egui_tests/tests/snapshots/visuals/slider.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a41bf44780feefa108a230ae617830445791bde16d712ac35530350d5d009481 -size 9045 +oid sha256:3667467ff1cf2ce210ec1e1555b40bba827008c5ee40d25ccaf082d2718c6d77 +size 10144 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit.png b/tests/egui_tests/tests/snapshots/visuals/text_edit.png index d8e56eb2a..649a05fc4 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a103b51df184d5480438e8b537106432205a6d86f2927ab1bd507fe8ed3bb29b -size 7656 +oid sha256:d06b03948190e2d6408c339b97ec3f3e2104ffc7da61f5935b7df8bb89c9d7aa +size 8813 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png index f44900fa5..70c4bfe8f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf4236b1a8f63d184cd780c334d9f996e4d47817a96a29f0d81658d2d897597f -size 10529 +oid sha256:b2be8ebcc7d8cc7b3824ae27c57969c0d1bc2d5affb8f3f9df687fb3d1860280 +size 11567 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png index 7329c49cf..a5bda4b8f 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_no_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7a63953853f526b83f80d63335b03e60258ea9a3416d19f8ed57d746b5c551d -size 21557 +oid sha256:934263e4413e48ea3abf8b53e213f3a61459b697b30cf05436e2d2e6a3d48e3c +size 22356 diff --git a/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png b/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png index e1a15cf7d..e49bb4414 100644 --- a/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png +++ b/tests/egui_tests/tests/snapshots/visuals/text_edit_placeholder_clip.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f7d802a4de7e30f8d254cab6d9ca127866c104c1738103bc4a579917e8f42d3 -size 9850 +oid sha256:eb3230e609246415501d89984bb59ee1dad1241b8054009e7a5108efe3965904 +size 10880 diff --git a/tests/test_size_pass/src/main.rs b/tests/test_size_pass/src/main.rs index 6bb293302..ce645eb98 100644 --- a/tests/test_size_pass/src/main.rs +++ b/tests/test_size_pass/src/main.rs @@ -9,7 +9,7 @@ fn main() -> eframe::Result { let options = eframe::NativeOptions::default(); eframe::run_simple_native("My egui App", options, move |ctx, _frame| { // A bottom panel to force the tooltips to consider if the fit below or under the widget: - egui::TopBottomPanel::bottom("bottom").show(ctx, |ui| { + egui::Panel::bottom("bottom").show(ctx, |ui| { ui.horizontal(|ui| { ui.vertical(|ui| { ui.label("Single tooltips:"); diff --git a/tests/test_ui_stack/src/main.rs b/tests/test_ui_stack/src/main.rs index 47b4ee5ca..bb2158297 100644 --- a/tests/test_ui_stack/src/main.rs +++ b/tests/test_ui_stack/src/main.rs @@ -34,7 +34,7 @@ impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { ctx.all_styles_mut(|style| style.interaction.tooltip_delay = 0.0); - egui::SidePanel::left("side_panel_left").show(ctx, |ui| { + egui::Panel::left("side_panel_left").show(ctx, |ui| { ui.heading("Information"); ui.label( "This is a demo/test environment of the `UiStack` feature. The tables display \ @@ -82,7 +82,7 @@ impl eframe::App for MyApp { }); }); - egui::SidePanel::right("side_panel_right").show(ctx, |ui| { + egui::Panel::right("side_panel_right").show(ctx, |ui| { egui::ScrollArea::both().auto_shrink(false).show(ui, |ui| { stack_ui(ui); @@ -170,7 +170,7 @@ impl eframe::App for MyApp { }); }); - egui::TopBottomPanel::bottom("bottom_panel") + egui::Panel::bottom("bottom_panel") .resizable(true) .show(ctx, |ui| { egui::ScrollArea::vertical() diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index ab31a4ece..a862dbd32 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -31,7 +31,7 @@ pub struct ViewportState { pub visible: bool, pub immediate: bool, pub title: String, - pub children: Vec>>, + pub children: Vec>>, } impl ViewportState {