diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index 3efe98dc6..2ea949e88 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -70,6 +70,41 @@ impl<'open> Window<'open> { } } + /// Construct a [`Window`] that follows the given viewport. + pub fn from_viewport(id: ViewportId, viewport: ViewportBuilder) -> Self { + let ViewportBuilder { + title, + app_id, + inner_size, + min_inner_size, + max_inner_size, + resizable, + decorations, + title_shown, + minimize_button, + .. // A lot of things not implemented yet + } = viewport; + + let mut window = Self::new(title.or(app_id).unwrap_or_else(String::new)).id(Id::new(id)); + + if let Some(inner_size) = inner_size { + window = window.default_size(inner_size); + } + if let Some(min_inner_size) = min_inner_size { + window = window.min_size(min_inner_size); + } + if let Some(max_inner_size) = max_inner_size { + window = window.max_size(max_inner_size); + } + if let Some(resizable) = resizable { + window = window.resizable(resizable); + } + window = window.title_bar(decorations.unwrap_or(true) && title_shown.unwrap_or(true)); + window = window.collapsible(minimize_button.unwrap_or(true)); + + window + } + /// Assign a unique id to the Window. Required if the title changes, or is shared with another window. #[inline] pub fn id(mut self, id: Id) -> Self { diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index ccf5657c1..df7353d86 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -193,7 +193,7 @@ impl ContextImpl { pub struct ViewportState { /// The type of viewport. /// - /// This will never be [`ViewportClass::Embedded`], + /// This will never be [`ViewportClass::EmbeddedWindow`], /// since those don't result in real viewports. pub class: ViewportClass, @@ -4013,21 +4013,23 @@ impl Context { /// /// If [`Context::embed_viewports`] is `true` (e.g. if the current egui /// backend does not support multiple viewports), the given callback - /// will be called immediately, embedding the new viewport in the current one. - /// You can check this with the [`ViewportClass`] given in the callback. - /// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content. + /// will be called immediately, embedding the new viewport in the current one, + /// inside of a [`crate::Window`]. + /// You can know by checking for [`ViewportClass::EmbeddedWindow`]. /// /// See [`crate::viewport`] for more information about viewports. pub fn show_viewport_deferred( &self, new_viewport_id: ViewportId, viewport_builder: ViewportBuilder, - viewport_ui_cb: impl Fn(&Self, ViewportClass) + Send + Sync + 'static, + viewport_ui_cb: impl Fn(&mut Ui, ViewportClass) + Send + Sync + 'static, ) { profiling::function_scope!(); if self.embed_viewports() { - viewport_ui_cb(self, ViewportClass::Embedded); + crate::Window::from_viewport(new_viewport_id, viewport_builder).show(self, |ui| { + viewport_ui_cb(ui, ViewportClass::EmbeddedWindow); + }); } else { self.write(|ctx| { ctx.viewport_parents @@ -4038,7 +4040,9 @@ impl Context { viewport.builder = viewport_builder; viewport.used = true; viewport.viewport_ui_cb = Some(Arc::new(move |ctx| { - (viewport_ui_cb)(ctx, ViewportClass::Deferred); + crate::CentralPanel::no_frame().show(ctx, |ui| { + (viewport_ui_cb)(ui, ViewportClass::Deferred); + }); })); }); } @@ -4065,28 +4069,32 @@ impl Context { /// /// If [`Context::embed_viewports`] is `true` (e.g. if the current egui /// backend does not support multiple viewports), the given callback - /// will be called immediately, embedding the new viewport in the current one. - /// You can check this with the [`ViewportClass`] given in the callback. - /// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content. + /// will be called immediately, embedding the new viewport in the current one, + /// inside of a [`crate::Window`]. + /// You can know by checking for [`ViewportClass::EmbeddedWindow`]. /// /// See [`crate::viewport`] for more information about viewports. pub fn show_viewport_immediate( &self, new_viewport_id: ViewportId, builder: ViewportBuilder, - mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T, + mut viewport_ui_cb: impl FnMut(&mut Ui, ViewportClass) -> T, ) -> T { profiling::function_scope!(); if self.embed_viewports() { - return viewport_ui_cb(self, ViewportClass::Embedded); + return self.show_embedded_viewport(new_viewport_id, builder, |ui| { + viewport_ui_cb(ui, ViewportClass::EmbeddedWindow) + }); } IMMEDIATE_VIEWPORT_RENDERER.with(|immediate_viewport_renderer| { let immediate_viewport_renderer = immediate_viewport_renderer.borrow(); let Some(immediate_viewport_renderer) = immediate_viewport_renderer.as_ref() else { // This egui backend does not support multiple viewports. - return viewport_ui_cb(self, ViewportClass::Embedded); + return self.show_embedded_viewport(new_viewport_id, builder, |ui| { + viewport_ui_cb(ui, ViewportClass::EmbeddedWindow) + }); }; let ids = self.write(|ctx| { @@ -4110,8 +4118,10 @@ impl Context { let viewport = ImmediateViewport { ids, builder, - viewport_ui_cb: Box::new(move |context| { - *out = Some(viewport_ui_cb(context, ViewportClass::Immediate)); + viewport_ui_cb: Box::new(move |ctx| { + crate::CentralPanel::no_frame().show(ctx, |ui| { + *out = Some((viewport_ui_cb)(ui, ViewportClass::Immediate)); + }); }), }; @@ -4123,6 +4133,20 @@ impl Context { ) }) } + + fn show_embedded_viewport( + &self, + new_viewport_id: ViewportId, + builder: ViewportBuilder, + viewport_ui_cb: impl FnOnce(&mut Ui) -> T, + ) -> T { + crate::Window::from_viewport(new_viewport_id, builder) + .collapsible(false) + .show(self, |ui| viewport_ui_cb(ui)) + .unwrap_or_else(|| panic!("Window did not show")) + .inner + .unwrap_or_else(|| panic!("Window was collapsed")) + } } /// ## Interaction diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 3d757e477..8a48daaba 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -33,7 +33,9 @@ //! In short: immediate viewports are simpler to use, but can waste a lot of CPU time. //! //! ### Embedded viewports -//! These are not real, independent viewports, but is a fallback mode for when the integration does not support real viewports. In your callback is called with [`ViewportClass::Embedded`] it means you need to create a [`crate::Window`] to wrap your ui in, which will then be embedded in the parent viewport, unable to escape it. +//! These are not real, independent viewports, but is a fallback mode for when the integration does not support real viewports. +//! In your callback is called with [`ViewportClass::EmbeddedWindow`] it means the viewport is embedded inside of +//! a regular [`crate::Window`], trapped in the parent viewport. //! //! //! ## Using the viewports @@ -101,7 +103,10 @@ pub enum ViewportClass { /// The fallback, when the egui integration doesn't support viewports, /// or [`crate::Context::embed_viewports`] is set to `true`. - Embedded, + /// + /// If you get this, it is because you are already wrapped in a [`crate::Window`] + /// inside of the parent viewport. + EmbeddedWindow, } // ---------------------------------------------------------------------------- @@ -1189,7 +1194,7 @@ pub struct ViewportOutput { /// What type of viewport are we? /// - /// This will never be [`ViewportClass::Embedded`], + /// This will never be [`ViewportClass::EmbeddedWindow`], /// since those don't result in real viewports. pub class: ViewportClass, diff --git a/crates/egui_demo_lib/src/demo/extra_viewport.rs b/crates/egui_demo_lib/src/demo/extra_viewport.rs index d7c875f5f..3682a231a 100644 --- a/crates/egui_demo_lib/src/demo/extra_viewport.rs +++ b/crates/egui_demo_lib/src/demo/extra_viewport.rs @@ -22,17 +22,12 @@ impl crate::Demo for ExtraViewport { egui::ViewportBuilder::default() .with_title(self.name()) .with_inner_size([400.0, 512.0]), - |ctx, class| { - if class == egui::ViewportClass::Embedded { + |ui, class| { + if class == egui::ViewportClass::EmbeddedWindow { // Not a real viewport - egui::Window::new(self.name()) - .id(id) - .open(open) - .show(ctx, |ui| { - ui.label("This egui integration does not support multiple viewports"); - }); + ui.label("This egui integration does not support multiple viewports"); } else { - egui::CentralPanel::default().show(ctx, |ui| { + egui::CentralPanel::default().show_inside(ui, |ui| { viewport_content(ui, ctx, open); }); } diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png index 886077140..65e840a5d 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Extra Viewport.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e62836d9afa18cf4e486fe2819e652bf5df160026dc258201db0b99a75bdf7f1 -size 10125 +oid sha256:f5bc54f7829d1362ff13404103a8734a3cf739d63a5f812bad5e6beba57e73fe +size 9510 diff --git a/examples/multiple_viewports/src/main.rs b/examples/multiple_viewports/src/main.rs index 919c24a3a..8da505980 100644 --- a/examples/multiple_viewports/src/main.rs +++ b/examples/multiple_viewports/src/main.rs @@ -44,10 +44,20 @@ impl eframe::App for MyApp { "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); + { + 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); + } + + ui.add_space(16.0); + { + let mut embedded = ui.embed_viewports(); + ui.checkbox(&mut embedded, "Embed all viewports"); + ui.set_embed_viewports(embedded); + } }); if self.show_immediate_viewport { @@ -56,19 +66,20 @@ impl eframe::App for MyApp { egui::ViewportBuilder::default() .with_title("Immediate Viewport") .with_inner_size([200.0, 100.0]), - |ctx, class| { - assert!( - class == egui::ViewportClass::Immediate, - "This egui backend doesn't support multiple viewports" - ); + |ui, class| { + if class == egui::ViewportClass::EmbeddedWindow { + ui.label( + "This viewport is embedded in the parent window, and cannot be moved outside of it.", + ); + } else { + egui::CentralPanel::default().show_inside(ui, |ui| { + ui.label("Hello from immediate viewport"); - egui::CentralPanel::default().show(ctx, |ui| { - ui.label("Hello from immediate viewport"); - }); - - if ctx.input(|i| i.viewport().close_requested()) { - // Tell parent viewport that we should not show next frame: - self.show_immediate_viewport = false; + if ui.input(|i| i.viewport().close_requested()) { + // Tell parent viewport that we should not show next frame: + self.show_immediate_viewport = false; + } + }); } }, ); @@ -81,18 +92,20 @@ impl eframe::App for MyApp { egui::ViewportBuilder::default() .with_title("Deferred Viewport") .with_inner_size([200.0, 100.0]), - move |ctx, class| { - assert!( - class == egui::ViewportClass::Deferred, - "This egui backend doesn't support multiple viewports" - ); + move |ui, class| { + if class == egui::ViewportClass::EmbeddedWindow { + ui.label( + "This viewport is embedded in the parent window, and cannot be moved outside of it.", + ); + } else { + egui::CentralPanel::default().show_inside(ui, |ui| { + ui.label("Hello from deferred viewport"); - egui::CentralPanel::default().show(ctx, |ui| { - ui.label("Hello from deferred viewport"); - }); - if ctx.input(|i| i.viewport().close_requested()) { - // Tell parent to close us. - show_deferred_viewport.store(false, Ordering::Relaxed); + if ui.input(|i| i.viewport().close_requested()) { + // Tell parent to close us. + show_deferred_viewport.store(false, Ordering::Relaxed); + } + }); } }, ); diff --git a/tests/test_viewports/src/main.rs b/tests/test_viewports/src/main.rs index 13795740c..f26fb043a 100644 --- a/tests/test_viewports/src/main.rs +++ b/tests/test_viewports/src/main.rs @@ -76,35 +76,29 @@ impl ViewportState { if immediate { let mut vp_state = vp_state.write(); - ctx.show_viewport_immediate(vp_id, viewport, move |ctx, class| { - if ctx.input(|i| i.viewport().close_requested()) { + ctx.show_viewport_immediate(vp_id, viewport, move |ui, class| { + if ui.input(|i| i.viewport().close_requested()) { vp_state.visible = false; } - show_as_popup(ctx, class, &title, vp_id.into(), |ui: &mut egui::Ui| { + show_as_popup(ui, class, |ui: &mut egui::Ui| { generic_child_ui(ui, &mut vp_state, close_button); }); }); } else { let count = Arc::new(RwLock::new(0)); - ctx.show_viewport_deferred(vp_id, viewport, move |ctx, class| { + ctx.show_viewport_deferred(vp_id, viewport, move |ui, class| { let mut vp_state = vp_state.write(); - if ctx.input(|i| i.viewport().close_requested()) { + if ui.input(|i| i.viewport().close_requested()) { vp_state.visible = false; } let count = count.clone(); - show_as_popup( - ctx, - class, - &title, - vp_id.into(), - move |ui: &mut egui::Ui| { - let current_count = *count.read(); - ui.label(format!("Callback has been reused {current_count} times")); - *count.write() += 1; + show_as_popup(ui, class, move |ui: &mut egui::Ui| { + let current_count = *count.read(); + ui.label(format!("Callback has been reused {current_count} times")); + *count.write() += 1; - generic_child_ui(ui, &mut vp_state, close_button); - }, - ); + generic_child_ui(ui, &mut vp_state, close_button); + }); }); } } @@ -180,17 +174,15 @@ impl eframe::App for App { /// This will make the content as a popup if cannot has his own native window fn show_as_popup( - ctx: &egui::Context, + ui: &mut egui::Ui, class: egui::ViewportClass, - title: &str, - id: Id, content: impl FnOnce(&mut egui::Ui), ) { - if class == egui::ViewportClass::Embedded { - // Not a real viewport - egui::Window::new(title).id(id).show(ctx, content); + if class == egui::ViewportClass::EmbeddedWindow { + // Not a real viewport - already has a frame + content(ui); } else { - egui::CentralPanel::default().show(ctx, content); + egui::CentralPanel::default().show_inside(ui, content); } }