diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index b3430bcff..aff7c7d38 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -123,6 +123,7 @@ pub trait App { /// /// This is called for the root viewport ([`egui::ViewportId::ROOT`]). /// Use [`egui::Context::show_viewport`] to spawn additional viewports (windows). + /// (A "viewport" in egui means an native OS window). fn update(&mut self, ctx: &egui::Context, frame: &mut Frame); /// Get a handle to the app. diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index ee6935205..80387dbdb 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -85,7 +85,7 @@ pub fn window_builder( .. } = native_options; - let mut viewport_builder = egui::ViewportBuilder::ROOT + let mut viewport_builder = egui::ViewportBuilder::DEFAULTS .with_title(title) .with_decorations(*decorated) .with_fullscreen(*fullscreen) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 884a643be..e11d3b382 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -201,18 +201,16 @@ impl State { let screen_size_in_pixels = screen_size_in_pixels(window); let screen_size_in_points = screen_size_in_pixels / pixels_per_point; - let getting_info = !window.is_minimized().unwrap_or_else(|| { - eprintln!("Cannot determine the Viewport/native window minimized state"); - true - }); + self.egui_input.screen_rect = (screen_size_in_points.x > 0.0 + && screen_size_in_points.y > 0.0) + .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points)); - self.egui_input.screen_rect = if getting_info { - Some(egui::Rect::from_min_size(Pos2::ZERO, screen_size_in_points)) - } else { - None + let has_a_position = match window.is_minimized() { + None | Some(true) => false, + Some(false) => true, }; - let inner_pos_px = if getting_info { + let inner_pos_px = if has_a_position { window .inner_position() .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) @@ -221,7 +219,7 @@ impl State { None }; - let outer_pos_px = if getting_info { + let outer_pos_px = if has_a_position { window .outer_position() .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) @@ -230,14 +228,14 @@ impl State { None }; - let inner_size_px = if getting_info { + let inner_size_px = if has_a_position { let size = window.inner_size(); Some(Vec2::new(size.width as f32, size.height as f32)) } else { None }; - let outer_size_px = if getting_info { + let outer_size_px = if has_a_position { let size = window.outer_size(); Some(Vec2::new(size.width as f32, size.height as f32)) } else { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index b72c8ec51..1debee8f5 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -75,6 +75,7 @@ pub struct RawInput { impl Default for RawInput { fn default() -> Self { Self { + viewport: ViewportInfo::default(), screen_rect: None, pixels_per_point: None, max_texture_side: None, @@ -85,7 +86,6 @@ impl Default for RawInput { hovered_files: Default::default(), dropped_files: Default::default(), focused: true, // integrations opt into global focus tracking - viewport: ViewportInfo::default(), } } } @@ -97,6 +97,7 @@ impl RawInput { /// * [`Self::dropped_files`] is moved. pub fn take(&mut self) -> RawInput { RawInput { + viewport: self.viewport.take(), screen_rect: self.screen_rect.take(), pixels_per_point: self.pixels_per_point.take(), max_texture_side: self.max_texture_side.take(), @@ -107,13 +108,13 @@ impl RawInput { hovered_files: self.hovered_files.clone(), dropped_files: std::mem::take(&mut self.dropped_files), focused: self.focused, - viewport: self.viewport.take(), } } /// Add on new input. pub fn append(&mut self, newer: Self) { let Self { + viewport, screen_rect, pixels_per_point, max_texture_side, @@ -124,13 +125,12 @@ impl RawInput { mut hovered_files, mut dropped_files, focused, - viewport, } = newer; + self.viewport = viewport; self.screen_rect = screen_rect.or(self.screen_rect); self.pixels_per_point = pixels_per_point.or(self.pixels_per_point); self.max_texture_side = max_texture_side.or(self.max_texture_side); - self.viewport = viewport; self.time = time; // use latest time self.predicted_dt = predicted_dt; // use latest dt self.modifiers = modifiers; // use latest diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 528090806..17135760d 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -79,7 +79,7 @@ pub struct Memory { #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) new_font_definitions: Option, - // Current viewport + // Current active viewport #[cfg_attr(feature = "persistence", serde(skip))] pub(crate) viewport_id: ViewportId, @@ -96,7 +96,6 @@ pub struct Memory { // ------------------------------------------------- // Per-viewport: - #[cfg_attr(feature = "persistence", serde(skip))] areas: ViewportIdMap, #[cfg_attr(feature = "persistence", serde(skip))] diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 9ac13d129..255600d47 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -135,7 +135,7 @@ pub struct ViewportBuilder { impl ViewportBuilder { /// Default settings for the root viewport. - pub const ROOT: Self = Self { + pub const DEFAULTS: Self = Self { title: None, name: None, position: None, @@ -154,18 +154,12 @@ impl ViewportBuilder { min_inner_size: None, max_inner_size: None, drag_and_drop: Some(true), - close_button: Some(false), // We disable the close button by default because we haven't implemented closing of child viewports yet + close_button: Some(true), minimize_button: Some(true), maximize_button: Some(true), hittest: Some(true), }; - /// Default settings for a new child viewport. - pub const CHILD: Self = Self { - close_button: Some(false), // We disable the close button by default because we haven't implemented closing of child viewports yet - ..Self::ROOT - }; - /// Empty settings for everything. /// /// If used the first frame, backend-specific defaults will be used. diff --git a/examples/multiple_viewports/src/main.rs b/examples/multiple_viewports/src/main.rs index 945473132..930fe3e73 100644 --- a/examples/multiple_viewports/src/main.rs +++ b/examples/multiple_viewports/src/main.rs @@ -1,5 +1,10 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + use eframe::egui; fn main() -> Result<(), eframe::Error> { @@ -9,7 +14,7 @@ fn main() -> Result<(), eframe::Error> { ..Default::default() }; eframe::run_native( - "Confirm exit", + "Multiple viewports", options, Box::new(|_cc| Box::::default()), ) @@ -17,7 +22,15 @@ fn main() -> Result<(), eframe::Error> { #[derive(Default)] struct MyApp { - show_child_viewport: bool, + /// Immediate viewports are show immediately, so passing state to/from them is easy. + /// The downside is that their painting is linked with the parent viewport: + /// if either needs repainting, they are both repainted. + show_immediate_viewport: bool, + + /// Deferred viewports run independant of the parent viewport, which can save + /// CPU if only some of the viewports require repainting. + /// However, this requires passing state with `Arc` and locks. + show_deferred_viewport: Arc, } impl eframe::App for MyApp { @@ -25,17 +38,48 @@ impl eframe::App for MyApp { egui::CentralPanel::default().show(ctx, |ui| { ui.label("Hello from the root viewport"); - ui.checkbox(&mut self.show_child_viewport, "Show secondary viewport"); + ui.checkbox( + &mut self.show_immediate_viewport, + "Show immediate child viewport", + ); + + let mut show_deferred_viewport = self.show_deferred_viewport.load(Ordering::Relaxed); + ui.checkbox(&mut show_deferred_viewport, "Show deferred child viewport"); + self.show_deferred_viewport + .store(show_deferred_viewport, Ordering::Relaxed); }); - if self.show_child_viewport { - ctx.show_viewport( - egui::ViewportId::from_hash_of("secondary_viewport"), - egui::ViewportBuilder::CHILD.with_title("Secondary Viewport"), + if self.show_immediate_viewport { + ctx.show_viewport_immediate( + egui::ViewportId::from_hash_of("immediate_viewport"), + egui::ViewportBuilder::DEFAULTS.with_title("Immediate Viewport"), |ctx| { egui::CentralPanel::default().show(ctx, |ui| { - ui.label("Hello from secondary viewport"); + ui.label("Hello from immediate viewport"); }); + if ctx.input(|i| i.raw.viewport.close_requested) { + // Tell parent viewport that we should not show next frame: + self.show_immediate_viewport = false; + ctx.request_repaint(); // make sure there is a next frame + } + }, + ); + } + + if self.show_deferred_viewport.load(Ordering::Relaxed) { + let show_deferred_viewport = self.show_deferred_viewport.clone(); + ctx.show_viewport_immediate( + egui::ViewportId::from_hash_of("deferred_viewport"), + egui::ViewportBuilder::DEFAULTS.with_title("Deferred Viewport"), + |ctx| { + egui::CentralPanel::default().show(ctx, |ui| { + ui.label("Hello from deferred viewport"); + }); + if ctx.input(|i| i.raw.viewport.close_requested) { + // Tell parent to close use + show_deferred_viewport.store(false, Ordering::Relaxed); + ctx.request_repaint(); // make sure there is a next frame + } }, ); } diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index f195bf4c0..33676830e 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -65,7 +65,7 @@ impl ViewportState { let immediate = vp_state.read().immediate; let title = vp_state.read().title.clone(); - let viewport = ViewportBuilder::ROOT + let viewport = ViewportBuilder::DEFAULTS .with_title(&title) .with_inner_size(Some(egui::vec2(450.0, 400.0)));