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..53b314da862 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: @@ -108,7 +104,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 +163,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..c4b06ef8ffe 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", @@ -4208,19 +4208,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 +4246,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 +4256,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 +4269,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 +4323,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 +4335,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 +4348,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 +4384,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 +4433,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 +4458,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 +4850,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..21e8d847e23 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" diff --git a/crates/eframe/Cargo.toml b/crates/eframe/Cargo.toml index 0b18845c34a..b438bdea7cc 100644 --- a/crates/eframe/Cargo.toml +++ b/crates/eframe/Cargo.toml @@ -21,7 +21,6 @@ include = [ [package.metadata.docs.rs] all-features = true -rustc-args = ["--cfg=web_sys_unstable_apis"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [lints] 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..09f77d743e5 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` 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..87116b71227 100644 --- a/crates/eframe/src/native/glow_integration.rs +++ b/crates/eframe/src/native/glow_integration.rs @@ -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 @@ -809,6 +803,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 +1126,7 @@ impl GlutinWindowContext { viewport_id, event_loop, Some(window.scale_factor() as f32), + event_loop.system_theme(), self.max_texture_side, ) }); @@ -1365,6 +1372,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 cd9ab3d66ee..61585f9bb4a 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 @@ -174,16 +175,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..0bcce42c638 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -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..c5e58d35e15 100644 --- a/crates/eframe/src/web/events.rs +++ b/crates/eframe/src/web/events.rs @@ -94,6 +94,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 +279,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 +293,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 +309,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 +351,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 +573,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 +771,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..7e6a5101216 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -45,8 +45,6 @@ use web_sys::MediaQueryList; use input::*; -use crate::Theme; - // ---------------------------------------------------------------------------- pub(crate) fn string_from_js_value(value: &JsValue) -> String { @@ -103,7 +101,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 +112,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 +168,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 +252,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/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..4060442f6dc 100644 --- a/crates/egui-winit/Cargo.toml +++ b/crates/egui-winit/Cargo.toml @@ -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..c35d2878227 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; @@ -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/src/containers/area.rs b/crates/egui/src/containers/area.rs index 062261af31b..196c3de2cab 100644 --- a/crates/egui/src/containers/area.rs +++ b/crates/egui/src/containers/area.rs @@ -532,16 +532,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 +559,6 @@ impl Prepared { } } - if !self.enabled { - ui.disable(); - } - if self.sizing_pass { - ui.set_sizing_pass(); - } ui } diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs index cb29f277644..5c6af803b3e 100644 --- a/crates/egui/src/containers/combo_box.rs +++ b/crates/egui/src/containers/combo_box.rs @@ -425,7 +425,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..f59598400b4 100644 --- a/crates/egui/src/containers/frame.rs +++ b/crates/egui/src/containers/frame.rs @@ -250,10 +250,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..9aafa0cfae1 100644 --- a/crates/egui/src/containers/panel.rs +++ b/crates/egui/src/containers/panel.rs @@ -261,14 +261,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) @@ -367,15 +368,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; @@ -750,14 +749,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) @@ -857,15 +857,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; @@ -1091,10 +1089,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 +1123,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/resize.rs b/crates/egui/src/containers/resize.rs index 4980c9c623d..54a00af0eb7 100644 --- a/crates/egui/src/containers/resize.rs +++ b/crates/egui/src/containers/resize.rs @@ -270,10 +270,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..20fd4e4f7c5 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -560,10 +560,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), ); { @@ -732,7 +732,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/context.rs b/crates/egui/src/context.rs index d1a05db4bc2..a4ca219a1b4 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1926,10 +1926,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 +1948,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); } } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 50f20b9d6f9..43fa9b6684f 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -2,7 +2,7 @@ use epaint::ColorImage; -use crate::{emath::*, Key, ViewportId, ViewportIdMap}; +use crate::{emath::*, Key, Theme, ViewportId, ViewportIdMap}; /// What the integrations provides to egui at the start of each frame. /// @@ -73,6 +73,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 +94,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 +123,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 +141,7 @@ impl RawInput { mut hovered_files, mut dropped_files, focused, + system_theme, } = newer; self.viewport_id = viewport_ids; @@ -147,6 +155,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 +198,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 +719,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 +1053,7 @@ impl RawInput { hovered_files, dropped_files, focused, + system_theme, } = self; ui.label(format!("Active viwport: {viewport_id:?}")); @@ -1068,6 +1078,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/grid.rs b/crates/egui/src/grid.rs index 2466c41e9b7..e46f2e33949 100644 --- a/crates/egui/src/grid.rs +++ b/crates/egui/src/grid.rs @@ -425,11 +425,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/lib.rs b/crates/egui/src/lib.rs index b4571c88a38..1df03f0ba8c 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}, diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index a09f0b57e31..86aa3526799 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