mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Viewports: give the caller a Ui instead of Context (#7779)
* Part of https://github.com/emilk/egui/issues/3524 This is a breaking change, as it changes the how embedded viewports work. Before it was up to the user to display a `egui::Window` if they wanted. Now egui creates an `egui::Window` for you, so you only need to add the contents. To signal this change in behavior, `ViewportClass::Embedded` is gone and is now called `ViewportClass::EmbeddedWindow`.
This commit is contained in:
@@ -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.
|
/// Assign a unique id to the Window. Required if the title changes, or is shared with another window.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn id(mut self, id: Id) -> Self {
|
pub fn id(mut self, id: Id) -> Self {
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ impl ContextImpl {
|
|||||||
pub struct ViewportState {
|
pub struct ViewportState {
|
||||||
/// The type of viewport.
|
/// The type of viewport.
|
||||||
///
|
///
|
||||||
/// This will never be [`ViewportClass::Embedded`],
|
/// This will never be [`ViewportClass::EmbeddedWindow`],
|
||||||
/// since those don't result in real viewports.
|
/// since those don't result in real viewports.
|
||||||
pub class: ViewportClass,
|
pub class: ViewportClass,
|
||||||
|
|
||||||
@@ -4013,21 +4013,23 @@ impl Context {
|
|||||||
///
|
///
|
||||||
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
|
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
|
||||||
/// backend does not support multiple viewports), the given callback
|
/// backend does not support multiple viewports), the given callback
|
||||||
/// will be called immediately, embedding the new viewport in the current one.
|
/// will be called immediately, embedding the new viewport in the current one,
|
||||||
/// You can check this with the [`ViewportClass`] given in the callback.
|
/// inside of a [`crate::Window`].
|
||||||
/// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content.
|
/// You can know by checking for [`ViewportClass::EmbeddedWindow`].
|
||||||
///
|
///
|
||||||
/// See [`crate::viewport`] for more information about viewports.
|
/// See [`crate::viewport`] for more information about viewports.
|
||||||
pub fn show_viewport_deferred(
|
pub fn show_viewport_deferred(
|
||||||
&self,
|
&self,
|
||||||
new_viewport_id: ViewportId,
|
new_viewport_id: ViewportId,
|
||||||
viewport_builder: ViewportBuilder,
|
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!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if self.embed_viewports() {
|
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 {
|
} else {
|
||||||
self.write(|ctx| {
|
self.write(|ctx| {
|
||||||
ctx.viewport_parents
|
ctx.viewport_parents
|
||||||
@@ -4038,7 +4040,9 @@ impl Context {
|
|||||||
viewport.builder = viewport_builder;
|
viewport.builder = viewport_builder;
|
||||||
viewport.used = true;
|
viewport.used = true;
|
||||||
viewport.viewport_ui_cb = Some(Arc::new(move |ctx| {
|
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
|
/// If [`Context::embed_viewports`] is `true` (e.g. if the current egui
|
||||||
/// backend does not support multiple viewports), the given callback
|
/// backend does not support multiple viewports), the given callback
|
||||||
/// will be called immediately, embedding the new viewport in the current one.
|
/// will be called immediately, embedding the new viewport in the current one,
|
||||||
/// You can check this with the [`ViewportClass`] given in the callback.
|
/// inside of a [`crate::Window`].
|
||||||
/// If you find [`ViewportClass::Embedded`], you need to create a new [`crate::Window`] for you content.
|
/// You can know by checking for [`ViewportClass::EmbeddedWindow`].
|
||||||
///
|
///
|
||||||
/// See [`crate::viewport`] for more information about viewports.
|
/// See [`crate::viewport`] for more information about viewports.
|
||||||
pub fn show_viewport_immediate<T>(
|
pub fn show_viewport_immediate<T>(
|
||||||
&self,
|
&self,
|
||||||
new_viewport_id: ViewportId,
|
new_viewport_id: ViewportId,
|
||||||
builder: ViewportBuilder,
|
builder: ViewportBuilder,
|
||||||
mut viewport_ui_cb: impl FnMut(&Self, ViewportClass) -> T,
|
mut viewport_ui_cb: impl FnMut(&mut Ui, ViewportClass) -> T,
|
||||||
) -> T {
|
) -> T {
|
||||||
profiling::function_scope!();
|
profiling::function_scope!();
|
||||||
|
|
||||||
if self.embed_viewports() {
|
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| {
|
IMMEDIATE_VIEWPORT_RENDERER.with(|immediate_viewport_renderer| {
|
||||||
let immediate_viewport_renderer = immediate_viewport_renderer.borrow();
|
let immediate_viewport_renderer = immediate_viewport_renderer.borrow();
|
||||||
let Some(immediate_viewport_renderer) = immediate_viewport_renderer.as_ref() else {
|
let Some(immediate_viewport_renderer) = immediate_viewport_renderer.as_ref() else {
|
||||||
// This egui backend does not support multiple viewports.
|
// 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| {
|
let ids = self.write(|ctx| {
|
||||||
@@ -4110,8 +4118,10 @@ impl Context {
|
|||||||
let viewport = ImmediateViewport {
|
let viewport = ImmediateViewport {
|
||||||
ids,
|
ids,
|
||||||
builder,
|
builder,
|
||||||
viewport_ui_cb: Box::new(move |context| {
|
viewport_ui_cb: Box::new(move |ctx| {
|
||||||
*out = Some(viewport_ui_cb(context, ViewportClass::Immediate));
|
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<T>(
|
||||||
|
&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
|
/// ## Interaction
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
//! In short: immediate viewports are simpler to use, but can waste a lot of CPU time.
|
//! In short: immediate viewports are simpler to use, but can waste a lot of CPU time.
|
||||||
//!
|
//!
|
||||||
//! ### Embedded viewports
|
//! ### 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
|
//! ## Using the viewports
|
||||||
@@ -101,7 +103,10 @@ pub enum ViewportClass {
|
|||||||
|
|
||||||
/// The fallback, when the egui integration doesn't support viewports,
|
/// The fallback, when the egui integration doesn't support viewports,
|
||||||
/// or [`crate::Context::embed_viewports`] is set to `true`.
|
/// 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?
|
/// 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.
|
/// since those don't result in real viewports.
|
||||||
pub class: ViewportClass,
|
pub class: ViewportClass,
|
||||||
|
|
||||||
|
|||||||
@@ -22,17 +22,12 @@ impl crate::Demo for ExtraViewport {
|
|||||||
egui::ViewportBuilder::default()
|
egui::ViewportBuilder::default()
|
||||||
.with_title(self.name())
|
.with_title(self.name())
|
||||||
.with_inner_size([400.0, 512.0]),
|
.with_inner_size([400.0, 512.0]),
|
||||||
|ctx, class| {
|
|ui, class| {
|
||||||
if class == egui::ViewportClass::Embedded {
|
if class == egui::ViewportClass::EmbeddedWindow {
|
||||||
// Not a real viewport
|
// Not a real viewport
|
||||||
egui::Window::new(self.name())
|
ui.label("This egui integration does not support multiple viewports");
|
||||||
.id(id)
|
|
||||||
.open(open)
|
|
||||||
.show(ctx, |ui| {
|
|
||||||
ui.label("This egui integration does not support multiple viewports");
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show_inside(ui, |ui| {
|
||||||
viewport_content(ui, ctx, open);
|
viewport_content(ui, ctx, open);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:e62836d9afa18cf4e486fe2819e652bf5df160026dc258201db0b99a75bdf7f1
|
oid sha256:f5bc54f7829d1362ff13404103a8734a3cf739d63a5f812bad5e6beba57e73fe
|
||||||
size 10125
|
size 9510
|
||||||
|
|||||||
@@ -44,10 +44,20 @@ impl eframe::App for MyApp {
|
|||||||
"Show immediate child 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");
|
let mut show_deferred_viewport =
|
||||||
self.show_deferred_viewport
|
self.show_deferred_viewport.load(Ordering::Relaxed);
|
||||||
.store(show_deferred_viewport, 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 {
|
if self.show_immediate_viewport {
|
||||||
@@ -56,19 +66,20 @@ impl eframe::App for MyApp {
|
|||||||
egui::ViewportBuilder::default()
|
egui::ViewportBuilder::default()
|
||||||
.with_title("Immediate Viewport")
|
.with_title("Immediate Viewport")
|
||||||
.with_inner_size([200.0, 100.0]),
|
.with_inner_size([200.0, 100.0]),
|
||||||
|ctx, class| {
|
|ui, class| {
|
||||||
assert!(
|
if class == egui::ViewportClass::EmbeddedWindow {
|
||||||
class == egui::ViewportClass::Immediate,
|
ui.label(
|
||||||
"This egui backend doesn't support multiple viewports"
|
"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| {
|
if ui.input(|i| i.viewport().close_requested()) {
|
||||||
ui.label("Hello from immediate viewport");
|
// Tell parent viewport that we should not show next frame:
|
||||||
});
|
self.show_immediate_viewport = false;
|
||||||
|
}
|
||||||
if ctx.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()
|
egui::ViewportBuilder::default()
|
||||||
.with_title("Deferred Viewport")
|
.with_title("Deferred Viewport")
|
||||||
.with_inner_size([200.0, 100.0]),
|
.with_inner_size([200.0, 100.0]),
|
||||||
move |ctx, class| {
|
move |ui, class| {
|
||||||
assert!(
|
if class == egui::ViewportClass::EmbeddedWindow {
|
||||||
class == egui::ViewportClass::Deferred,
|
ui.label(
|
||||||
"This egui backend doesn't support multiple viewports"
|
"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| {
|
if ui.input(|i| i.viewport().close_requested()) {
|
||||||
ui.label("Hello from deferred viewport");
|
// Tell parent to close us.
|
||||||
});
|
show_deferred_viewport.store(false, Ordering::Relaxed);
|
||||||
if ctx.input(|i| i.viewport().close_requested()) {
|
}
|
||||||
// Tell parent to close us.
|
});
|
||||||
show_deferred_viewport.store(false, Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -76,35 +76,29 @@ impl ViewportState {
|
|||||||
|
|
||||||
if immediate {
|
if immediate {
|
||||||
let mut vp_state = vp_state.write();
|
let mut vp_state = vp_state.write();
|
||||||
ctx.show_viewport_immediate(vp_id, viewport, move |ctx, class| {
|
ctx.show_viewport_immediate(vp_id, viewport, move |ui, class| {
|
||||||
if ctx.input(|i| i.viewport().close_requested()) {
|
if ui.input(|i| i.viewport().close_requested()) {
|
||||||
vp_state.visible = false;
|
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);
|
generic_child_ui(ui, &mut vp_state, close_button);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let count = Arc::new(RwLock::new(0));
|
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();
|
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;
|
vp_state.visible = false;
|
||||||
}
|
}
|
||||||
let count = count.clone();
|
let count = count.clone();
|
||||||
show_as_popup(
|
show_as_popup(ui, class, move |ui: &mut egui::Ui| {
|
||||||
ctx,
|
let current_count = *count.read();
|
||||||
class,
|
ui.label(format!("Callback has been reused {current_count} times"));
|
||||||
&title,
|
*count.write() += 1;
|
||||||
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;
|
|
||||||
|
|
||||||
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
|
/// This will make the content as a popup if cannot has his own native window
|
||||||
fn show_as_popup(
|
fn show_as_popup(
|
||||||
ctx: &egui::Context,
|
ui: &mut egui::Ui,
|
||||||
class: egui::ViewportClass,
|
class: egui::ViewportClass,
|
||||||
title: &str,
|
|
||||||
id: Id,
|
|
||||||
content: impl FnOnce(&mut egui::Ui),
|
content: impl FnOnce(&mut egui::Ui),
|
||||||
) {
|
) {
|
||||||
if class == egui::ViewportClass::Embedded {
|
if class == egui::ViewportClass::EmbeddedWindow {
|
||||||
// Not a real viewport
|
// Not a real viewport - already has a frame
|
||||||
egui::Window::new(title).id(id).show(ctx, content);
|
content(ui);
|
||||||
} else {
|
} else {
|
||||||
egui::CentralPanel::default().show(ctx, content);
|
egui::CentralPanel::default().show_inside(ui, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user