diff --git a/.cargo/config.toml b/.cargo/config.toml index b18b6ce735d..9741f113e7d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,9 +1,2 @@ -# clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work -# https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html -# check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility -# we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93 -[target.wasm32-unknown-unknown] -rustflags = ["--cfg=web_sys_unstable_apis"] - [alias] xtask = "run --quiet --package xtask --" diff --git a/.github/workflows/deploy_web_demo.yml b/.github/workflows/deploy_web_demo.yml index 41203da390b..717266d6362 100644 --- a/.github/workflows/deploy_web_demo.yml +++ b/.github/workflows/deploy_web_demo.yml @@ -22,11 +22,7 @@ concurrency: cancel-in-progress: false env: - # web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses, - # as well as by the wasm32-backend of the wgpu crate. - # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html - # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html - RUSTFLAGS: --cfg=web_sys_unstable_apis -D warnings + RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings jobs: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8aaa18faa36..60dda31e1c1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,13 +1,9 @@ -on: [push, pull_request] +on: [ push, pull_request ] name: Rust env: - # web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses, - # as well as by the wasm32-backend of the wgpu crate. - # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html - # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html - RUSTFLAGS: --cfg=web_sys_unstable_apis -D warnings + RUSTFLAGS: -D warnings RUSTDOCFLAGS: -D warnings jobs: @@ -61,6 +57,10 @@ jobs: - name: check epaint --no-default-features run: cargo check --locked --no-default-features --lib -p epaint + # Regression test for https://github.com/emilk/egui/issues/4771 + - name: cargo check -p test_egui_extras_compilation + run: cargo check -p test_egui_extras_compilation + - name: Test doc-tests run: cargo test --doc --all-features @@ -108,7 +108,7 @@ jobs: - name: wasm-bindgen uses: jetli/wasm-bindgen-action@v0.1.0 with: - version: "0.2.92" + version: "0.2.93" - run: ./scripts/wasm_bindgen_check.sh --skip-setup @@ -167,6 +167,26 @@ jobs: # --------------------------------------------------------------------------- + ios: + name: ios + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.76.0 + targets: aarch64-apple-ios + + - name: Set up cargo cache + uses: Swatinem/rust-cache@v2 + + # Default features are disabled because glutin doesn't compile for ios. + - run: cargo check --features wgpu --target aarch64-apple-ios --no-default-features + working-directory: crates/eframe + + # --------------------------------------------------------------------------- + windows: name: Check Windows runs-on: windows-latest diff --git a/Cargo.lock b/Cargo.lock index c4a6affbfad..565fe828799 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,9 +632,9 @@ dependencies = [ [[package]] name = "calloop" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.6.0", "log", @@ -646,9 +646,9 @@ dependencies = [ [[package]] name = "calloop-wayland-source" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", "rustix 0.38.21", @@ -2235,9 +2235,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -3490,9 +3490,9 @@ dependencies = [ [[package]] name = "sctk-adwaita" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7555fcb4f753d095d734fdefebb0ad8c98478a21db500492d87c55913d3b0086" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", @@ -3627,9 +3627,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "smithay-client-toolkit" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.6.0", "calloop", @@ -3652,9 +3652,9 @@ dependencies = [ [[package]] name = "smithay-clipboard" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb62b280ce5a5cba847669933a0948d00904cf83845c944eae96a4738cea1a6" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" dependencies = [ "libc", "smithay-client-toolkit", @@ -3804,6 +3804,14 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test_egui_extras_compilation" +version = "0.1.0" +dependencies = [ + "eframe", + "egui_extras", +] + [[package]] name = "test_inline_glow_paint" version = "0.1.0" @@ -4208,19 +4216,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -4245,9 +4254,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4255,9 +4264,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -4268,9 +4277,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wayland-backend" @@ -4322,9 +4331,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.31.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -4334,9 +4343,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.2.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +checksum = "f79f2d57c7fcc6ab4d602adba364bf59a5c24de57bd194486bf9b8360e06bfc4" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -4347,9 +4356,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953" dependencies = [ "bitflags 2.6.0", "wayland-backend", @@ -4383,9 +4392,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -4432,9 +4441,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "wgpu" -version = "22.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c87e07e87a179614940ad845397e03201847453a37b43a31a3b54eee2e6e32ce" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" dependencies = [ "arrayvec", "cfg_aliases 0.1.1", @@ -4457,9 +4466,9 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "22.0.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f191908a21968991463fcf3b42cb6c9648c0fb7fa301b8fc733bc21a9ed9bd" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", "bit-vec 0.7.0", @@ -4849,9 +4858,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winit" -version = "0.30.2" +version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dc930d6cfbf53c4fe0b95689cdc2e17b8658c3f4214b9953298ccb5a1a15c90" +checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" dependencies = [ "ahash", "android-activity", diff --git a/Cargo.toml b/Cargo.toml index fd9690c603c..f1a4004952f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,14 +90,12 @@ thiserror = "1.0.37" web-time = "1.1.0" # Timekeeping for native and web wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" -web-sys = "0.3.58" -wgpu = { version = "22.0.0", default-features = false, features = [ +web-sys = "0.3.70" +wgpu = { version = "22.1.0", default-features = false, features = [ # Make the renderer `Sync` even on wasm32, because it makes the code simpler: "fragile-send-sync-non-atomic-wasm", ] } - -# Currently can't upgrade above 0.30.2 due to https://github.com/rust-windowing/winit/issues/3837 -winit = { version = "=0.30.2", default-features = false } +winit = { version = "0.30.5", default-features = false } [workspace.lints.rust] unsafe_code = "deny" @@ -120,6 +118,7 @@ unused_qualifications = "allow" [workspace.lints.rustdoc] all = "warn" missing_crate_level_docs = "warn" +broken_intra_doc_links = "warn" # See also clippy.toml [workspace.lints.clippy] @@ -250,6 +249,7 @@ use_self = "warn" useless_transmute = "warn" verbose_file_reads = "warn" wildcard_dependencies = "warn" +wildcard_imports = "warn" zero_sized_map_values = "warn" # TODO(emilk): enable more of these lints: @@ -264,4 +264,3 @@ unwrap_used = "allow" # TODO(emilk): We really wanna warn on thi manual_range_contains = "allow" # this one is just worse imho self_named_module_files = "allow" # Disabled waiting on https://github.com/rust-lang/rust-clippy/issues/9602 significant_drop_tightening = "allow" # Too many false positives -wildcard_imports = "allow" # we do this a lot in egui diff --git a/crates/ecolor/Cargo.toml b/crates/ecolor/Cargo.toml index 0fc5546453a..c1a10070c03 100644 --- a/crates/ecolor/Cargo.toml +++ b/crates/ecolor/Cargo.toml @@ -21,6 +21,7 @@ workspace = true [package.metadata.docs.rs] all-features = true +rustdoc-args = ["--generate-link-to-definition"] [lib] diff --git a/crates/ecolor/src/cint_impl.rs b/crates/ecolor/src/cint_impl.rs index 03758bba7ec..5d34e5e36f7 100644 --- a/crates/ecolor/src/cint_impl.rs +++ b/crates/ecolor/src/cint_impl.rs @@ -1,4 +1,4 @@ -use super::*; +use super::{linear_f32_from_linear_u8, linear_u8_from_linear_f32, Color32, Hsva, HsvaGamma, Rgba}; use cint::{Alpha, ColorInterop, EncodedSrgb, Hsv, LinearSrgb, PremultipliedAlpha}; // ---- Color32 ---- diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 0b18845c34a..1621458e961 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -21,8 +21,8 @@ include = [ [package.metadata.docs.rs] all-features = true -rustc-args = ["--cfg=web_sys_unstable_apis"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] +rustdoc-args = ["--generate-link-to-definition"] [lints] workspace = true diff --git a/crates/eframe/README.md b/crates/eframe/README.md index ac57d999844..5ffd427d038 100644 --- a/crates/eframe/README.md +++ b/crates/eframe/README.md @@ -28,8 +28,6 @@ You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[work You can opt-in to the using [`egui_wgpu`](https://github.com/emilk/egui/tree/master/crates/egui_wgpu) for rendering by enabling the `wgpu` feature and setting `NativeOptions::renderer` to `Renderer::Wgpu`. -To get copy-paste working on web, you need to compile with `export RUSTFLAGS=--cfg=web_sys_unstable_apis`. - ## 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. diff --git a/crates/eframe/src/epi.rs b/crates/eframe/src/epi.rs index ac6d0f45a24..3b303dce1ee 100644 --- a/crates/eframe/src/epi.rs +++ b/crates/eframe/src/epi.rs @@ -297,21 +297,6 @@ pub struct NativeOptions { #[cfg(any(feature = "glow", feature = "wgpu"))] pub renderer: Renderer, - /// Try to detect and follow the system preferred setting for dark vs light mode. - /// - /// The theme will automatically change when the dark vs light mode preference is changed. - /// - /// Does not work on Linux (see ). - /// - /// See also [`Self::default_theme`]. - pub follow_system_theme: bool, - - /// Which theme to use in case [`Self::follow_system_theme`] is `false` - /// or eframe fails to detect the system theme. - /// - /// Default: [`Theme::Dark`]. - pub default_theme: Theme, - /// This controls what happens when you close the main eframe window. /// /// If `true`, execution will continue after the eframe window is closed. @@ -417,8 +402,6 @@ impl Default for NativeOptions { #[cfg(any(feature = "glow", feature = "wgpu"))] renderer: Renderer::default(), - follow_system_theme: cfg!(target_os = "macos") || cfg!(target_os = "windows"), - default_theme: Theme::Dark, run_and_return: true, #[cfg(any(feature = "glow", feature = "wgpu"))] @@ -449,19 +432,6 @@ impl Default for NativeOptions { /// Options when using `eframe` in a web page. #[cfg(target_arch = "wasm32")] pub struct WebOptions { - /// Try to detect and follow the system preferred setting for dark vs light mode. - /// - /// See also [`Self::default_theme`]. - /// - /// Default: `true`. - pub follow_system_theme: bool, - - /// Which theme to use in case [`Self::follow_system_theme`] is `false` - /// or system theme detection fails. - /// - /// Default: `Theme::Dark`. - pub default_theme: Theme, - /// Sets the number of bits in the depth buffer. /// /// `egui` doesn't need the depth buffer, so the default value is 0. @@ -492,8 +462,6 @@ pub struct WebOptions { impl Default for WebOptions { fn default() -> Self { Self { - follow_system_theme: true, - default_theme: Theme::Dark, depth_buffer: 0, #[cfg(feature = "glow")] @@ -509,31 +477,6 @@ impl Default for WebOptions { // ---------------------------------------------------------------------------- -/// Dark or Light theme. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub enum Theme { - /// Dark mode: light text on a dark background. - Dark, - - /// Light mode: dark text on a light background. - Light, -} - -impl Theme { - /// Get the egui visuals corresponding to this theme. - /// - /// Use with [`egui::Context::set_visuals`]. - pub fn egui_visuals(self) -> egui::Visuals { - match self { - Self::Dark => egui::Visuals::dark(), - Self::Light => egui::Visuals::light(), - } - } -} - -// ---------------------------------------------------------------------------- - /// WebGL Context options #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -814,11 +757,6 @@ pub struct IntegrationInfo { #[cfg(target_arch = "wasm32")] pub web_info: WebInfo, - /// Does the OS use dark or light mode? - /// - /// `None` means "don't know". - pub system_theme: Option, - /// Seconds of cpu usage (in seconds) on the previous frame. /// /// This includes [`App::update`] as well as rendering (except for vsync waiting). diff --git a/crates/eframe/src/lib.rs b/crates/eframe/src/lib.rs index fa0e75d2aa6..d3849efa5d7 100644 --- a/crates/eframe/src/lib.rs +++ b/crates/eframe/src/lib.rs @@ -10,9 +10,6 @@ //! call [`crate::run_native`] from your `main.rs`, and/or use `eframe::WebRunner` from your `lib.rs`. //! //! ## Compiling for web -//! To get copy-paste working on web, you need to compile with -//! `export RUSTFLAGS=--cfg=web_sys_unstable_apis`. -//! //! You need to install the `wasm32` target with `rustup target add wasm32-unknown-unknown`. //! //! Build the `.wasm` using `cargo build --target wasm32-unknown-unknown` @@ -477,4 +474,4 @@ mod profiling_scopes { } #[allow(unused_imports)] -pub(crate) use profiling_scopes::*; +pub(crate) use profiling_scopes::{profile_function, profile_scope}; diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 5d02e2386e9..5f9d555e3a5 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -7,11 +7,12 @@ use winit::event_loop::ActiveEventLoop; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; -use egui::{DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId}; +use egui::{DeferredViewportUiCallback, ViewportBuilder, ViewportId}; use egui_winit::{EventResponse, WindowSettings}; -use crate::{epi, Theme}; +use crate::epi; +#[cfg_attr(target_os = "ios", allow(dead_code, unused_variables, unused_mut))] pub fn viewport_builder( egui_zoom_factor: f32, event_loop: &ActiveEventLoop, @@ -53,8 +54,10 @@ pub fn viewport_builder( if clamp_size_to_monitor_size { if let Some(initial_window_size) = viewport_builder.inner_size { - let initial_window_size = initial_window_size - .at_most(largest_monitor_point_size(egui_zoom_factor, event_loop)); + let initial_window_size = egui::NumExt::at_most( + initial_window_size, + largest_monitor_point_size(egui_zoom_factor, event_loop), + ); viewport_builder = viewport_builder.with_inner_size(initial_window_size); } } @@ -65,7 +68,10 @@ pub fn viewport_builder( #[cfg(not(target_os = "ios"))] if native_options.centered { crate::profile_scope!("center"); - if let Some(monitor) = event_loop.available_monitors().next() { + if let Some(monitor) = event_loop + .primary_monitor() + .or_else(|| event_loop.available_monitors().next()) + { let monitor_size = monitor .size() .to_logical::(egui_zoom_factor as f64 * monitor.scale_factor()); @@ -95,6 +101,7 @@ pub fn apply_window_settings( } } +#[cfg(not(target_os = "ios"))] fn largest_monitor_point_size(egui_zoom_factor: f32, event_loop: &ActiveEventLoop) -> egui::Vec2 { crate::profile_function!(); @@ -158,7 +165,6 @@ pub struct EpiIntegration { close: bool, can_drag_window: bool, - follow_system_theme: bool, #[cfg(feature = "persistence")] persist_window: bool, app_icon_setter: super::app_icon::AppTitleIconSetter, @@ -169,7 +175,6 @@ impl EpiIntegration { pub fn new( egui_ctx: egui::Context, window: &winit::window::Window, - system_theme: Option, app_name: &str, native_options: &crate::NativeOptions, storage: Option>, @@ -180,10 +185,7 @@ impl EpiIntegration { #[cfg(feature = "wgpu")] wgpu_render_state: Option, ) -> Self { let frame = epi::Frame { - info: epi::IntegrationInfo { - system_theme, - cpu_usage: None, - }, + info: epi::IntegrationInfo { cpu_usage: None }, storage, #[cfg(feature = "glow")] gl, @@ -217,7 +219,6 @@ impl EpiIntegration { pending_full_output: Default::default(), close: false, can_drag_window: false, - follow_system_theme: native_options.follow_system_theme, #[cfg(feature = "persistence")] persist_window: native_options.persist_window, app_icon_setter, @@ -241,22 +242,13 @@ impl EpiIntegration { use winit::event::{ElementState, MouseButton, WindowEvent}; - match event { - WindowEvent::Destroyed => { - log::debug!("Received WindowEvent::Destroyed"); - self.close = true; - } - WindowEvent::MouseInput { - button: MouseButton::Left, - state: ElementState::Pressed, - .. - } => self.can_drag_window = true, - WindowEvent::ThemeChanged(winit_theme) if self.follow_system_theme => { - let theme = theme_from_winit_theme(*winit_theme); - self.frame.info.system_theme = Some(theme); - self.egui_ctx.set_visuals(theme.egui_visuals()); - } - _ => {} + if let WindowEvent::MouseInput { + button: MouseButton::Left, + state: ElementState::Pressed, + .. + } = event + { + self.can_drag_window = true; } egui_winit.on_window_event(window, event) @@ -398,10 +390,3 @@ pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option Theme { - match theme { - winit::window::Theme::Dark => Theme::Dark, - winit::window::Theme::Light => Theme::Light, - } -} diff --git a/crates/eframe/src/native/glow_integration.rs b/crates/eframe/src/native/glow_integration.rs index 366b71cfd7a..599420e597d 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -1,4 +1,4 @@ -//! Note that this file contains code very similar to [`wgpu_integration`]. +//! Note that this file contains code very similar to [`super::wgpu_integration`]. //! When making changes to one you often also want to apply it to the other. //! //! This is also very complex code, and not very pretty. @@ -36,13 +36,13 @@ use egui::{ use egui_winit::accesskit_winit; use crate::{ - native::{epi_integration::EpiIntegration, winit_integration::create_egui_context}, - App, AppCreator, CreationContext, NativeOptions, Result, Storage, + native::epi_integration::EpiIntegration, App, AppCreator, CreationContext, NativeOptions, + Result, Storage, }; use super::{ - winit_integration::{EventResult, UserEvent, WinitApp}, - *, + epi_integration, event_loop_context, + winit_integration::{create_egui_context, EventResult, UserEvent, WinitApp}, }; // ---------------------------------------------------------------------------- @@ -228,14 +228,11 @@ impl GlowWinitApp { } } - let system_theme = - winit_integration::system_theme(&glutin.window(ViewportId::ROOT), &self.native_options); let painter = Rc::new(RefCell::new(painter)); let integration = EpiIntegration::new( egui_ctx, &glutin.window(ViewportId::ROOT), - system_theme, &self.app_name, &self.native_options, storage, @@ -281,9 +278,6 @@ impl GlowWinitApp { } } - let theme = system_theme.unwrap_or(self.native_options.default_theme); - integration.egui_ctx.set_visuals(theme.egui_visuals()); - if self .native_options .viewport @@ -468,6 +462,8 @@ impl WinitApp for GlowWinitApp { #[cfg(feature = "accesskit")] fn on_accesskit_event(&mut self, event: accesskit_winit::Event) -> crate::Result { + use super::winit_integration; + if let Some(running) = &self.running { let mut glutin = running.glutin.borrow_mut(); if let Some(viewport_id) = glutin.viewport_from_window.get(&event.window_id).copied() { @@ -809,6 +805,18 @@ impl GlowWinitRunning { } } + winit::event::WindowEvent::Destroyed => { + log::debug!( + "Received WindowEvent::Destroyed for viewport {:?}", + viewport_id + ); + if viewport_id == Some(ViewportId::ROOT) { + return EventResult::Exit; + } else { + return EventResult::Wait; + } + } + _ => {} } @@ -1120,6 +1128,7 @@ impl GlutinWindowContext { viewport_id, event_loop, Some(window.scale_factor() as f32), + event_loop.system_theme(), self.max_texture_side, ) }); @@ -1365,6 +1374,7 @@ fn initialize_or_update_viewport( ); viewport.window = None; viewport.egui_winit = None; + viewport.gl_surface = None; } viewport.deferred_commands.append(&mut delta_commands); diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 3c7e0003a41..cd4c458efc9 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, time::Instant}; +use std::time::Instant; use winit::{ application::ApplicationHandler, @@ -32,11 +32,12 @@ fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result( mut native_options: epi::NativeOptions, f: impl FnOnce(&mut EventLoop, epi::NativeOptions) -> R, ) -> Result { - thread_local!(static EVENT_LOOP: RefCell>> = RefCell::new(None)); + thread_local!(static EVENT_LOOP: std::cell::RefCell>> = std::cell::RefCell::new(None)); EVENT_LOOP.with(|event_loop| { // Since we want to reference NativeOptions when creating the EventLoop we can't @@ -169,16 +170,6 @@ impl WinitAppWrapper { }); if let Some(next_repaint_time) = next_repaint_time { - // WaitUntil seems to not work on iOS - #[cfg(target_os = "ios")] - winit_app - .window_id_from_viewport_id(egui::ViewportId::ROOT) - .map(|window_id| { - winit_app - .window(window_id) - .map(|window| window.request_redraw()) - }); - event_loop.set_control_flow(ControlFlow::WaitUntil(next_repaint_time)); }; } diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 74f4e957c70..9cbd9675e9c 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -1,4 +1,4 @@ -//! Note that this file contains code very similar to [`glow_integration`]. +//! Note that this file contains code very similar to [`super::glow_integration`]. //! When making changes to one you often also want to apply it to the other. //! //! This is also very complex code, and not very pretty. @@ -29,7 +29,7 @@ use crate::{ App, AppCreator, CreationContext, NativeOptions, Result, Storage, }; -use super::{winit_integration::WinitApp, *}; +use super::{epi_integration, event_loop_context, winit_integration, winit_integration::WinitApp}; // ---------------------------------------------------------------------------- // Types: @@ -158,6 +158,7 @@ impl WgpuWinitApp { ViewportClass::Root, self.native_options.viewport.clone(), None, + painter, ) .initialize_window(event_loop, egui_ctx, viewport_from_window, painter); } @@ -203,11 +204,9 @@ impl WgpuWinitApp { let wgpu_render_state = painter.render_state(); - let system_theme = winit_integration::system_theme(&window, &self.native_options); let integration = EpiIntegration::new( egui_ctx.clone(), &window, - system_theme, &self.app_name, &self.native_options, storage, @@ -243,6 +242,7 @@ impl WgpuWinitApp { ViewportId::ROOT, event_loop, Some(window.scale_factor() as f32), + event_loop.system_theme(), painter.max_texture_side(), ); @@ -251,8 +251,6 @@ impl WgpuWinitApp { let event_loop_proxy = self.repaint_proxy.lock().clone(); egui_winit.init_accesskit(&window, event_loop_proxy); } - let theme = system_theme.unwrap_or(self.native_options.default_theme); - egui_ctx.set_visuals(theme.egui_visuals()); let app_creator = std::mem::take(&mut self.app_creator) .expect("Single-use AppCreator has unexpectedly already been taken"); @@ -872,6 +870,7 @@ impl Viewport { viewport_id, event_loop, Some(window.scale_factor() as f32), + event_loop.system_theme(), painter.max_texture_side(), )); @@ -929,8 +928,14 @@ fn render_immediate_viewport( .. } = &mut *shared.borrow_mut(); - let viewport = - initialize_or_update_viewport(viewports, ids, ViewportClass::Immediate, builder, None); + let viewport = initialize_or_update_viewport( + viewports, + ids, + ViewportClass::Immediate, + builder, + None, + painter, + ); if viewport.window.is_none() { event_loop_context::with_current_event_loop(|event_loop| { viewport.initialize_window(event_loop, egui_ctx, viewport_from_window, painter); @@ -1053,7 +1058,7 @@ fn handle_viewport_output( let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent); let viewport = - initialize_or_update_viewport(viewports, ids, class, builder, viewport_ui_cb); + initialize_or_update_viewport(viewports, ids, class, builder, viewport_ui_cb, painter); if let Some(window) = viewport.window.as_ref() { let old_inner_size = window.inner_size(); @@ -1086,13 +1091,14 @@ fn handle_viewport_output( remove_viewports_not_in(viewports, painter, viewport_from_window, viewport_output); } -fn initialize_or_update_viewport( - viewports: &mut Viewports, +fn initialize_or_update_viewport<'a>( + viewports: &'a mut Viewports, ids: ViewportIdPair, class: ViewportClass, mut builder: ViewportBuilder, viewport_ui_cb: Option>, -) -> &mut Viewport { + painter: &mut egui_wgpu::winit::Painter, +) -> &'a mut Viewport { crate::profile_function!(); if builder.icon.is_none() { @@ -1137,6 +1143,12 @@ fn initialize_or_update_viewport( ); viewport.window = None; viewport.egui_winit = None; + if let Err(err) = pollster::block_on(painter.set_window(viewport.ids.this, None)) { + log::error!( + "when rendering viewport_id={:?}, set_window Error {err}", + viewport.ids.this + ); + } } viewport.deferred_commands.append(&mut delta_commands); diff --git a/crates/eframe/src/native/winit_integration.rs b/crates/eframe/src/native/winit_integration.rs index 1489b0bf40d..049c90a63ca 100644 --- a/crates/eframe/src/native/winit_integration.rs +++ b/crates/eframe/src/native/winit_integration.rs @@ -118,16 +118,6 @@ pub enum EventResult { Exit, } -pub fn system_theme(window: &Window, options: &crate::NativeOptions) -> Option { - if options.follow_system_theme { - window - .theme() - .map(super::epi_integration::theme_from_winit_theme) - } else { - None - } -} - #[cfg(feature = "accesskit")] pub(crate) fn on_accesskit_window_event( egui_winit: &mut egui_winit::State, diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 1a0c3f44c53..6ef30004482 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -38,18 +38,11 @@ impl AppRunner { ) -> Result { let painter = super::ActiveWebPainter::new(canvas, &web_options).await?; - let system_theme = if web_options.follow_system_theme { - super::system_theme() - } else { - None - }; - let info = epi::IntegrationInfo { web_info: epi::WebInfo { user_agent: super::user_agent().unwrap_or_default(), location: super::web_location(), }, - system_theme, cpu_usage: None, }; let storage = LocalStorage::default(); @@ -68,9 +61,6 @@ impl AppRunner { o.zoom_factor = 1.0; }); - let theme = system_theme.unwrap_or(web_options.default_theme); - egui_ctx.set_visuals(theme.egui_visuals()); - let cc = epi::CreationContext { egui_ctx: egui_ctx.clone(), integration_info: info.clone(), @@ -132,6 +122,7 @@ impl AppRunner { .entry(egui::ViewportId::ROOT) .or_default() .native_pixels_per_point = Some(super::native_pixels_per_point()); + runner.input.raw.system_theme = super::system_theme(); Ok(runner) } @@ -285,14 +276,10 @@ impl AppRunner { super::open_url(&open.url, open.new_tab); } - #[cfg(web_sys_unstable_apis)] if !copied_text.is_empty() { super::set_clipboard_text(&copied_text); } - #[cfg(not(web_sys_unstable_apis))] - let _ = copied_text; - if self.has_focus() { // The eframe app has focus. if ime.is_some() { @@ -305,7 +292,10 @@ impl AppRunner { } } - if let Err(err) = self.text_agent.move_to(ime, self.canvas()) { + if let Err(err) = self + .text_agent + .move_to(ime, self.canvas(), self.egui_ctx.zoom_factor()) + { log::error!( "failed to update text agent position: {}", super::string_from_js_value(&err) diff --git a/crates/eframe/src/web/events.rs b/crates/eframe/src/web/events.rs index e3696057d04..6755e2b6483 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -1,4 +1,9 @@ -use super::*; +use super::{ + button_from_mouse_event, location_hash, modifiers_from_kb_event, modifiers_from_mouse_event, + modifiers_from_wheel_event, pos_from_mouse_event, prefers_color_scheme_dark, primary_touch_pos, + push_touches, text_from_keyboard_event, theme_from_dark_mode, translate_key, AppRunner, + Closure, JsCast, JsValue, WebRunner, +}; use web_sys::EventTarget; // TODO(emilk): there are more calls to `prevent_default` and `stop_propagaton` @@ -94,6 +99,7 @@ pub(crate) fn install_event_handlers(runner_ref: &WebRunner) -> Result<(), JsVal install_wheel(runner_ref, &canvas)?; install_drag_and_drop(runner_ref, &canvas)?; install_window_events(runner_ref, &window)?; + install_color_scheme_change_event(runner_ref, &window)?; Ok(()) } @@ -278,7 +284,6 @@ pub(crate) fn on_keyup(event: web_sys::KeyboardEvent, runner: &mut AppRunner) { } fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), JsValue> { - #[cfg(web_sys_unstable_apis)] runner_ref.add_event_listener(target, "paste", |event: web_sys::ClipboardEvent, runner| { if let Some(data) = event.clipboard_data() { if let Ok(text) = data.get_data("text") { @@ -293,7 +298,6 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul } })?; - #[cfg(web_sys_unstable_apis)] runner_ref.add_event_listener(target, "cut", |event: web_sys::ClipboardEvent, runner| { if runner.input.raw.focused { runner.input.raw.events.push(egui::Event::Cut); @@ -310,7 +314,6 @@ fn install_copy_cut_paste(runner_ref: &WebRunner, target: &EventTarget) -> Resul event.prevent_default(); })?; - #[cfg(web_sys_unstable_apis)] runner_ref.add_event_listener(target, "copy", |event: web_sys::ClipboardEvent, runner| { if runner.input.raw.focused { runner.input.raw.events.push(egui::Event::Copy); @@ -353,17 +356,17 @@ fn install_window_events(runner_ref: &WebRunner, window: &EventTarget) -> Result Ok(()) } -pub(crate) fn install_color_scheme_change_event(runner_ref: &WebRunner) -> Result<(), JsValue> { - let window = web_sys::window().unwrap(); - - if let Some(media_query_list) = prefers_color_scheme_dark(&window)? { +fn install_color_scheme_change_event( + runner_ref: &WebRunner, + window: &web_sys::Window, +) -> Result<(), JsValue> { + if let Some(media_query_list) = prefers_color_scheme_dark(window)? { runner_ref.add_event_listener::( &media_query_list, "change", |event, runner| { let theme = theme_from_dark_mode(event.matches()); - runner.frame.info.system_theme = Some(theme); - runner.egui_ctx().set_visuals(theme.egui_visuals()); + runner.input.raw.system_theme = Some(theme); runner.needs_repaint.repaint_asap(); }, )?; @@ -575,6 +578,13 @@ fn install_touchend(runner_ref: &WebRunner, target: &EventTarget) -> Result<(), runner.needs_repaint.repaint_asap(); event.stop_propagation(); event.prevent_default(); + + // Fix virtual keyboard IOS + // Need call focus at the same time of event + if runner.text_agent.has_focus() { + runner.text_agent.set_focus(false); + runner.text_agent.set_focus(true); + } } } }) @@ -766,8 +776,8 @@ pub(crate) fn install_resize_observer(runner_ref: &WebRunner) -> Result<(), JsVa }) as Box); let observer = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())?; - let mut options = web_sys::ResizeObserverOptions::new(); - options.box_(web_sys::ResizeObserverBoxOptions::ContentBox); + let options = web_sys::ResizeObserverOptions::new(); + options.set_box(web_sys::ResizeObserverBoxOptions::ContentBox); if let Some(runner_lock) = runner_ref.try_lock() { observer.observe_with_options(runner_lock.canvas(), &options); drop(runner_lock); diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 519c4052876..746b619fb30 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -43,9 +43,11 @@ pub use backend::*; use wasm_bindgen::prelude::*; use web_sys::MediaQueryList; -use input::*; - -use crate::Theme; +use input::{ + button_from_mouse_event, modifiers_from_kb_event, modifiers_from_mouse_event, + modifiers_from_wheel_event, pos_from_mouse_event, primary_touch_pos, push_touches, + text_from_keyboard_event, translate_key, +}; // ---------------------------------------------------------------------------- @@ -103,7 +105,7 @@ pub fn native_pixels_per_point() -> f32 { /// Ask the browser about the preferred system theme. /// /// `None` means unknown. -pub fn system_theme() -> Option { +pub fn system_theme() -> Option { let dark_mode = prefers_color_scheme_dark(&web_sys::window()?) .ok()?? .matches(); @@ -114,11 +116,11 @@ fn prefers_color_scheme_dark(window: &web_sys::Window) -> Result Theme { +fn theme_from_dark_mode(dark_mode: bool) -> egui::Theme { if dark_mode { - Theme::Dark + egui::Theme::Dark } else { - Theme::Light + egui::Theme::Light } } @@ -170,26 +172,16 @@ fn set_cursor_icon(cursor: egui::CursorIcon) -> Option<()> { } /// Set the clipboard text. -#[cfg(web_sys_unstable_apis)] fn set_clipboard_text(s: &str) { if let Some(window) = web_sys::window() { - if let Some(clipboard) = window.navigator().clipboard() { - let promise = clipboard.write_text(s); - let future = wasm_bindgen_futures::JsFuture::from(promise); - let future = async move { - if let Err(err) = future.await { - log::error!("Copy/cut action failed: {}", string_from_js_value(&err)); - } - }; - wasm_bindgen_futures::spawn_local(future); - } else { - let is_secure_context = window.is_secure_context(); - if is_secure_context { - log::warn!("window.navigator.clipboard is null; can't copy text"); - } else { - log::warn!("window.navigator.clipboard is null; can't copy text, probably because we're not in a secure context. See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts"); + let promise = window.navigator().clipboard().write_text(s); + let future = wasm_bindgen_futures::JsFuture::from(promise); + let future = async move { + if let Err(err) = future.await { + log::error!("Copy/cut action failed: {}", string_from_js_value(&err)); } - } + }; + wasm_bindgen_futures::spawn_local(future); } } @@ -264,16 +256,3 @@ pub fn percent_decode(s: &str) -> String { .decode_utf8_lossy() .to_string() } - -/// Returns `true` if the app is likely running on a mobile device. -pub(crate) fn is_mobile() -> bool { - fn try_is_mobile() -> Option { - const MOBILE_DEVICE: [&str; 6] = - ["Android", "iPhone", "iPad", "iPod", "webOS", "BlackBerry"]; - - let user_agent = web_sys::window()?.navigator().user_agent().ok()?; - let is_mobile = MOBILE_DEVICE.iter().any(|&name| user_agent.contains(name)); - Some(is_mobile) - } - try_is_mobile().unwrap_or(false) -} diff --git a/crates/eframe/src/web/text_agent.rs b/crates/eframe/src/web/text_agent.rs index bc59d90563d..f0eb67d60e8 100644 --- a/crates/eframe/src/web/text_agent.rs +++ b/crates/eframe/src/web/text_agent.rs @@ -5,7 +5,7 @@ use std::cell::Cell; use wasm_bindgen::prelude::*; -use super::{is_mobile, AppRunner, WebRunner}; +use super::{AppRunner, WebRunner}; pub struct TextAgent { input: web_sys::HtmlInputElement, @@ -22,12 +22,17 @@ impl TextAgent { .create_element("input")? .dyn_into::()?; input.set_type("text"); + input.set_autofocus(true); + input.set_attribute("autocapitalize", "off")?; // append it to `` and hide it outside of the viewport let style = input.style(); - style.set_property("opacity", "0")?; + style.set_property("background-color", "transparent")?; + style.set_property("border", "none")?; + style.set_property("outline", "none")?; style.set_property("width", "1px")?; style.set_property("height", "1px")?; + style.set_property("caret-color", "transparent")?; style.set_property("position", "absolute")?; style.set_property("top", "0")?; style.set_property("left", "0")?; @@ -39,6 +44,12 @@ impl TextAgent { let input = input.clone(); move |event: web_sys::InputEvent, runner: &mut AppRunner| { let text = input.value(); + // Fix android virtual keyboard Gboard + // This removes the virtual keyboard's suggestion. + if !event.is_composing() { + input.blur().ok(); + input.focus().ok(); + } // if `is_composing` is true, then user is using IME, for example: emoji, pinyin, kanji, hangul, etc. // In that case, the browser emits both `input` and `compositionupdate` events, // and we need to ignore the `input` event. @@ -103,14 +114,8 @@ impl TextAgent { &self, ime: Option, canvas: &web_sys::HtmlCanvasElement, + zoom_factor: f32, ) -> Result<(), JsValue> { - // Mobile keyboards don't follow the text input it's writing to, - // instead typically being fixed in place on the bottom of the screen, - // so don't bother moving the text agent on mobile. - if is_mobile() { - return Ok(()); - } - // Don't move the text agent unless the position actually changed: if self.prev_ime_output.get() == ime { return Ok(()); @@ -119,14 +124,24 @@ impl TextAgent { let Some(ime) = ime else { return Ok(()) }; - let canvas_rect = super::canvas_content_rect(canvas); + let mut canvas_rect = super::canvas_content_rect(canvas); + // Fix for safari with virtual keyboard flapping position + if is_mobile_safari() { + canvas_rect.min.y = canvas.offset_top() as f32; + } let cursor_rect = ime.cursor_rect.translate(canvas_rect.min.to_vec2()); let style = self.input.style(); // This is where the IME input will point to: - style.set_property("left", &format!("{}px", cursor_rect.center().x))?; - style.set_property("top", &format!("{}px", cursor_rect.center().y))?; + style.set_property( + "left", + &format!("{}px", cursor_rect.center().x * zoom_factor), + )?; + style.set_property( + "top", + &format!("{}px", cursor_rect.center().y * zoom_factor), + )?; Ok(()) } @@ -173,3 +188,16 @@ impl Drop for TextAgent { self.input.remove(); } } + +/// Returns `true` if the app is likely running on a mobile device on navigator Safari. +fn is_mobile_safari() -> bool { + (|| { + let user_agent = web_sys::window()?.navigator().user_agent().ok()?; + let is_ios = user_agent.contains("iPhone") + || user_agent.contains("iPad") + || user_agent.contains("iPod"); + let is_safari = user_agent.contains("Safari"); + Some(is_ios && is_safari) + })() + .unwrap_or(false) +} diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index 12034089305..86b61b2a9c4 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -35,11 +35,6 @@ impl WebRunner { /// Will install a panic handler that will catch and log any panics #[allow(clippy::new_without_default)] pub fn new() -> Self { - #[cfg(not(web_sys_unstable_apis))] - log::warn!( - "eframe compiled without RUSTFLAGS='--cfg=web_sys_unstable_apis'. Copying text won't work." - ); - let panic_handler = PanicHandler::install(); Self { @@ -63,8 +58,6 @@ impl WebRunner { ) -> Result<(), JsValue> { self.destroy(); - let follow_system_theme = web_options.follow_system_theme; - let text_agent = TextAgent::attach(self)?; let runner = AppRunner::new(canvas, web_options, app_creator, text_agent).await?; @@ -83,10 +76,6 @@ impl WebRunner { { events::install_event_handlers(self)?; - if follow_system_theme { - events::install_color_scheme_change_event(self)?; - } - // The resize observer handles calling `request_animation_frame` to start the render loop. events::install_resize_observer(self)?; } diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 647016e302b..88b81b0c635 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -28,7 +28,7 @@ workspace = true [package.metadata.docs.rs] all-features = true - +rustdoc-args = ["--generate-link-to-definition"] [features] default = [] diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 1189c1e20ad..972351ee64c 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -433,4 +433,4 @@ mod profiling_scopes { } #[allow(unused_imports)] -pub(crate) use profiling_scopes::*; +pub(crate) use profiling_scopes::{profile_function, profile_scope}; diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index f0644ee1527..46db8821e02 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -96,17 +96,6 @@ pub struct Painter { surfaces: ViewportIdMap, } -impl Drop for Painter { - fn drop(&mut self) { - // Drop surfaces before dropping the render state. - // - // This is a workaround for a bug in wgpu 22.0.0. - // Fixed in https://github.com/gfx-rs/wgpu/pull/6052 - // Remove with wgpu 22.1.0 update! - self.surfaces.clear(); - } -} - impl Painter { /// Manages [`wgpu`] state, including surface state, required to render egui. /// diff --git a/crates/egui-winit/Cargo.toml b/crates/egui-winit/Cargo.toml index 95e8215b7d9..4472c70f552 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -18,7 +18,7 @@ workspace = true [package.metadata.docs.rs] all-features = true - +rustdoc-args = ["--generate-link-to-definition"] [features] default = ["clipboard", "links", "wayland", "winit/default", "x11"] @@ -80,7 +80,7 @@ serde = { workspace = true, optional = true } webbrowser = { version = "1.0.0", optional = true } [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] -smithay-clipboard = { version = "0.7.0", optional = true } +smithay-clipboard = { version = "0.7.2", optional = true } # The wayland-cursor normally selected doesn't properly enable all the features it uses # and thus doesn't compile as it is used in egui-winit. This is fixed upstream, so force diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index e2565794ae3..1a546794cb1 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -14,7 +14,7 @@ pub use accesskit_winit; pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; -use egui::{Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo}; +use egui::{Pos2, Rect, Theme, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportInfo}; pub use winit; pub mod clipboard; @@ -26,7 +26,7 @@ use ahash::HashSet; use raw_window_handle::HasDisplayHandle; #[allow(unused_imports)] -pub(crate) use profiling_scopes::*; +pub(crate) use profiling_scopes::{profile_function, profile_scope}; use winit::{ dpi::{PhysicalPosition, PhysicalSize}, @@ -111,6 +111,7 @@ impl State { viewport_id: ViewportId, display_target: &dyn HasDisplayHandle, native_pixels_per_point: Option, + theme: Option, max_texture_side: Option, ) -> Self { crate::profile_function!(); @@ -150,6 +151,7 @@ impl State { .entry(ViewportId::ROOT) .or_default() .native_pixels_per_point = native_pixels_per_point; + slf.egui_input.system_theme = theme.map(to_egui_theme); if let Some(max_texture_side) = max_texture_side { slf.set_max_texture_side(max_texture_side); @@ -341,8 +343,7 @@ impl State { // We use input_method_editor_started to manually insert CompositionStart // between Commits. match ime { - winit::event::Ime::Enabled => {} - winit::event::Ime::Preedit(_, None) => { + winit::event::Ime::Enabled => { self.ime_event_enable(); } winit::event::Ime::Preedit(text, Some(_cursor)) => { @@ -357,7 +358,7 @@ impl State { .push(egui::Event::Ime(egui::ImeEvent::Commit(text.clone()))); self.ime_event_disable(); } - winit::event::Ime::Disabled => { + winit::event::Ime::Disabled | winit::event::Ime::Preedit(_, None) => { self.ime_event_disable(); } }; @@ -404,6 +405,13 @@ impl State { consumed: false, } } + WindowEvent::ThemeChanged(winit_theme) => { + self.egui_input.system_theme = Some(to_egui_theme(*winit_theme)); + EventResponse { + repaint: true, + consumed: false, + } + } WindowEvent::HoveredFile(path) => { self.egui_input.hovered_files.push(egui::HoveredFile { path: Some(path.clone()), @@ -463,7 +471,6 @@ impl State { | WindowEvent::Occluded(_) | WindowEvent::Resized(_) | WindowEvent::Moved(_) - | WindowEvent::ThemeChanged(_) | WindowEvent::TouchpadPressure { .. } | WindowEvent::CloseRequested => EventResponse { repaint: true, @@ -891,6 +898,13 @@ impl State { } } +fn to_egui_theme(theme: winit::window::Theme) -> Theme { + match theme { + winit::window::Theme::Dark => Theme::Dark, + winit::window::Theme::Light => Theme::Light, + } +} + pub fn inner_rect_in_points(window: &Window, pixels_per_point: f32) -> Option { let inner_pos_px = window.inner_position().ok()?; let inner_pos_px = egui::pos2(inner_pos_px.x as f32, inner_pos_px.y as f32); @@ -1593,7 +1607,11 @@ pub fn create_winit_window_attributes( .with_decorations(decorations.unwrap_or(true)) .with_resizable(resizable.unwrap_or(true)) .with_visible(visible.unwrap_or(true)) - .with_maximized(maximized.unwrap_or(false)) + .with_maximized(if cfg!(target_os = "ios") { + true + } else { + maximized.unwrap_or(false) + }) .with_window_level(match window_level.unwrap_or_default() { egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom, egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop, @@ -1617,6 +1635,7 @@ pub fn create_winit_window_attributes( }) .with_active(active.unwrap_or(true)); + #[cfg(not(target_os = "ios"))] if let Some(size) = inner_size { window_attributes = window_attributes.with_inner_size(PhysicalSize::new( pixels_per_point * size.x, @@ -1624,6 +1643,7 @@ pub fn create_winit_window_attributes( )); } + #[cfg(not(target_os = "ios"))] if let Some(size) = min_inner_size { window_attributes = window_attributes.with_min_inner_size(PhysicalSize::new( pixels_per_point * size.x, @@ -1631,6 +1651,7 @@ pub fn create_winit_window_attributes( )); } + #[cfg(not(target_os = "ios"))] if let Some(size) = max_inner_size { window_attributes = window_attributes.with_max_inner_size(PhysicalSize::new( pixels_per_point * size.x, @@ -1638,12 +1659,22 @@ pub fn create_winit_window_attributes( )); } + #[cfg(not(target_os = "ios"))] if let Some(pos) = position { window_attributes = window_attributes.with_position(PhysicalPosition::new( pixels_per_point * pos.x, pixels_per_point * pos.y, )); } + #[cfg(target_os = "ios")] + { + // Unused: + _ = pixels_per_point; + _ = position; + _ = inner_size; + _ = min_inner_size; + _ = max_inner_size; + } if let Some(icon) = icon { let winit_icon = to_winit_icon(&icon); diff --git a/crates/egui/Cargo.toml b/crates/egui/Cargo.toml index a920df2bf46..487a4eac6e9 100644 --- a/crates/egui/Cargo.toml +++ b/crates/egui/Cargo.toml @@ -18,6 +18,7 @@ workspace = true [package.metadata.docs.rs] all-features = true +rustdoc-args = ["--generate-link-to-definition"] [lib] diff --git a/crates/egui/src/containers/area.rs b/crates/egui/src/containers/area.rs index 062261af31b..378b0edc8d9 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -2,7 +2,10 @@ //! It has no frame or own size. It is potentially movable. //! It is the foundation for windows and popups. -use crate::*; +use crate::{ + emath, pos2, Align2, Context, Id, InnerResponse, LayerId, NumExt, Order, Pos2, Rect, Response, + Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState, +}; /// State of an [`Area`] that is persisted between frames. /// @@ -21,8 +24,8 @@ pub struct AreaState { /// /// Area size is intentionally NOT persisted between sessions, /// so that a bad tooltip or menu size won't be remembered forever. - /// A resizable [`Window`] remembers the size the user picked using - /// the state in the [`Resize`] container. + /// A resizable [`crate::Window`] remembers the size the user picked using + /// the state in the [`crate::Resize`] container. #[cfg_attr(feature = "serde", serde(skip))] pub size: Option, @@ -83,7 +86,7 @@ impl AreaState { /// An area on the screen that can be moved by dragging. /// -/// This forms the base of the [`Window`] container. +/// This forms the base of the [`crate::Window`] container. /// /// ``` /// # egui::__run_test_ctx(|ctx| { @@ -232,7 +235,7 @@ impl Area { /// If the contents are smaller than this size, the area will shrink to fit the contents. /// If the contents overflow, the area will grow. /// - /// If not set, [`style::Spacing::default_area_size`] will be used. + /// If not set, [`crate::style::Spacing::default_area_size`] will be used. #[inline] pub fn default_size(mut self, default_size: impl Into) -> Self { self.default_size = default_size.into(); @@ -532,16 +535,19 @@ impl Prepared { pub(crate) fn content_ui(&self, ctx: &Context) -> Ui { let max_rect = self.state.rect(); - let clip_rect = self.constrain_rect; // Don't paint outside our bounds + let mut ui_builder = UiBuilder::new() + .ui_stack_info(UiStackInfo::new(self.kind)) + .max_rect(max_rect); - let mut ui = Ui::new( - ctx.clone(), - self.layer_id, - self.layer_id.id, - max_rect, - clip_rect, - UiStackInfo::new(self.kind), - ); + if !self.enabled { + ui_builder = ui_builder.disabled(); + } + if self.sizing_pass { + ui_builder = ui_builder.sizing_pass(); + } + + let mut ui = Ui::new(ctx.clone(), self.layer_id, self.layer_id.id, ui_builder); + ui.set_clip_rect(self.constrain_rect); // Don't paint outside our bounds if self.fade_in { if let Some(last_became_visible_at) = self.state.last_became_visible_at { @@ -556,12 +562,6 @@ impl Prepared { } } - if !self.enabled { - ui.disable(); - } - if self.sizing_pass { - ui.set_sizing_pass(); - } ui } diff --git a/crates/egui/src/containers/collapsing_header.rs b/crates/egui/src/containers/collapsing_header.rs index e0c298980f3..70fd3736bc0 100644 --- a/crates/egui/src/containers/collapsing_header.rs +++ b/crates/egui/src/containers/collapsing_header.rs @@ -1,6 +1,9 @@ use std::hash::Hash; -use crate::*; +use crate::{ + emath, epaint, pos2, remap, remap_clamp, vec2, Context, Id, InnerResponse, NumExt, Rect, + Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, WidgetInfo, WidgetText, WidgetType, +}; use epaint::Shape; #[derive(Clone, Copy, Debug)] @@ -15,7 +18,7 @@ pub(crate) struct InnerState { /// This is a a building block for building collapsing regions. /// -/// It is used by [`CollapsingHeader`] and [`Window`], but can also be used on its own. +/// It is used by [`CollapsingHeader`] and [`crate::Window`], but can also be used on its own. /// /// See [`CollapsingState::show_header`] for how to show a collapsing header with a custom header. #[derive(Clone, Debug)] diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index cb29f277644..75305425e42 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -1,6 +1,10 @@ use epaint::Shape; -use crate::{style::WidgetVisuals, *}; +use crate::{ + epaint, style::WidgetVisuals, vec2, Align2, Context, Id, InnerResponse, NumExt, Painter, + PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui, + UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType, +}; #[allow(unused_imports)] // Documentation use crate::style::Spacing; @@ -150,7 +154,7 @@ impl ComboBox { /// Controls the wrap mode used for the selected text. /// - /// By default, [`Ui::wrap_mode`] will be used, which can be overridden with [`Style::wrap_mode`]. + /// By default, [`Ui::wrap_mode`] will be used, which can be overridden with [`crate::Style::wrap_mode`]. /// /// Note that any `\n` in the text will always produce a new line. #[inline] @@ -425,7 +429,7 @@ fn button_frame( outer_rect.set_height(outer_rect.height().at_least(interact_size.y)); let inner_rect = outer_rect.shrink2(margin); - let mut content_ui = ui.child_ui(inner_rect, *ui.layout(), None); + let mut content_ui = ui.new_child(UiBuilder::new().max_rect(inner_rect)); add_contents(&mut content_ui); let mut outer_rect = content_ui.min_rect().expand2(margin); diff --git a/crates/egui/src/containers/frame.rs b/crates/egui/src/containers/frame.rs index 07cd679acea..432356a870f 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -1,7 +1,10 @@ //! Frame container -use crate::{layers::ShapeIdx, *}; -use epaint::*; +use crate::{ + epaint, layers::ShapeIdx, InnerResponse, Response, Sense, Style, Ui, UiBuilder, UiKind, + UiStackInfo, +}; +use epaint::{Color32, Margin, Rect, Rounding, Shadow, Shape, Stroke}; /// Add a background, frame and/or margin to a rectangular background of a [`Ui`]. /// @@ -250,10 +253,10 @@ impl Frame { inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x); inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y); - let content_ui = ui.child_ui( - inner_rect, - *ui.layout(), - Some(UiStackInfo::new(UiKind::Frame).with_frame(self)), + let content_ui = ui.new_child( + UiBuilder::new() + .ui_stack_info(UiStackInfo::new(UiKind::Frame).with_frame(self)) + .max_rect(inner_rect), ); // content_ui.set_clip_rect(outer_rect_bounds.shrink(self.stroke.width * 0.5)); // Can't do this since we don't know final size yet diff --git a/crates/egui/src/containers/panel.rs b/crates/egui/src/containers/panel.rs index 3b16c9c1fa8..d6c0321f110 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -3,7 +3,7 @@ //! Panels can either be a child of a [`Ui`] (taking up a portion of the parent) //! or be top-level (taking up a portion of the whole screen). //! -//! Together with [`Window`] and [`Area`]:s, top-level panels are +//! Together with [`crate::Window`] and [`crate::Area`]:s, top-level panels are //! the only places where you can put you widgets. //! //! The order in which you add panels matter! @@ -13,9 +13,12 @@ //! //! ⚠ Always add any [`CentralPanel`] last. //! -//! Add your [`Window`]:s after any top-level panels. +//! Add your [`crate::Window`]:s after any top-level panels. -use crate::*; +use crate::{ + lerp, vec2, Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt, + Rangef, Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, +}; fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 { ctx.animate_bool_responsive(id, is_expanded) @@ -135,9 +138,9 @@ impl SidePanel { /// If you want your panel to be resizable you also need a widget in it that /// takes up more space as you resize it, such as: /// * Wrapping text ([`Ui::horizontal_wrapped`]). - /// * A [`ScrollArea`]. - /// * A [`Separator`]. - /// * A [`TextEdit`]. + /// * A [`crate::ScrollArea`]. + /// * A [`crate::Separator`]. + /// * A [`crate::TextEdit`]. /// * … #[inline] pub fn resizable(mut self, resizable: bool) -> Self { @@ -261,14 +264,15 @@ impl SidePanel { } } - let mut panel_ui = ui.child_ui_with_id_source( - panel_rect, - Layout::top_down(Align::Min), - id, - Some(UiStackInfo::new(match side { - Side::Left => UiKind::LeftPanel, - Side::Right => UiKind::RightPanel, - })), + let mut panel_ui = ui.new_child( + UiBuilder::new() + .id_source(id) + .ui_stack_info(UiStackInfo::new(match side { + Side::Left => UiKind::LeftPanel, + Side::Right => UiKind::RightPanel, + })) + .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) @@ -325,6 +329,9 @@ impl SidePanel { ui.ctx().set_cursor_icon(cursor_icon); } + // Keep this rect snapped so that panel content can be pixel-perfect + let rect = ui.painter().round_rect_to_pixels(rect); + PanelState { rect }.store(ui.ctx(), id); { @@ -339,10 +346,14 @@ 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 - // In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel - // (hence the shrink). - let resize_x = side.opposite().side_x(rect.shrink(1.0)); - let resize_x = ui.painter().round_to_pixel(resize_x); + let resize_x = side.opposite().side_x(rect); + + // This makes it pixel-perfect for odd-sized strokes (width=1.0, width=3.0, etc) + let resize_x = ui.painter().round_to_pixel_center(resize_x); + + // We want the line exactly on the last pixel but rust rounds away from zero so we bring it back a bit for + // left-side panels + let resize_x = resize_x - if side == Side::Left { 1.0 } else { 0.0 }; ui.painter().vline(resize_x, panel_rect.y_range(), stroke); } @@ -367,15 +378,13 @@ impl SidePanel { let layer_id = LayerId::background(); let side = self.side; let available_rect = ctx.available_rect(); - let clip_rect = ctx.screen_rect(); let mut panel_ui = Ui::new( ctx.clone(), layer_id, self.id, - available_rect, - clip_rect, - UiStackInfo::default(), + UiBuilder::new().max_rect(available_rect), ); + panel_ui.set_clip_rect(ctx.screen_rect()); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); let rect = inner_response.response.rect; @@ -614,9 +623,9 @@ impl TopBottomPanel { /// If you want your panel to be resizable you also need a widget in it that /// takes up more space as you resize it, such as: /// * Wrapping text ([`Ui::horizontal_wrapped`]). - /// * A [`ScrollArea`]. - /// * A [`Separator`]. - /// * A [`TextEdit`]. + /// * A [`crate::ScrollArea`]. + /// * A [`crate::Separator`]. + /// * A [`crate::TextEdit`]. /// * … #[inline] pub fn resizable(mut self, resizable: bool) -> Self { @@ -634,7 +643,7 @@ impl TopBottomPanel { } /// The initial height of the [`TopBottomPanel`], including margins. - /// Defaults to [`style::Spacing::interact_size`].y, plus frame 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); @@ -750,14 +759,15 @@ impl TopBottomPanel { } } - let mut panel_ui = ui.child_ui_with_id_source( - panel_rect, - Layout::top_down(Align::Min), - id, - Some(UiStackInfo::new(match side { - TopBottomSide::Top => UiKind::TopPanel, - TopBottomSide::Bottom => UiKind::BottomPanel, - })), + let mut panel_ui = ui.new_child( + UiBuilder::new() + .id_source(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) @@ -814,6 +824,9 @@ impl TopBottomPanel { ui.ctx().set_cursor_icon(cursor_icon); } + // Keep this rect snapped so that panel content can be pixel-perfect + let rect = ui.painter().round_rect_to_pixels(rect); + PanelState { rect }.store(ui.ctx(), id); { @@ -828,10 +841,12 @@ impl TopBottomPanel { Stroke::NONE }; // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done - // In the meantime: nudge the line so its inside the panel, so it won't be covered by neighboring panel - // (hence the shrink). - let resize_y = side.opposite().side_y(rect.shrink(1.0)); - let resize_y = ui.painter().round_to_pixel(resize_y); + let resize_y = side.opposite().side_y(rect); + let resize_y = ui.painter().round_to_pixel_center(resize_y); + + // We want the line exactly on the last pixel but rust rounds away from zero so we bring it back a bit for + // top-side panels + let resize_y = resize_y - if side == TopBottomSide::Top { 1.0 } else { 0.0 }; ui.painter().hline(panel_rect.x_range(), resize_y, stroke); } @@ -857,15 +872,13 @@ impl TopBottomPanel { let available_rect = ctx.available_rect(); let side = self.side; - let clip_rect = ctx.screen_rect(); let mut panel_ui = Ui::new( ctx.clone(), layer_id, self.id, - available_rect, - clip_rect, - UiStackInfo::default(), // set by show_inside_dyn + UiBuilder::new().max_rect(available_rect), ); + panel_ui.set_clip_rect(ctx.screen_rect()); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); let rect = inner_response.response.rect; @@ -1043,7 +1056,7 @@ impl TopBottomPanel { /// /// ⚠ [`CentralPanel`] must be added after all other panels! /// -/// NOTE: Any [`Window`]s and [`Area`]s will cover the top-level [`CentralPanel`]. +/// NOTE: Any [`crate::Window`]s and [`crate::Area`]s will cover the top-level [`CentralPanel`]. /// /// See the [module level docs](crate::containers::panel) for more details. /// @@ -1091,10 +1104,11 @@ impl CentralPanel { let Self { frame } = self; let panel_rect = ui.available_rect_before_wrap(); - let mut panel_ui = ui.child_ui( - panel_rect, - Layout::top_down(Align::Min), - Some(UiStackInfo::new(UiKind::CentralPanel)), + let mut panel_ui = ui.new_child( + UiBuilder::new() + .ui_stack_info(UiStackInfo::new(UiKind::CentralPanel)) + .max_rect(panel_rect) + .layout(Layout::top_down(Align::Min)), ); panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475) @@ -1124,15 +1138,13 @@ impl CentralPanel { let layer_id = LayerId::background(); let id = Id::new((ctx.viewport_id(), "central_panel")); - let clip_rect = ctx.screen_rect(); let mut panel_ui = Ui::new( ctx.clone(), layer_id, id, - available_rect, - clip_rect, - UiStackInfo::default(), // set by show_inside_dyn + UiBuilder::new().max_rect(available_rect), ); + panel_ui.set_clip_rect(ctx.screen_rect()); let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents); diff --git a/crates/egui/src/containers/popup.rs b/crates/egui/src/containers/popup.rs index d7fc9354a21..959d653fffb 100644 --- a/crates/egui/src/containers/popup.rs +++ b/crates/egui/src/containers/popup.rs @@ -2,7 +2,11 @@ use frame_state::PerWidgetTooltipState; -use crate::*; +use crate::{ + frame_state, vec2, AboveOrBelow, Align, Align2, Area, AreaState, Context, Frame, Id, + InnerResponse, Key, LayerId, Layout, Order, Pos2, Rect, Response, Sense, Ui, UiKind, Vec2, + Widget, WidgetText, +}; // ---------------------------------------------------------------------------- @@ -322,14 +326,14 @@ pub fn was_tooltip_open_last_frame(ctx: &Context, widget_id: Id) -> bool { pub enum PopupCloseBehavior { /// Popup will be closed on click anywhere, inside or outside the popup. /// - /// It is used in [`ComboBox`]. + /// It is used in [`crate::ComboBox`]. CloseOnClick, /// Popup will be closed if the click happened somewhere else /// but in the popup's body CloseOnClickOutside, - /// Clicks will be ignored. Popup might be closed manually by calling [`Memory::close_popup`] + /// Clicks will be ignored. Popup might be closed manually by calling [`crate::Memory::close_popup`] /// or by pressing the escape button IgnoreClicks, } @@ -358,7 +362,7 @@ pub fn popup_below_widget( /// /// The opened popup will have a minimum width matching its parent. /// -/// You must open the popup with [`Memory::open_popup`] or [`Memory::toggle_popup`]. +/// You must open the popup with [`crate::Memory::open_popup`] or [`crate::Memory::toggle_popup`]. /// /// Returns `None` if the popup is not open. /// diff --git a/crates/egui/src/containers/resize.rs b/crates/egui/src/containers/resize.rs index 4980c9c623d..98518bcef11 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -1,4 +1,7 @@ -use crate::*; +use crate::{ + pos2, vec2, Align2, Color32, Context, CursorIcon, Id, NumExt, Rect, Response, Sense, Shape, Ui, + UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, +}; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -88,7 +91,7 @@ impl Resize { /// Preferred / suggested height. Actual height will depend on contents. /// /// Examples: - /// * if the contents is a [`ScrollArea`] then this decides the maximum size. + /// * if the contents is a [`crate::ScrollArea`] then this decides the maximum size. /// * if the contents is a canvas, this decides the height of it, /// * if the contents is text and buttons, then the `default_height` is ignored /// and the height is picked automatically.. @@ -270,10 +273,10 @@ impl Resize { content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); // Respect parent region - let mut content_ui = ui.child_ui( - inner_rect, - *ui.layout(), - Some(UiStackInfo::new(UiKind::Resize)), + let mut content_ui = ui.new_child( + UiBuilder::new() + .ui_stack_info(UiStackInfo::new(UiKind::Resize)) + .max_rect(inner_rect), ); content_ui.set_clip_rect(content_clip_rect); diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 3f0e3a2df35..0cac8181e10 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -1,6 +1,9 @@ #![allow(clippy::needless_range_loop)] -use crate::*; +use crate::{ + emath, epaint, frame_state, lerp, pos2, remap, remap_clamp, vec2, Context, Id, NumExt, Pos2, + Rangef, Rect, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b, +}; #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -157,7 +160,7 @@ impl ScrollBarVisibility { /// # }); /// ``` /// -/// You can scroll to an element using [`Response::scroll_to_me`], [`Ui::scroll_to_cursor`] and [`Ui::scroll_to_rect`]. +/// You can scroll to an element using [`crate::Response::scroll_to_me`], [`Ui::scroll_to_cursor`] and [`Ui::scroll_to_rect`]. #[derive(Clone, Debug)] #[must_use = "You should call .show()"] pub struct ScrollArea { @@ -367,7 +370,7 @@ impl ScrollArea { /// * If `false`, the scroll area will not respond to user scrolling. /// /// This can be used, for example, to optionally freeze scrolling while the user - /// is typing text in a [`TextEdit`] widget contained within the scroll area. + /// is typing text in a [`crate::TextEdit`] widget contained within the scroll area. /// /// This controls both scrolling directions. #[inline] @@ -560,10 +563,10 @@ impl ScrollArea { } let content_max_rect = Rect::from_min_size(inner_rect.min - state.offset, content_max_size); - let mut content_ui = ui.child_ui( - content_max_rect, - *ui.layout(), - Some(UiStackInfo::new(UiKind::ScrollArea)), + let mut content_ui = ui.new_child( + UiBuilder::new() + .ui_stack_info(UiStackInfo::new(UiKind::ScrollArea)) + .max_rect(content_max_rect), ); { @@ -572,10 +575,8 @@ impl ScrollArea { let mut content_clip_rect = ui.clip_rect(); for d in 0..2 { if scroll_enabled[d] { - if state.content_is_too_large[d] { - content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin; - content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin; - } + content_clip_rect.min[d] = inner_rect.min[d] - clip_rect_margin; + content_clip_rect.max[d] = inner_rect.max[d] + clip_rect_margin; } else { // Nice handling of forced resizing beyond the possible: content_clip_rect.max[d] = ui.clip_rect().max[d] - current_bar_use[d]; @@ -732,7 +733,7 @@ impl ScrollArea { let rect = Rect::from_x_y_ranges(ui.max_rect().x_range(), y_min..=y_max); - ui.allocate_ui_at_rect(rect, |viewport_ui| { + ui.allocate_new_ui(UiBuilder::new().max_rect(rect), |viewport_ui| { viewport_ui.skip_ahead_auto_ids(min_row); // Make sure we get consistent IDs. add_contents(viewport_ui, min_row..max_row) }) diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 15fc8d80572..c183a4dc731 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -3,10 +3,13 @@ use std::sync::Arc; use crate::collapsing_header::CollapsingState; -use crate::*; -use epaint::*; +use crate::{ + Align, Align2, Context, CursorIcon, Id, InnerResponse, LayerId, NumExt, Order, Response, Sense, + TextStyle, Ui, UiKind, Vec2b, WidgetRect, WidgetText, +}; +use epaint::{emath, pos2, vec2, Galley, Pos2, Rect, RectShape, Rounding, Shape, Stroke, Vec2}; -use super::*; +use super::{area, resize, Area, Frame, Resize, ScrollArea}; /// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default). /// @@ -436,9 +439,6 @@ impl<'open> Window<'open> { let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); // Keep the original inner margin for later use let window_margin = window_frame.inner_margin; - let border_padding = window_frame.stroke.width / 2.0; - // Add border padding to the inner margin to prevent it from covering the contents - window_frame.inner_margin += border_padding; let is_explicitly_closed = matches!(open, Some(false)); let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible()); @@ -528,7 +528,7 @@ impl<'open> Window<'open> { frame.content_ui.spacing_mut().item_spacing.y = title_content_spacing; let title_bar = if with_title_bar { - let title_bar = show_title_bar( + let title_bar = TitleBar::new( &mut frame.content_ui, title, show_close_button, @@ -572,9 +572,9 @@ impl<'open> Window<'open> { if let Some(title_bar) = title_bar { let mut title_rect = Rect::from_min_size( - outer_rect.min + vec2(border_padding, border_padding), + outer_rect.min, Vec2 { - x: outer_rect.size().x - border_padding * 2.0, + x: outer_rect.size().x, y: title_bar_height, }, ); @@ -584,9 +584,6 @@ impl<'open> Window<'open> { if on_top && area_content_ui.visuals().window_highlight_topmost { let mut round = window_frame.rounding; - // Eliminate the rounding gap between the title bar and the window frame - round -= border_padding; - if !is_collapsed { round.se = 0.0; round.sw = 0.0; @@ -600,7 +597,7 @@ impl<'open> Window<'open> { // Fix title bar separator line position if let Some(response) = &mut content_response { - response.rect.min.y = outer_rect.min.y + title_bar_height + border_padding; + response.rect.min.y = outer_rect.min.y + title_bar_height; } title_bar.ui( @@ -664,14 +661,10 @@ fn paint_resize_corner( } }; - // Adjust the corner offset to accommodate the stroke width and window rounding - let offset = if radius <= 2.0 && stroke.width < 2.0 { - 2.0 - } else { - // The corner offset is calculated to make the corner appear to be in the correct position - (2.0_f32.sqrt() * (1.0 + radius + stroke.width / 2.0) - radius) - * 45.0_f32.to_radians().cos() - }; + // Adjust the corner offset to accommodate for window rounding + let offset = + ((2.0_f32.sqrt() * (1.0 + radius) - radius) * 45.0_f32.to_radians().cos()).max(2.0); + let corner_size = Vec2::splat(ui.visuals().resize_corner_size); let corner_rect = corner.align_size_within_rect(corner_size, outer_rect); let corner_rect = corner_rect.translate(-offset * corner.to_sign()); // move away from corner @@ -1040,60 +1033,60 @@ struct TitleBar { rect: Rect, } -fn show_title_bar( - ui: &mut Ui, - title: WidgetText, - show_close_button: bool, - collapsing: &mut CollapsingState, - collapsible: bool, -) -> TitleBar { - let inner_response = ui.horizontal(|ui| { - let height = ui - .fonts(|fonts| title.font_height(fonts, ui.style())) - .max(ui.spacing().interact_size.y); - ui.set_min_height(height); - - let item_spacing = ui.spacing().item_spacing; - let button_size = Vec2::splat(ui.spacing().icon_width); +impl TitleBar { + fn new( + ui: &mut Ui, + title: WidgetText, + show_close_button: bool, + collapsing: &mut CollapsingState, + collapsible: bool, + ) -> Self { + let inner_response = ui.horizontal(|ui| { + let height = ui + .fonts(|fonts| title.font_height(fonts, ui.style())) + .max(ui.spacing().interact_size.y); + ui.set_min_height(height); - let pad = (height - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical) + let item_spacing = ui.spacing().item_spacing; + let button_size = Vec2::splat(ui.spacing().icon_width); - if collapsible { - ui.add_space(pad); - collapsing.show_default_button_with_size(ui, button_size); - } + let pad = (height - button_size.y) / 2.0; // calculated so that the icon is on the diagonal (if window padding is symmetrical) - let title_galley = title.into_galley( - ui, - Some(crate::TextWrapMode::Extend), - f32::INFINITY, - TextStyle::Heading, - ); + if collapsible { + ui.add_space(pad); + collapsing.show_default_button_with_size(ui, button_size); + } - let minimum_width = if collapsible || show_close_button { - // If at least one button is shown we make room for both buttons (since title is centered): - 2.0 * (pad + button_size.x + item_spacing.x) + title_galley.size().x - } else { - pad + title_galley.size().x + pad - }; - let min_rect = Rect::from_min_size(ui.min_rect().min, vec2(minimum_width, height)); - let id = ui.advance_cursor_after_rect(min_rect); + let title_galley = title.into_galley( + ui, + Some(crate::TextWrapMode::Extend), + f32::INFINITY, + TextStyle::Heading, + ); - TitleBar { - id, - title_galley, - min_rect, - rect: Rect::NAN, // Will be filled in later - } - }); + let minimum_width = if collapsible || show_close_button { + // If at least one button is shown we make room for both buttons (since title is centered): + 2.0 * (pad + button_size.x + item_spacing.x) + title_galley.size().x + } else { + pad + title_galley.size().x + pad + }; + let min_rect = Rect::from_min_size(ui.min_rect().min, vec2(minimum_width, height)); + let id = ui.advance_cursor_after_rect(min_rect); + + Self { + id, + title_galley, + min_rect, + rect: Rect::NAN, // Will be filled in later + } + }); - let title_bar = inner_response.inner; - let rect = inner_response.response.rect; + let title_bar = inner_response.inner; + let rect = inner_response.response.rect; - TitleBar { rect, ..title_bar } -} + Self { rect, ..title_bar } + } -impl TitleBar { /// Finishes painting of the title bar when the window content size already known. /// /// # Parameters @@ -1133,7 +1126,6 @@ impl TitleBar { let text_pos = emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top(); let text_pos = text_pos - self.title_galley.rect.min.to_vec2(); - let text_pos = text_pos - 1.5 * Vec2::Y; // HACK: center on x-height of text (looks better) ui.painter().galley( text_pos, self.title_galley.clone(), @@ -1147,6 +1139,7 @@ impl TitleBar { let stroke = ui.visuals().widgets.noninteractive.bg_stroke; // Workaround: To prevent border infringement, // the 0.1 value should ideally be calculated using TessellationOptions::feathering_size_in_pixels + // or we could support selectively disabling feathering on line caps let x_range = outer_rect.x_range().shrink(0.1); ui.painter().hline(x_range, y, stroke); } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index d1a05db4bc2..7225af6d1ca 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -4,24 +4,40 @@ use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration use containers::area::AreaState; use epaint::{ - emath::TSTransform, mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *, + emath, emath::TSTransform, mutex::RwLock, pos2, stats::PaintStats, tessellator, text::Fonts, + util::OrderedFloat, vec2, ClippedPrimitive, ClippedShape, Color32, ImageData, ImageDelta, Pos2, + Rect, TessellationOptions, TextureAtlas, TextureId, Vec2, }; use crate::{ animation_manager::AnimationManager, + containers, data::output::PlatformOutput, + epaint, frame_state::FrameState, - input_state::*, + hit_test, + input_state::{InputState, MultiTouchInfo, PointerEvent}, + interaction, layers::GraphicLayers, + load, load::{Bytes, Loaders, SizedTexture}, memory::Options, + menu, os::OperatingSystem, output::FullOutput, + resize, scroll_area, util::IdTypeMap, viewport::ViewportClass, - TextureHandle, ViewportCommand, *, + Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, + ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, + ModifierNames, NumExt, Order, Painter, RawInput, Response, RichText, ScrollArea, Sense, Style, + TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder, ViewportCommand, ViewportId, + ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, Widget, WidgetRect, WidgetText, }; +#[cfg(feature = "accesskit")] +use crate::IdMap; + use self::{hit_test::WidgetHits, interaction::InteractionSnapshot}; /// Information given to the backend about when it is time to repaint the ui. @@ -723,7 +739,7 @@ impl Context { /// Run the ui code for one frame. /// - /// Put your widgets into a [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`]. + /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. /// /// This will modify the internal reference to point to a new generation of [`Context`]. /// Any old clones of this [`Context`] will refer to the old [`Context`], which will not get new input. @@ -1224,7 +1240,7 @@ impl Context { /// This is called by [`Response::widget_info`], but can also be called directly. /// - /// With some debug flags it will store the widget info in [`WidgetRects`] for later display. + /// With some debug flags it will store the widget info in [`crate::WidgetRects`] for later display. #[inline] pub fn register_widget_info(&self, id: Id, make_info: impl Fn() -> crate::WidgetInfo) { #[cfg(debug_assertions)] @@ -1326,7 +1342,7 @@ impl Context { /// Format the given shortcut in a human-readable way (e.g. `Ctrl+Shift+X`). /// - /// Can be used to get the text for [`Button::shortcut_text`]. + /// Can be used to get the text for [`crate::Button::shortcut_text`]. pub fn format_shortcut(&self, shortcut: &KeyboardShortcut) -> String { let os = self.os(); @@ -1623,7 +1639,7 @@ impl Context { self.options_mut(|opt| opt.style = style.into()); } - /// The [`Visuals`] used by all subsequent windows, panels etc. + /// The [`crate::Visuals`] used by all subsequent windows, panels etc. /// /// You can also use [`Ui::visuals_mut`] to change the visuals of a single [`Ui`]. /// @@ -1656,7 +1672,7 @@ impl Context { /// The number of physical pixels for each logical point on this monitor. /// - /// This is given as input to egui via [`ViewportInfo::native_pixels_per_point`] + /// This is given as input to egui via [`crate::ViewportInfo::native_pixels_per_point`] /// and cannot be changed. #[inline(always)] pub fn native_pixels_per_point(&self) -> Option { @@ -1701,26 +1717,42 @@ impl Context { }); } - /// Useful for pixel-perfect rendering + /// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels). + #[inline] + pub(crate) fn round_to_pixel_center(&self, point: f32) -> f32 { + let pixels_per_point = self.pixels_per_point(); + ((point * pixels_per_point - 0.5).round() + 0.5) / pixels_per_point + } + + /// Useful for pixel-perfect rendering of lines that are one pixel wide (or any odd number of pixels). + #[inline] + pub(crate) fn round_pos_to_pixel_center(&self, point: Pos2) -> Pos2 { + pos2( + self.round_to_pixel_center(point.x), + self.round_to_pixel_center(point.y), + ) + } + + /// Useful for pixel-perfect rendering of filled shapes #[inline] pub(crate) fn round_to_pixel(&self, point: f32) -> f32 { let pixels_per_point = self.pixels_per_point(); (point * pixels_per_point).round() / pixels_per_point } - /// Useful for pixel-perfect rendering + /// Useful for pixel-perfect rendering of filled shapes #[inline] pub(crate) fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 { pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y)) } - /// Useful for pixel-perfect rendering + /// Useful for pixel-perfect rendering of filled shapes #[inline] pub(crate) fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 { vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y)) } - /// Useful for pixel-perfect rendering + /// Useful for pixel-perfect rendering of filled shapes #[inline] pub(crate) fn round_rect_to_pixels(&self, rect: Rect) -> Rect { Rect { @@ -1744,7 +1776,7 @@ impl Context { /// /// The given name can be useful for later debugging, and will be visible if you call [`Self::texture_ui`]. /// - /// For how to load an image, see [`ImageData`] and [`ColorImage::from_rgba_unmultiplied`]. + /// For how to load an image, see [`crate::ImageData`] and [`crate::ColorImage::from_rgba_unmultiplied`]. /// /// ``` /// struct MyImage { @@ -1926,10 +1958,10 @@ impl Context { paint_widget_id(widget, "hovered", Color32::WHITE); } } - for &widget in &clicked { + if let Some(widget) = clicked { paint_widget_id(widget, "clicked", Color32::RED); } - for &widget in &dragged { + if let Some(widget) = dragged { paint_widget_id(widget, "dragged", Color32::GREEN); } } @@ -1948,10 +1980,10 @@ impl Context { paint_widget(widget, "contains_pointer", Color32::BLUE); } } - for widget in &click { + if let Some(widget) = &click { paint_widget(widget, "click", Color32::RED); } - for widget in &drag { + if let Some(widget) = &drag { paint_widget(widget, "drag", Color32::GREEN); } } @@ -2263,7 +2295,7 @@ impl Context { /// True if egui is currently interested in the pointer (mouse or touch). /// - /// Could be the pointer is hovering over a [`Window`] or the user is dragging a widget. + /// Could be the pointer is hovering over a [`crate::Window`] or the user is dragging a widget. /// If `false`, the pointer is outside of any egui area and so /// you may be interested in what it is doing (e.g. controlling your game). /// Returns `false` if a drag started outside of egui and then moved over an egui area. @@ -2279,7 +2311,7 @@ impl Context { self.memory(|m| m.interaction().is_using_pointer()) } - /// If `true`, egui is currently listening on text input (e.g. typing text in a [`TextEdit`]). + /// If `true`, egui is currently listening on text input (e.g. typing text in a [`crate::TextEdit`]). pub fn wants_keyboard_input(&self) -> bool { self.memory(|m| m.focused().is_some()) } @@ -2320,7 +2352,7 @@ impl Context { /// If you detect a click or drag and wants to know where it happened, use this. /// - /// Latest position of the mouse, but ignoring any [`Event::PointerGone`] + /// Latest position of the mouse, but ignoring any [`crate::Event::PointerGone`] /// if there were interactions this frame. /// When tapping a touch screen, this will be the location of the touch. #[inline(always)] @@ -2389,7 +2421,7 @@ impl Context { /// Moves the given area to the top in its [`Order`]. /// - /// [`Area`]:s and [`Window`]:s also do this automatically when being clicked on or interacted with. + /// [`crate::Area`]:s and [`crate::Window`]:s also do this automatically when being clicked on or interacted with. pub fn move_to_top(&self, layer_id: LayerId) { self.memory_mut(|mem| mem.areas_mut().move_to_top(layer_id)); } @@ -2397,7 +2429,7 @@ impl Context { /// Mark the `child` layer as a sublayer of `parent`. /// /// Sublayers are moved directly above the parent layer at the end of the frame. This is mainly - /// intended for adding a new [`Area`] inside a [`Window`]. + /// intended for adding a new [`crate::Area`] inside a [`crate::Window`]. /// /// This currently only supports one level of nesting. If `parent` is a sublayer of another /// layer, the behavior is unspecified. @@ -2582,7 +2614,7 @@ impl Context { /// Show the state of egui, including its input and output. pub fn inspection_ui(&self, ui: &mut Ui) { - use crate::containers::*; + use crate::containers::CollapsingHeader; ui.label(format!("Is using pointer: {}", self.is_using_pointer())) .on_hover_text( @@ -2985,7 +3017,7 @@ impl Context { } } - /// Release all memory and textures related to images used in [`Ui::image`] or [`Image`]. + /// Release all memory and textures related to images used in [`Ui::image`] or [`crate::Image`]. /// /// If you attempt to load any images again, they will be reloaded from scratch. pub fn forget_all_images(&self) { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 50f20b9d6f9..c1b175dd843 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -2,7 +2,10 @@ use epaint::ColorImage; -use crate::{emath::*, Key, ViewportId, ViewportIdMap}; +use crate::{ + emath::{Pos2, Rect, Vec2}, + Key, Theme, ViewportId, ViewportIdMap, +}; /// What the integrations provides to egui at the start of each frame. /// @@ -73,6 +76,11 @@ pub struct RawInput { /// /// False when the user alt-tab away from the application, for instance. pub focused: bool, + + /// Does the OS use dark or light mode? + /// + /// `None` means "don't know". + pub system_theme: Option, } impl Default for RawInput { @@ -89,6 +97,7 @@ impl Default for RawInput { hovered_files: Default::default(), dropped_files: Default::default(), focused: true, // integrations opt into global focus tracking + system_theme: None, } } } @@ -117,6 +126,7 @@ impl RawInput { hovered_files: self.hovered_files.clone(), dropped_files: std::mem::take(&mut self.dropped_files), focused: self.focused, + system_theme: self.system_theme, } } @@ -134,6 +144,7 @@ impl RawInput { mut hovered_files, mut dropped_files, focused, + system_theme, } = newer; self.viewport_id = viewport_ids; @@ -147,6 +158,7 @@ impl RawInput { self.hovered_files.append(&mut hovered_files); self.dropped_files.append(&mut dropped_files); self.focused = focused; + self.system_theme = system_theme; } } @@ -189,7 +201,7 @@ pub struct ViewportInfo { /// This should always be set, if known. /// /// On web this takes browser scaling into account, - /// and orresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript. + /// and corresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript. pub native_pixels_per_point: Option, /// Current monitor size in egui points. @@ -710,7 +722,7 @@ impl Modifiers { } /// Checks that the `ctrl/cmd` matches, and that the `shift/alt` of the argument is a subset - /// of the pressed ksey (`self`). + /// of the pressed key (`self`). /// /// This means that if the pattern has not set `shift`, then `self` can have `shift` set or not. /// @@ -1044,6 +1056,7 @@ impl RawInput { hovered_files, dropped_files, focused, + system_theme, } = self; ui.label(format!("Active viwport: {viewport_id:?}")); @@ -1068,6 +1081,7 @@ impl RawInput { ui.label(format!("hovered_files: {}", hovered_files.len())); ui.label(format!("dropped_files: {}", dropped_files.len())); ui.label(format!("focused: {focused}")); + ui.label(format!("system_theme: {system_theme:?}")); ui.scope(|ui| { ui.set_min_height(150.0); ui.label(format!("events: {events:#?}")) diff --git a/crates/egui/src/debug_text.rs b/crates/egui/src/debug_text.rs index 672bc20c6a6..23f41243cb8 100644 --- a/crates/egui/src/debug_text.rs +++ b/crates/egui/src/debug_text.rs @@ -5,7 +5,9 @@ //! The plugin registers itself onto a specific [`Context`] //! to get callbacks on certain events ([`Context::on_begin_frame`], [`Context::on_end_frame`]). -use crate::*; +use crate::{ + text, Align, Align2, Color32, Context, FontFamily, FontId, Id, Rect, Shape, Vec2, WidgetText, +}; /// Register this plugin on the given egui context, /// so that it will be called every frame. diff --git a/crates/egui/src/frame_state.rs b/crates/egui/src/frame_state.rs index 70c558fc498..ebfd0e47be1 100644 --- a/crates/egui/src/frame_state.rs +++ b/crates/egui/src/frame_state.rs @@ -1,6 +1,9 @@ use ahash::{HashMap, HashSet}; -use crate::{id::IdSet, *}; +use crate::{id::IdSet, style, Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects}; + +#[cfg(debug_assertions)] +use crate::{pos2, Align2, Color32, FontId, NumExt, Painter}; /// Reset at the start of each frame. #[derive(Clone, Debug, Default)] @@ -184,12 +187,12 @@ pub struct FrameState { pub tooltips: TooltipFrameState, /// Starts off as the `screen_rect`, shrinks as panels are added. - /// The [`CentralPanel`] does not change this. + /// The [`crate::CentralPanel`] does not change this. /// This is the area available to Window's. pub available_rect: Rect, /// Starts off as the `screen_rect`, shrinks as panels are added. - /// The [`CentralPanel`] retracts from this. + /// The [`crate::CentralPanel`] retracts from this. pub unused_rect: Rect, /// How much space is used by panels. diff --git a/crates/egui/src/grid.rs b/crates/egui/src/grid.rs index 2466c41e9b7..3b25ca226b8 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -1,4 +1,10 @@ -use crate::*; +use crate::{ + vec2, Align2, Color32, Context, Id, InnerResponse, NumExt, Painter, Rect, Region, Style, Ui, + UiBuilder, Vec2, +}; + +#[cfg(debug_assertions)] +use crate::Stroke; #[derive(Clone, Debug, Default, PartialEq)] pub(crate) struct State { @@ -382,7 +388,7 @@ impl Grid { } /// Change which row number the grid starts on. - /// This can be useful when you have a large [`Grid`] inside of [`ScrollArea::show_rows`]. + /// This can be useful when you have a large [`crate::Grid`] inside of [`crate::ScrollArea::show_rows`]. #[inline] pub fn start_row(mut self, start_row: usize) -> Self { self.start_row = start_row; @@ -425,11 +431,14 @@ impl Grid { // then we should pick a default layout that matches that alignment, // which we do here: let max_rect = ui.cursor().intersect(ui.max_rect()); - ui.allocate_ui_at_rect(max_rect, |ui| { - if prev_state.is_none() { - // Hide the ui this frame, and make things as narrow as possible. - ui.set_sizing_pass(); - } + + let mut ui_builder = UiBuilder::new().max_rect(max_rect); + if prev_state.is_none() { + // Hide the ui this frame, and make things as narrow as possible. + ui_builder = ui_builder.sizing_pass(); + } + + ui.allocate_new_ui(ui_builder, |ui| { ui.horizontal(|ui| { let is_color = color_picker.is_some(); let mut grid = GridLayout { diff --git a/crates/egui/src/gui_zoom.rs b/crates/egui/src/gui_zoom.rs index bde60f4321b..4e67d1a1450 100644 --- a/crates/egui/src/gui_zoom.rs +++ b/crates/egui/src/gui_zoom.rs @@ -1,10 +1,10 @@ //! Helpers for zooming the whole GUI of an app (changing [`Context::pixels_per_point`]. //! -use crate::*; +use crate::{Button, Context, Key, KeyboardShortcut, Modifiers, Ui}; /// The suggested keyboard shortcuts for global gui zooming. pub mod kb_shortcuts { - use super::*; + use super::{Key, KeyboardShortcut, Modifiers}; /// Primary keyboard shortcut for zooming in (`Cmd` + `+`). pub const ZOOM_IN: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Plus); diff --git a/crates/egui/src/hit_test.rs b/crates/egui/src/hit_test.rs index 42099d248cc..725eebbac72 100644 --- a/crates/egui/src/hit_test.rs +++ b/crates/egui/src/hit_test.rs @@ -2,7 +2,7 @@ use ahash::HashMap; use emath::TSTransform; -use crate::*; +use crate::{ahash, emath, LayerId, Pos2, WidgetRect, WidgetRects}; /// Result of a hit-test against [`WidgetRects`]. /// @@ -333,6 +333,10 @@ fn find_closest(widgets: impl Iterator, pos: Pos2) -> Option< #[cfg(test)] mod tests { + use emath::{pos2, vec2, Rect}; + + use crate::{Id, Sense}; + use super::*; fn wr(id: Id, sense: Sense, rect: Rect) -> WidgetRect { diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 5749e16825f..9e52031452a 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -1,7 +1,13 @@ mod touch_state; -use crate::data::input::*; -use crate::{emath::*, util::History}; +use crate::data::input::{ + Event, EventFilter, KeyboardShortcut, Modifiers, MouseWheelUnit, PointerButton, RawInput, + TouchDeviceId, ViewportInfo, NUM_POINTER_BUTTONS, +}; +use crate::{ + emath::{vec2, NumExt, Pos2, Rect, Vec2}, + util::History, +}; use std::{ collections::{BTreeMap, HashSet}, time::Duration, diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 06b19a0b719..7cbadbf1193 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -1,6 +1,6 @@ //! How mouse and touch interzcts with widgets. -use crate::*; +use crate::{hit_test, id, input_state, memory, Id, InputState, Key, WidgetRects}; use self::{hit_test::WidgetHits, id::IdSet, input_state::PointerEvent, memory::InteractionState}; @@ -28,7 +28,7 @@ pub struct InteractionSnapshot { /// Set the same frame a drag starts, /// but unset the frame a drag ends. /// - /// NOTE: this may not have a corresponding [`WidgetRect`], + /// NOTE: this may not have a corresponding [`crate::WidgetRect`], /// if this for instance is a drag-and-drop widget which /// isn't painted whilst being dragged pub dragged: Option, diff --git a/crates/egui/src/introspection.rs b/crates/egui/src/introspection.rs index 7a4c98c2ba9..7f0ced6ba93 100644 --- a/crates/egui/src/introspection.rs +++ b/crates/egui/src/introspection.rs @@ -1,5 +1,8 @@ //! Showing UI:s for egui/epaint types. -use crate::*; +use crate::{ + epaint, memory, pos2, remap_clamp, vec2, Color32, CursorIcon, FontFamily, FontId, Label, Mesh, + NumExt, Rect, Response, Sense, Shape, Slider, TextStyle, TextWrapMode, Ui, Widget, +}; pub fn font_family_ui(ui: &mut Ui, font_family: &mut FontFamily) { let families = ui.fonts(|f| f.families()); diff --git a/crates/egui/src/layers.rs b/crates/egui/src/layers.rs index 768099e23d6..3a657ba013f 100644 --- a/crates/egui/src/layers.rs +++ b/crates/egui/src/layers.rs @@ -1,7 +1,7 @@ //! Handles paint layers, i.e. how things //! are sometimes painted behind or in front of other things. -use crate::{Id, *}; +use crate::{ahash, epaint, Id, IdMap, Rect}; use epaint::{emath::TSTransform, ClippedShape, Shape}; /// Different layer categories @@ -67,7 +67,7 @@ impl Order { } /// An identifier for a paint layer. -/// Also acts as an identifier for [`Area`]:s. +/// Also acts as an identifier for [`crate::Area`]:s. #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct LayerId { @@ -158,6 +158,11 @@ impl PaintList { self.0[idx.0].shape = Shape::Noop; } + /// Mutate the shape at the given index, if any. + pub fn mutate_shape(&mut self, idx: ShapeIdx, f: impl FnOnce(&mut ClippedShape)) { + self.0.get_mut(idx.0).map(f); + } + /// Transform each [`Shape`] and clip rectangle by this much, in-place pub fn transform(&mut self, transform: TSTransform) { for ClippedShape { clip_rect, shape } in &mut self.0 { diff --git a/crates/egui/src/layout.rs b/crates/egui/src/layout.rs index a303509de5d..03b3bbd5287 100644 --- a/crates/egui/src/layout.rs +++ b/crates/egui/src/layout.rs @@ -1,4 +1,7 @@ -use crate::{emath::*, Align}; +use crate::{ + emath::{pos2, vec2, Align2, NumExt, Pos2, Rect, Vec2}, + Align, +}; use std::f32::INFINITY; // ---------------------------------------------------------------------------- diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index b4571c88a38..eb04e47799b 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -400,6 +400,7 @@ mod sense; pub mod style; pub mod text_selection; mod ui; +mod ui_builder; mod ui_stack; pub mod util; pub mod viewport; @@ -414,6 +415,7 @@ mod callstack; #[cfg(feature = "accesskit")] pub use accesskit; +#[deprecated = "Use the ahash crate directly."] pub use ahash; pub use epaint; @@ -442,7 +444,7 @@ pub mod text { }; } -pub use { +pub use self::{ containers::*, context::{Context, RepaintCause, RequestRepaintInfo}, data::{ @@ -460,13 +462,14 @@ pub use { layers::{LayerId, Order}, layout::*, load::SizeHint, - memory::{Memory, Options}, + memory::{Memory, Options, Theme}, painter::Painter, response::{InnerResponse, Response}, sense::Sense, style::{FontSelection, Style, TextStyle, Visuals}, text::{Galley, TextFormat}, ui::Ui, + ui_builder::UiBuilder, ui_stack::*, viewport::*, widget_rect::{WidgetRect, WidgetRects}, @@ -692,4 +695,4 @@ mod profiling_scopes { } #[allow(unused_imports)] -pub(crate) use profiling_scopes::*; +pub(crate) use profiling_scopes::{profile_function, profile_scope}; diff --git a/crates/egui/src/load/bytes_loader.rs b/crates/egui/src/load/bytes_loader.rs index d03b2ad418a..9c70f54e1ad 100644 --- a/crates/egui/src/load/bytes_loader.rs +++ b/crates/egui/src/load/bytes_loader.rs @@ -1,4 +1,7 @@ -use super::*; +use super::{ + generate_loader_id, Bytes, BytesLoadResult, BytesLoader, BytesPoll, Context, Cow, HashMap, + LoadError, Mutex, +}; /// Maps URI:s to [`Bytes`], e.g. found with `include_bytes!`. /// diff --git a/crates/egui/src/load/texture_loader.rs b/crates/egui/src/load/texture_loader.rs index 26c254d4d0b..88533e5d95d 100644 --- a/crates/egui/src/load/texture_loader.rs +++ b/crates/egui/src/load/texture_loader.rs @@ -1,4 +1,7 @@ -use super::*; +use super::{ + BytesLoader, Context, HashMap, ImagePoll, Mutex, SizeHint, SizedTexture, TextureHandle, + TextureLoadResult, TextureLoader, TextureOptions, TexturePoll, +}; #[derive(Default)] pub struct DefaultTextureLoader { diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index a09f0b57e31..2a919200976 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -8,6 +8,9 @@ use crate::{ ViewportId, ViewportIdMap, ViewportIdSet, }; +mod theme; +pub use theme::Theme; + // ---------------------------------------------------------------------------- /// The data that egui persists between frames. @@ -169,6 +172,21 @@ pub struct Options { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) style: std::sync::Arc