From 9c5ff1d98d86729a0c2ddd1a2c17586829fb7715 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 17 Nov 2023 12:25:37 +0100 Subject: [PATCH 01/36] emath: allow mul/div Pos2+Rect with f32 for scale converions --- crates/emath/src/pos2.rs | 36 ++++++++++++++++++++++++++++++++++++ crates/emath/src/rect.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/crates/emath/src/pos2.rs b/crates/emath/src/pos2.rs index 3b5018bff61..8a6f9d20aa6 100644 --- a/crates/emath/src/pos2.rs +++ b/crates/emath/src/pos2.rs @@ -280,6 +280,42 @@ impl Sub for Pos2 { } } +impl Mul for Pos2 { + type Output = Pos2; + + #[inline(always)] + fn mul(self, factor: f32) -> Pos2 { + Pos2 { + x: self.x * factor, + y: self.y * factor, + } + } +} + +impl Mul for f32 { + type Output = Pos2; + + #[inline(always)] + fn mul(self, vec: Pos2) -> Pos2 { + Pos2 { + x: self * vec.x, + y: self * vec.y, + } + } +} + +impl Div for Pos2 { + type Output = Pos2; + + #[inline(always)] + fn div(self, factor: f32) -> Pos2 { + Pos2 { + x: self.x / factor, + y: self.y / factor, + } + } +} + impl std::fmt::Debug for Pos2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "[{:.1} {:.1}]", self.x, self.y) diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index cd990cc4ba5..69ff7d30384 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -622,3 +622,39 @@ impl From<[Pos2; 2]> for Rect { Self { min, max } } } + +impl Mul for Rect { + type Output = Rect; + + #[inline] + fn mul(self, factor: f32) -> Rect { + Rect { + min: self.min * factor, + max: self.max * factor, + } + } +} + +impl Mul for f32 { + type Output = Rect; + + #[inline] + fn mul(self, vec: Rect) -> Rect { + Rect { + min: self * vec.min, + max: self * vec.max, + } + } +} + +impl Div for Rect { + type Output = Rect; + + #[inline] + fn div(self, factor: f32) -> Rect { + Rect { + min: self.min / factor, + max: self.max / factor, + } + } +} From 136a149712b6e9e4d800e51ef3ad9f9ce9875a2f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 17 Nov 2023 12:26:26 +0100 Subject: [PATCH 02/36] Use ui points for the rectangles in `ViewportInfo` --- crates/egui-winit/src/lib.rs | 129 +++++++++++++++++----------- crates/egui/src/data/input.rs | 29 ++++--- examples/test_viewports/src/main.rs | 4 +- 3 files changed, 97 insertions(+), 65 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 52b7e718241..4dc36ee7956 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -14,7 +14,9 @@ pub use accesskit_winit; pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; -use egui::{Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdPair}; +use egui::{ + Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdPair, ViewportInfo, +}; pub use winit; pub mod clipboard; @@ -207,57 +209,13 @@ impl State { && screen_size_in_points.y > 0.0) .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points)); - let has_a_position = match window.is_minimized() { - None | Some(true) => false, - Some(false) => true, - }; - - let inner_pos_px = if has_a_position { - window - .inner_position() - .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) - .ok() - } else { - None - }; - - let outer_pos_px = if has_a_position { - window - .outer_position() - .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) - .ok() - } else { - None - }; - - 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 has_a_position { - let size = window.outer_size(); - Some(Vec2::new(size.width as f32, size.height as f32)) - } else { - None - }; - - self.egui_input.viewport.ids = ids; - self.egui_input.viewport.inner_rect_px = - if let (Some(pos), Some(size)) = (inner_pos_px, inner_size_px) { - Some(Rect::from_min_size(pos, size)) - } else { - None - }; - - self.egui_input.viewport.outer_rect_px = - if let (Some(pos), Some(size)) = (outer_pos_px, outer_size_px) { - Some(Rect::from_min_size(pos, size)) - } else { - None - }; + let viewport_info = viewport_info( + window, + ids, + pixels_per_point, + self.egui_input.viewport.close_requested, + ); + self.egui_input.viewport = viewport_info; self.egui_input.take() } @@ -796,6 +754,73 @@ impl State { } } +fn viewport_info( + window: &winit::window::Window, + ids: ViewportIdPair, + pixels_per_point: f32, + close_requested: bool, +) -> ViewportInfo { + let has_a_position = match window.is_minimized() { + None | Some(true) => false, + Some(false) => true, + }; + + let inner_pos_px = if has_a_position { + window + .inner_position() + .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) + .ok() + } else { + None + }; + + let outer_pos_px = if has_a_position { + window + .outer_position() + .map(|pos| Pos2::new(pos.x as f32, pos.y as f32)) + .ok() + } else { + None + }; + + 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 has_a_position { + let size = window.outer_size(); + Some(Vec2::new(size.width as f32, size.height as f32)) + } else { + None + }; + + let inner_rect_px = if let (Some(pos), Some(size)) = (inner_pos_px, inner_size_px) { + Some(Rect::from_min_size(pos, size)) + } else { + None + }; + + let outer_rect_px = if let (Some(pos), Some(size)) = (outer_pos_px, outer_size_px) { + Some(Rect::from_min_size(pos, size)) + } else { + None + }; + + let inner_rect = inner_rect_px.map(|r| r / pixels_per_point); + let outer_rect = outer_rect_px.map(|r| r / pixels_per_point); + + ViewportInfo { + ids, + pixels_per_point, + inner_rect, + outer_rect, + close_requested, + } +} + fn open_url_in_browser(_url: &str) { #[cfg(feature = "webbrowser")] if let Err(err) = webbrowser::open(_url) { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index b08732536b3..2a122058567 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -143,19 +143,24 @@ impl RawInput { /// Information about the current viewport, /// given as input each frame. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ViewportInfo { /// Id of us and our parent. pub ids: ViewportIdPair, - /// Viewport inner position and size, only the drowable area - /// unit = physical pixels - pub inner_rect_px: Option, + /// Number of physical pixels per ui point. + pub pixels_per_point: f32, - /// Viewport outer position and size, drowable area + decorations - /// unit = physical pixels - pub outer_rect_px: Option, + /// The inner rectangle of the native window, in monitor space and ui points scale. + /// + /// This is the content rectangle of the viewport. + pub inner_rect: Option, + + /// The outer rectangle of the native window, in monitor space and ui points scale. + /// + /// This is the content rectangle plus decoration chrome. + pub outer_rect: Option, /// The user requested the viewport should close, /// e.g. by pressing the close button in the window decoration. @@ -170,13 +175,15 @@ impl ViewportInfo { pub fn ui(&self, ui: &mut crate::Ui) { let Self { ids, - inner_rect_px, - outer_rect_px, + pixels_per_point, + inner_rect, + outer_rect, close_requested, } = self; ui.label(format!("ids: {ids:?}")); - ui.label(format!("inner_rect_px: {inner_rect_px:?}")); - ui.label(format!("outer_rect_px: {outer_rect_px:?}")); + ui.label(format!("pixels_per_point: {pixels_per_point:?}")); + ui.label(format!("inner_rect: {inner_rect:?}")); + ui.label(format!("outer_rect: {outer_rect:?}")); ui.label(format!("close_requested: {close_requested:?}")); } } diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index 99908cd906b..ae3260671d4 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -221,14 +221,14 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc>]) { ui.add_space(8.0); - if let Some(inner_rect) = ctx.input(|i| i.raw.viewport.inner_rect_px) { + if let Some(inner_rect) = ctx.input(|i| i.raw.viewport.inner_rect) { ui.label(format!( "Inner Rect: Pos: {:?}, Size: {:?}", inner_rect.min, inner_rect.size() )); } - if let Some(outer_rect) = ctx.input(|i| i.raw.viewport.outer_rect_px) { + if let Some(outer_rect) = ctx.input(|i| i.raw.viewport.outer_rect) { ui.label(format!( "Outer Rect: Pos: {:?}, Size: {:?}", outer_rect.min, From 4d5721bdf05351bf651a602023b8ae77ee862f48 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 17 Nov 2023 17:47:17 +0100 Subject: [PATCH 03/36] Make `ViewportInfo` more informative --- crates/eframe/src/native/epi_integration.rs | 2 + crates/eframe/src/native/run.rs | 57 +++++++++++++++------ crates/egui-winit/src/lib.rs | 50 +++++++++++------- crates/egui/src/data/input.rs | 46 +++++++++++------ 4 files changed, 105 insertions(+), 50 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index a984258e84f..ff083a49909 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -313,6 +313,8 @@ pub fn create_storage(_app_name: &str) -> Option> { // ---------------------------------------------------------------------------- /// Everything needed to make a winit-based integration for [`epi`]. +/// +/// Only one instance per app (not one per viewport). pub struct EpiIntegration { pub frame: epi::Frame, last_auto_save: Instant, diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 3b67a21a19a..e1f9096998a 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -458,10 +458,12 @@ mod glow_integration { use egui::{ epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, NumExt as _, - ViewportClass, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, + ViewportClass, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, }; use egui_winit::{create_winit_window_builder, process_viewport_commands, EventResponse}; + use crate::native::epi_integration::EpiIntegration; + use super::*; // Note: that the current Glutin API design tightly couples the GL context with @@ -480,7 +482,7 @@ mod glow_integration { /// a Resumed event. On Android this ensures that any graphics state is only /// initialized once the application has an associated `SurfaceView`. struct GlowWinitRunning { - integration: epi_integration::EpiIntegration, + integration: EpiIntegration, app: Box, // These needs to be shared with the immediate viewport renderer, hence the Rc/Arc/RefCells: @@ -783,6 +785,8 @@ mod glow_integration { ids: ViewportIdPair, class: ViewportClass, builder: ViewportBuilder, + /// Up-to-date info about the viewport. + info: ViewportInfo, /// The user-callback that shows the ui. /// None for immediate viewports. @@ -945,6 +949,7 @@ mod glow_integration { ViewportId::ROOT, Viewport { ids: ViewportIdPair::ROOT, + info: ViewportInfo::from_builder(ViewportIdPair::ROOT, &viewport_builder), class: ViewportClass::Root, builder: viewport_builder, viewport_ui_cb: None, @@ -1187,10 +1192,11 @@ mod glow_integration { focused_viewport, ); - if let Some(viewport) = self.viewports.get(&viewport_id) { + if let Some(viewport) = self.viewports.get_mut(&viewport_id) { if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(viewport_id); egui_winit::process_viewport_commands( + &mut viewport.info, commands, window, is_viewport_focused, @@ -1233,6 +1239,7 @@ mod glow_integration { entry.insert(Viewport { ids, class, + info: ViewportInfo::from_builder(ids, &builder), builder, viewport_ui_cb, window: None, @@ -1261,7 +1268,12 @@ mod glow_integration { viewport.egui_winit = None; } else if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(ids.this); - process_viewport_commands(delta_commands, window, is_viewport_focused); + process_viewport_commands( + &mut viewport.info, + delta_commands, + window, + is_viewport_focused, + ); } entry.into_mut() @@ -1378,7 +1390,7 @@ mod glow_integration { let system_theme = system_theme(&glutin.window(ViewportId::ROOT), &self.native_options); - let mut integration = epi_integration::EpiIntegration::new( + let mut integration = EpiIntegration::new( &glutin.window(ViewportId::ROOT), system_theme, &self.app_name, @@ -1813,10 +1825,12 @@ mod wgpu_integration { use egui::{ DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportClass, ViewportIdMap, - ViewportIdPair, ViewportIdSet, ViewportOutput, + ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, }; use egui_winit::{create_winit_window_builder, process_viewport_commands}; + use crate::native::epi_integration::EpiIntegration; + use super::*; pub struct Viewport { @@ -1826,6 +1840,8 @@ mod wgpu_integration { builder: ViewportBuilder, + info: ViewportInfo, + /// `None` for sync viewports. viewport_ui_cb: Option>, @@ -1888,7 +1904,7 @@ mod wgpu_integration { /// a Resumed event. On Android this ensures that any graphics state is only /// initialized once the application has an associated `SurfaceView`. struct WgpuWinitRunning { - integration: epi_integration::EpiIntegration, + integration: EpiIntegration, /// The users application. app: Box, @@ -1988,7 +2004,7 @@ mod wgpu_integration { let wgpu_render_state = painter.render_state(); let system_theme = system_theme(&window, &self.native_options); - let mut integration = epi_integration::EpiIntegration::new( + let mut integration = EpiIntegration::new( &window, system_theme, &self.app_name, @@ -2065,6 +2081,7 @@ mod wgpu_integration { Viewport { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, + info: ViewportInfo::from_builder(ViewportIdPair::ROOT, &builder), builder, viewport_ui_cb: None, window: Some(Rc::new(window)), @@ -2718,12 +2735,16 @@ mod wgpu_integration { focused_viewport, ); - if let Some(window) = viewports - .get(&viewport_id) - .and_then(|vp| vp.window.as_ref()) - { - let is_viewport_focused = focused_viewport == Some(viewport_id); - egui_winit::process_viewport_commands(commands, window, is_viewport_focused); + if let Some(viewport) = viewports.get_mut(&viewport_id) { + if let Some(window) = viewport.window.as_ref() { + let is_viewport_focused = focused_viewport == Some(viewport_id); + egui_winit::process_viewport_commands( + &mut viewport.info, + commands, + window, + is_viewport_focused, + ); + } } } } @@ -2750,6 +2771,7 @@ mod wgpu_integration { entry.insert(Viewport { ids, class, + info: ViewportInfo::from_builder(ids, &builder), builder, viewport_ui_cb, window: None, @@ -2777,7 +2799,12 @@ mod wgpu_integration { viewport.egui_winit = None; } else if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(ids.this); - process_viewport_commands(delta_commands, window, is_viewport_focused); + process_viewport_commands( + &mut viewport.info, + delta_commands, + window, + is_viewport_focused, + ); } entry.into_mut() diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 4dc36ee7956..701d056056c 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -209,13 +209,9 @@ impl State { && screen_size_in_points.y > 0.0) .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points)); - let viewport_info = viewport_info( - window, - ids, - pixels_per_point, - self.egui_input.viewport.close_requested, - ); - self.egui_input.viewport = viewport_info; + // TODO: one `ViewportInfo` per viewport + self.egui_input.viewport.ids = ids; + update_viewport_info(&mut self.egui_input.viewport, window, pixels_per_point); self.egui_input.take() } @@ -754,12 +750,13 @@ impl State { } } -fn viewport_info( +fn update_viewport_info( + viewport_info: &mut ViewportInfo, window: &winit::window::Window, - ids: ViewportIdPair, pixels_per_point: f32, - close_requested: bool, -) -> ViewportInfo { +) { + crate::profile_function!(); + let has_a_position = match window.is_minimized() { None | Some(true) => false, Some(false) => true, @@ -812,13 +809,24 @@ fn viewport_info( let inner_rect = inner_rect_px.map(|r| r / pixels_per_point); let outer_rect = outer_rect_px.map(|r| r / pixels_per_point); - ViewportInfo { - ids, - pixels_per_point, - inner_rect, - outer_rect, - close_requested, - } + let monitor = window.current_monitor().is_some(); + let monitor_size = if monitor { + let size = window + .current_monitor() + .unwrap() + .size() + .to_logical::(pixels_per_point.into()); + Some(egui::vec2(size.width, size.height)) + } else { + None + }; + + viewport_info.pixels_per_point = pixels_per_point; + viewport_info.monitor_size = monitor_size; + viewport_info.inner_rect = inner_rect; + viewport_info.outer_rect = outer_rect; + viewport_info.fullscreen = Some(window.fullscreen().is_some()); + viewport_info.focused = Some(window.has_focus()); } fn open_url_in_browser(_url: &str) { @@ -1020,6 +1028,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option, window: &winit::window::Window, is_viewport_focused: bool, @@ -1056,7 +1065,10 @@ pub fn process_viewport_commands( log::warn!("{command:?}: {err}"); } } - ViewportCommand::Title(title) => window.set_title(&title), + ViewportCommand::Title(title) => { + window.set_title(&title); + viewport_info.title = Some(title.clone()); + } ViewportCommand::Transparent(v) => window.set_transparent(v), ViewportCommand::Visible(v) => window.set_visible(v), ViewportCommand::OuterPosition(pos) => { diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 2a122058567..b4b6c015467 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -1,6 +1,6 @@ //! The input needed by egui. -use crate::{emath::*, ViewportIdPair}; +use crate::{emath::*, ViewportBuilder, ViewportIdPair}; /// What the integrations provides to egui at the start of each frame. /// @@ -143,15 +143,27 @@ impl RawInput { /// Information about the current viewport, /// given as input each frame. +/// +/// `None` means "unknown". #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ViewportInfo { /// Id of us and our parent. pub ids: ViewportIdPair, + /// Name of the viewport, if known. + pub title: Option, + + /// The user requested the viewport should close, + /// e.g. by pressing the close button in the window decoration. + pub close_requested: bool, + /// Number of physical pixels per ui point. pub pixels_per_point: f32, + /// Current monitor size in egui points. + pub monitor_size: Option, + /// The inner rectangle of the native window, in monitor space and ui points scale. /// /// This is the content rectangle of the viewport. @@ -162,29 +174,31 @@ pub struct ViewportInfo { /// This is the content rectangle plus decoration chrome. pub outer_rect: Option, - /// The user requested the viewport should close, - /// e.g. by pressing the close button in the window decoration. - pub close_requested: bool, + /// Are we in fullscreen mode? + pub fullscreen: Option, + + /// Is the window focused and able to receive input? + /// + /// This should be the same as [`InputState::focused`]. + pub focused: Option, } impl ViewportInfo { + pub fn from_builder(ids: ViewportIdPair, builder: &ViewportBuilder) -> Self { + Self { + ids, + title: builder.title.clone(), + fullscreen: builder.fullscreen, + ..Default::default() + } + } + pub fn take(&mut self) -> Self { core::mem::take(self) } pub fn ui(&self, ui: &mut crate::Ui) { - let Self { - ids, - pixels_per_point, - inner_rect, - outer_rect, - close_requested, - } = self; - ui.label(format!("ids: {ids:?}")); - ui.label(format!("pixels_per_point: {pixels_per_point:?}")); - ui.label(format!("inner_rect: {inner_rect:?}")); - ui.label(format!("outer_rect: {outer_rect:?}")); - ui.label(format!("close_requested: {close_requested:?}")); + ui.label(format!("{self:#?}")); } } From 0bc7e890a76f98237f0a5b4c61d283c731d79603 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 17 Nov 2023 20:52:42 +0100 Subject: [PATCH 04/36] Give a map of all viewports to RawInput --- crates/eframe/src/native/epi_integration.rs | 5 ++- crates/eframe/src/native/run.rs | 49 +++++++-------------- crates/egui-winit/src/lib.rs | 27 +++++++++--- crates/egui/src/context.rs | 8 +++- crates/egui/src/data/input.rs | 48 +++++++++++--------- crates/egui/src/input_state.rs | 5 +++ crates/egui/src/memory.rs | 2 +- examples/multiple_viewports/src/main.rs | 4 +- examples/test_viewports/src/main.rs | 4 +- 9 files changed, 85 insertions(+), 67 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index ff083a49909..5de9a3faf9a 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -434,7 +434,8 @@ impl EpiIntegration { self.egui_ctx .memory_mut(|mem| mem.set_everything_is_visible(true)); - let raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT); + let all_viewports = std::iter::once(ViewportId::ROOT).collect(); + let raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT, &all_viewports); self.pre_update(window); let full_output = self.update(app, None, raw_input); self.post_update(app, window); @@ -485,7 +486,7 @@ impl EpiIntegration { _ => {} } - egui_winit.on_event(&self.egui_ctx, event) + egui_winit.on_event(&self.egui_ctx, event, viewport_id) } pub fn pre_update(&mut self, window: &winit::window::Window) { diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index e1f9096998a..a087c371cac 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -458,7 +458,7 @@ mod glow_integration { use egui::{ epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, NumExt as _, - ViewportClass, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, + ViewportClass, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, }; use egui_winit::{create_winit_window_builder, process_viewport_commands, EventResponse}; @@ -528,11 +528,12 @@ mod glow_integration { let (raw_input, viewport_ui_cb) = { let mut glutin = self.glutin.borrow_mut(); + let all_viewports = glutin.viewports.keys().copied().collect(); let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); let window = viewport.window.as_ref().unwrap(); let egui_winit = viewport.egui_winit.as_mut().unwrap(); - let raw_input = egui_winit.take_egui_input(window, viewport.ids); + let raw_input = egui_winit.take_egui_input(window, viewport.ids, &all_viewports); self.integration.pre_update(window); @@ -785,8 +786,6 @@ mod glow_integration { ids: ViewportIdPair, class: ViewportClass, builder: ViewportBuilder, - /// Up-to-date info about the viewport. - info: ViewportInfo, /// The user-callback that shows the ui. /// None for immediate viewports. @@ -949,7 +948,6 @@ mod glow_integration { ViewportId::ROOT, Viewport { ids: ViewportIdPair::ROOT, - info: ViewportInfo::from_builder(ViewportIdPair::ROOT, &viewport_builder), class: ViewportClass::Root, builder: viewport_builder, viewport_ui_cb: None, @@ -1196,7 +1194,6 @@ mod glow_integration { if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(viewport_id); egui_winit::process_viewport_commands( - &mut viewport.info, commands, window, is_viewport_focused, @@ -1239,7 +1236,6 @@ mod glow_integration { entry.insert(Viewport { ids, class, - info: ViewportInfo::from_builder(ids, &builder), builder, viewport_ui_cb, window: None, @@ -1268,12 +1264,7 @@ mod glow_integration { viewport.egui_winit = None; } else if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(ids.this); - process_viewport_commands( - &mut viewport.info, - delta_commands, - window, - is_viewport_focused, - ); + process_viewport_commands(delta_commands, window, is_viewport_focused); } entry.into_mut() @@ -1557,6 +1548,8 @@ mod glow_integration { let input = { let mut glutin = glutin.borrow_mut(); + let all_viewports = glutin.viewports.keys().copied().collect(); + let Some(viewport) = glutin.viewports.get_mut(&ids.this) else { return; }; @@ -1568,7 +1561,7 @@ mod glow_integration { return; }; - let mut input = winit_state.take_egui_input(window, ids); + let mut input = winit_state.take_egui_input(window, ids, &all_viewports); input.time = Some(beginning.elapsed().as_secs_f64()); input }; @@ -1825,7 +1818,7 @@ mod wgpu_integration { use egui::{ DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportClass, ViewportIdMap, - ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, + ViewportIdPair, ViewportIdSet, ViewportOutput, }; use egui_winit::{create_winit_window_builder, process_viewport_commands}; @@ -1840,8 +1833,6 @@ mod wgpu_integration { builder: ViewportBuilder, - info: ViewportInfo, - /// `None` for sync viewports. viewport_ui_cb: Option>, @@ -2081,7 +2072,6 @@ mod wgpu_integration { Viewport { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, - info: ViewportInfo::from_builder(ViewportIdPair::ROOT, &builder), builder, viewport_ui_cb: None, window: Some(Rc::new(window)), @@ -2172,6 +2162,9 @@ mod wgpu_integration { painter, viewport_from_window, } = &mut *shared.borrow_mut(); + + let all_viewports = viewports.keys().copied().collect(); + let viewport = initialize_or_update_viewport( viewports, ids, @@ -2190,7 +2183,7 @@ mod wgpu_integration { return; }; - let mut input = winit_state.take_egui_input(window, ids); + let mut input = winit_state.take_egui_input(window, ids, &all_viewports); input.time = Some(beginning.elapsed().as_secs_f64()); input }; @@ -2489,6 +2482,8 @@ mod wgpu_integration { return EventResult::Wait; } + let all_viewports = viewports.keys().copied().collect(); + let Some(viewport) = viewports.get_mut(&viewport_id) else { return EventResult::Wait; }; @@ -2513,6 +2508,7 @@ mod wgpu_integration { let raw_input = egui_winit.as_mut().unwrap().take_egui_input( window, ViewportIdPair::from_self_and_parent(viewport_id, ids.parent), + &all_viewports, ); integration.pre_update(window); @@ -2738,12 +2734,7 @@ mod wgpu_integration { if let Some(viewport) = viewports.get_mut(&viewport_id) { if let Some(window) = viewport.window.as_ref() { let is_viewport_focused = focused_viewport == Some(viewport_id); - egui_winit::process_viewport_commands( - &mut viewport.info, - commands, - window, - is_viewport_focused, - ); + egui_winit::process_viewport_commands(commands, window, is_viewport_focused); } } } @@ -2771,7 +2762,6 @@ mod wgpu_integration { entry.insert(Viewport { ids, class, - info: ViewportInfo::from_builder(ids, &builder), builder, viewport_ui_cb, window: None, @@ -2799,12 +2789,7 @@ mod wgpu_integration { viewport.egui_winit = None; } else if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(ids.this); - process_viewport_commands( - &mut viewport.info, - delta_commands, - window, - is_viewport_focused, - ); + process_viewport_commands(delta_commands, window, is_viewport_focused); } entry.into_mut() diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 701d056056c..e36f23baae5 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -15,7 +15,8 @@ pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; use egui::{ - Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdPair, ViewportInfo, + Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdPair, ViewportIdSet, + ViewportInfo, }; pub use winit; @@ -186,6 +187,7 @@ impl State { &mut self, window: &winit::window::Window, ids: ViewportIdPair, + all_viewports: &ViewportIdSet, ) -> egui::RawInput { crate::profile_function!(); @@ -209,9 +211,18 @@ impl State { && screen_size_in_points.y > 0.0) .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points)); - // TODO: one `ViewportInfo` per viewport - self.egui_input.viewport.ids = ids; - update_viewport_info(&mut self.egui_input.viewport, window, pixels_per_point); + // Prune dead viewports: + self.egui_input + .viewports + .retain(|id, _| all_viewports.contains(id)); + + // Update info about our current viewport: + let viewport_info = self.egui_input.viewports.entry(ids.this).or_default(); + viewport_info.parent = Some(ids.parent); + update_viewport_info(viewport_info, window, pixels_per_point); + + // Tell egui which viewport is now active: + self.egui_input.viewport_ids = ids; self.egui_input.take() } @@ -223,6 +234,7 @@ impl State { &mut self, egui_ctx: &egui::Context, event: &winit::event::WindowEvent<'_>, + viewport_id: ViewportId, ) -> EventResponse { crate::profile_function!(); @@ -407,7 +419,9 @@ impl State { // Things that may require repaint: WindowEvent::CloseRequested => { - self.egui_input.viewport.close_requested = true; + if let Some(viewport_info) = self.egui_input.viewports.get_mut(&viewport_id) { + viewport_info.close_requested = true; + } EventResponse { consumed: true, repaint: true, @@ -821,6 +835,7 @@ fn update_viewport_info( None }; + viewport_info.title = Some(window.title()); viewport_info.pixels_per_point = pixels_per_point; viewport_info.monitor_size = monitor_size; viewport_info.inner_rect = inner_rect; @@ -1028,7 +1043,6 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option, window: &winit::window::Window, is_viewport_focused: bool, @@ -1067,7 +1081,6 @@ pub fn process_viewport_commands( } ViewportCommand::Title(title) => { window.set_title(&title); - viewport_info.title = Some(title.clone()); } ViewportCommand::Transparent(v) => window.set_transparent(v), ViewportCommand::Visible(v) => window.set_visible(v), diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index d12056a1b91..e3626b2b4c5 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -227,7 +227,7 @@ struct ContextImpl { impl ContextImpl { fn begin_frame_mut(&mut self, mut new_raw_input: RawInput) { - let ids = new_raw_input.viewport.ids; + let ids = new_raw_input.viewport_ids; let viewport_id = ids.this; self.viewport_stack.push(ids); let viewport = self.viewports.entry(viewport_id).or_default(); @@ -2572,6 +2572,9 @@ impl Context { /// /// You need to call this each frame when the child viewport should exist. /// + /// You can check if the user wants to close the viewport by checking the + /// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewports`]. + /// /// The given callback will be called whenever the child viewport needs repainting, /// e.g. on an event or when [`Self::request_repaint`] is called. /// This means it may be called multiple times, for instance while the @@ -2629,6 +2632,9 @@ impl Context { /// /// You need to call this each frame when the child viewport should exist. /// + /// You can check if the user wants to close the viewport by checking the + /// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewports`]. + /// /// The given ui function will be called immediately. /// This may only be called on the main thread. /// This call will pause the current viewport and render the child viewport in its own window. diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index b4b6c015467..7c5929f35d2 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -1,6 +1,6 @@ //! The input needed by egui. -use crate::{emath::*, ViewportBuilder, ViewportIdPair}; +use crate::{emath::*, ViewportIdMap, ViewportIdPair}; /// What the integrations provides to egui at the start of each frame. /// @@ -13,8 +13,11 @@ use crate::{emath::*, ViewportBuilder, ViewportIdPair}; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct RawInput { - /// Information about the viwport the input is part of. - pub viewport: ViewportInfo, + /// The id of the active viewport, and out parent. + pub viewport_ids: ViewportIdPair, + + /// Information about all egui viewports. + pub viewports: ViewportIdMap, /// Position and size of the area that egui should use, in points. /// Usually you would set this to @@ -75,7 +78,8 @@ pub struct RawInput { impl Default for RawInput { fn default() -> Self { Self { - viewport: ViewportInfo::default(), + viewport_ids: Default::default(), + viewports: Default::default(), screen_rect: None, pixels_per_point: None, max_texture_side: None, @@ -97,7 +101,8 @@ impl RawInput { /// * [`Self::dropped_files`] is moved. pub fn take(&mut self) -> RawInput { RawInput { - viewport: self.viewport.take(), + viewport_ids: self.viewport_ids, + viewports: self.viewports.clone(), screen_rect: self.screen_rect.take(), pixels_per_point: self.pixels_per_point.take(), max_texture_side: self.max_texture_side.take(), @@ -114,7 +119,8 @@ impl RawInput { /// Add on new input. pub fn append(&mut self, newer: Self) { let Self { - viewport, + viewport_ids, + viewports, screen_rect, pixels_per_point, max_texture_side, @@ -127,7 +133,8 @@ impl RawInput { focused, } = newer; - self.viewport = viewport; + self.viewport_ids = viewport_ids; + self.viewports = viewports; 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); @@ -148,8 +155,8 @@ impl RawInput { #[derive(Clone, Debug, Default, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ViewportInfo { - /// Id of us and our parent. - pub ids: ViewportIdPair, + /// Parent viewport, if known. + pub parent: Option, /// Name of the viewport, if known. pub title: Option, @@ -184,15 +191,6 @@ pub struct ViewportInfo { } impl ViewportInfo { - pub fn from_builder(ids: ViewportIdPair, builder: &ViewportBuilder) -> Self { - Self { - ids, - title: builder.title.clone(), - fullscreen: builder.fullscreen, - ..Default::default() - } - } - pub fn take(&mut self) -> Self { core::mem::take(self) } @@ -1001,6 +999,8 @@ fn format_kb_shortcut() { impl RawInput { pub fn ui(&self, ui: &mut crate::Ui) { let Self { + viewport_ids, + viewports, screen_rect, pixels_per_point, max_texture_side, @@ -1011,10 +1011,18 @@ impl RawInput { hovered_files, dropped_files, focused, - viewport, } = self; - viewport.ui(ui); + ui.label(format!( + "Active viwport: {:?}, parent: {:?}", + viewport_ids.this, viewport_ids.parent, + )); + for (id, viewport) in viewports { + ui.group(|ui| { + ui.label(format!("Viewport {id:?}")); + viewport.ui(ui); + }); + } ui.label(format!("screen_rect: {screen_rect:?} points")); ui.label(format!("pixels_per_point: {pixels_per_point:?}")) .on_hover_text( diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index ac03c1ccdd9..86c836eb72c 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -231,6 +231,11 @@ impl InputState { } } + /// Infor about the acitve viewport + pub fn viewport(&self) -> &ViewportInfo { + self.raw.viewports.get(&self.raw.viewport_ids.this).expect("Failed to find current viewport in egui RawInput. This is the fault of the egui backend") + } + #[inline(always)] pub fn screen_rect(&self) -> Rect { self.screen_rect diff --git a/crates/egui/src/memory.rs b/crates/egui/src/memory.rs index 17135760d35..ed1c11df935 100644 --- a/crates/egui/src/memory.rs +++ b/crates/egui/src/memory.rs @@ -559,7 +559,7 @@ impl Memory { self.window_interactions .retain(|id, _| viewports.contains(id)); - self.viewport_id = new_input.viewport.ids.this; + self.viewport_id = new_input.viewport_ids.this; self.interactions .entry(self.viewport_id) .or_default() diff --git a/examples/multiple_viewports/src/main.rs b/examples/multiple_viewports/src/main.rs index d49125bcc06..de5eee4d73d 100644 --- a/examples/multiple_viewports/src/main.rs +++ b/examples/multiple_viewports/src/main.rs @@ -65,7 +65,7 @@ impl eframe::App for MyApp { ui.label("Hello from immediate viewport"); }); - if ctx.input(|i| i.raw.viewport.close_requested) { + if ctx.input(|i| i.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 @@ -90,7 +90,7 @@ impl eframe::App for MyApp { egui::CentralPanel::default().show(ctx, |ui| { ui.label("Hello from deferred viewport"); }); - if ctx.input(|i| i.raw.viewport.close_requested) { + if ctx.input(|i| i.viewport().close_requested) { // Tell parent to close us. 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 ae3260671d4..062f1f1cdb7 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -221,14 +221,14 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc>]) { ui.add_space(8.0); - if let Some(inner_rect) = ctx.input(|i| i.raw.viewport.inner_rect) { + if let Some(inner_rect) = ctx.input(|i| i.viewport().inner_rect) { ui.label(format!( "Inner Rect: Pos: {:?}, Size: {:?}", inner_rect.min, inner_rect.size() )); } - if let Some(outer_rect) = ctx.input(|i| i.raw.viewport.outer_rect) { + if let Some(outer_rect) = ctx.input(|i| i.viewport().outer_rect) { ui.label(format!( "Outer Rect: Pos: {:?}, Size: {:?}", outer_rect.min, From 3e84354eee8469b1de70dd0ecc84bba1a0311da8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 17 Nov 2023 21:20:17 +0100 Subject: [PATCH 05/36] Nicer ui for ViewportInfo --- crates/egui/src/data/input.rs | 60 ++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 7c5929f35d2..7879b71ad6c 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -196,7 +196,65 @@ impl ViewportInfo { } pub fn ui(&self, ui: &mut crate::Ui) { - ui.label(format!("{self:#?}")); + let Self { + parent, + title, + close_requested, + pixels_per_point, + monitor_size, + inner_rect, + outer_rect, + fullscreen, + focused, + } = self; + + crate::Grid::new("viewport_info").show(ui, |ui| { + ui.label("Parent:"); + ui.label(opt_as_str(parent)); + ui.end_row(); + + ui.label("Title:"); + ui.label(opt_as_str(title)); + ui.end_row(); + + ui.label("Close requested:"); + ui.label(close_requested.to_string()); + ui.end_row(); + + ui.label("Pixels per point:"); + ui.label(pixels_per_point.to_string()); + ui.end_row(); + + ui.label("Monitor size:"); + ui.label(opt_as_str(monitor_size)); + ui.end_row(); + + ui.label("Inner rect:"); + ui.label(opt_rect_as_string(inner_rect)); + ui.end_row(); + + ui.label("Outer rect:"); + ui.label(opt_rect_as_string(outer_rect)); + ui.end_row(); + + ui.label("Fullscreen:"); + ui.label(opt_as_str(fullscreen)); + ui.end_row(); + + ui.label("Focused:"); + ui.label(opt_as_str(focused)); + ui.end_row(); + + fn opt_rect_as_string(v: &Option) -> String { + v.as_ref().map_or(String::new(), |r| { + format!("Pos: {:?}, size: {:?}", r.min, r.size()) + }) + } + + fn opt_as_str(v: &Option) -> String { + v.as_ref().map_or(String::new(), |v| format!("{v:?}")) + } + }); } } From be0ae9508dc2b3b65fe45d9ef87a0b115f372d80 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Fri, 17 Nov 2023 21:20:42 +0100 Subject: [PATCH 06/36] Add demo of extra viewport --- .../src/demo/demo_app_windows.rs | 9 ++- .../egui_demo_lib/src/demo/extra_viewport.rs | 70 +++++++++++++++++++ crates/egui_demo_lib/src/demo/mod.rs | 6 ++ 3 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 crates/egui_demo_lib/src/demo/extra_viewport.rs diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 0e82d03c23d..14bf108f3ef 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -27,6 +27,7 @@ impl Default for Demos { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), @@ -61,9 +62,11 @@ impl Demos { pub fn checkboxes(&mut self, ui: &mut Ui) { let Self { demos, open } = self; for demo in demos { - let mut is_open = open.contains(demo.name()); - ui.toggle_value(&mut is_open, demo.name()); - set_open(open, demo.name(), is_open); + if demo.is_enabled(ui.ctx()) { + let mut is_open = open.contains(demo.name()); + ui.toggle_value(&mut is_open, demo.name()); + set_open(open, demo.name(), is_open); + } } } diff --git a/crates/egui_demo_lib/src/demo/extra_viewport.rs b/crates/egui_demo_lib/src/demo/extra_viewport.rs new file mode 100644 index 00000000000..e38dd72acd5 --- /dev/null +++ b/crates/egui_demo_lib/src/demo/extra_viewport.rs @@ -0,0 +1,70 @@ +#[derive(Default)] +pub struct ExtraViewport {} + +impl super::Demo for ExtraViewport { + fn is_enabled(&self, ctx: &egui::Context) -> bool { + !ctx.embed_viewports() + } + + fn name(&self) -> &'static str { + "🗖 Extra Viewport" + } + + fn show(&mut self, ctx: &egui::Context, open: &mut bool) { + if !*open { + return; + } + + let id = egui::Id::new(self.name()); + + ctx.show_viewport_immediate( + egui::ViewportId(id), + egui::ViewportBuilder::default() + .with_title(self.name()) + .with_inner_size([400.0, 512.0]), + |ctx, class| { + if class == egui::ViewportClass::Embedded { + // 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"); + }); + } else { + egui::CentralPanel::default().show(ctx, |ui| { + ui.push_id(id, |ui| { + viewport_contet(ui, ctx, open); + }) + }); + } + }, + ); + } +} + +fn viewport_contet(ui: &mut egui::Ui, ctx: &egui::Context, open: &mut bool) { + ui.label("egui and eframe supports having multiple native windows like this, which egui calls 'viewports'."); + + ui.label(format!( + "This viewport has id: {:?}, child of viewport {:?}", + ctx.viewport_id(), + ctx.parent_viewport_id() + )); + + ui.label("Here you can see all the open viewports:"); + + egui::ScrollArea::vertical().show(ui, |ui| { + let viewports = ui.input(|i| i.raw.viewports.clone()); + for (id, viewport) in viewports { + ui.group(|ui| { + ui.label(format!("viewport {id:?}")); + viewport.ui(ui); + }); + } + }); + + if ui.input(|i| i.viewport().close_requested) { + *open = false; + } +} diff --git a/crates/egui_demo_lib/src/demo/mod.rs b/crates/egui_demo_lib/src/demo/mod.rs index 220fbbcd11e..15ed3ef0506 100644 --- a/crates/egui_demo_lib/src/demo/mod.rs +++ b/crates/egui_demo_lib/src/demo/mod.rs @@ -11,6 +11,7 @@ pub mod context_menu; pub mod dancing_strings; pub mod demo_app_windows; pub mod drag_and_drop; +pub mod extra_viewport; pub mod font_book; pub mod highlighting; pub mod layout_test; @@ -46,6 +47,11 @@ pub trait View { /// Something to view pub trait Demo { + /// Is the demo enabled for this integraton? + fn is_enabled(&self, _ctx: &egui::Context) -> bool { + true + } + /// `&'static` so we can also use it as a key to store open/close state. fn name(&self) -> &'static str; From a1733fa563e9ab13a86da25bb5952c8ade6a0fe9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 10:36:21 +0100 Subject: [PATCH 07/36] Fix: give all viewports as input to `RawInput` --- crates/eframe/src/native/epi_integration.rs | 9 +- crates/eframe/src/native/run.rs | 83 ++++++++++++++++--- crates/egui-winit/src/lib.rs | 50 ++++++----- crates/egui/src/data/input.rs | 4 +- .../egui_demo_lib/src/demo/extra_viewport.rs | 4 +- 5 files changed, 106 insertions(+), 44 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 5de9a3faf9a..ade1535a78e 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -4,7 +4,10 @@ use winit::event_loop::EventLoopWindowTarget; use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _}; -use egui::{DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId, ViewportIdPair}; +use egui::{ + DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId, ViewportIdPair, + ViewportInfo, +}; use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings}; use crate::{epi, Theme, WindowInfo}; @@ -434,8 +437,8 @@ impl EpiIntegration { self.egui_ctx .memory_mut(|mem| mem.set_everything_is_visible(true)); - let all_viewports = std::iter::once(ViewportId::ROOT).collect(); - let raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT, &all_viewports); + let viewports = std::iter::once((ViewportId::ROOT, ViewportInfo::default())).collect(); + let raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT, viewports); self.pre_update(window); let full_output = self.update(app, None, raw_input); self.post_update(app, window); diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index a087c371cac..fa807296125 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -458,7 +458,7 @@ mod glow_integration { use egui::{ epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, NumExt as _, - ViewportClass, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, + ViewportClass, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, }; use egui_winit::{create_winit_window_builder, process_viewport_commands, EventResponse}; @@ -528,12 +528,18 @@ mod glow_integration { let (raw_input, viewport_ui_cb) = { let mut glutin = self.glutin.borrow_mut(); - let all_viewports = glutin.viewports.keys().copied().collect(); + let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); + viewport.update_viewport_info(); + let viewport_infos = glutin + .viewports + .iter() + .map(|(id, viewport)| (*id, viewport.info.clone())) + .collect(); let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); let window = viewport.window.as_ref().unwrap(); let egui_winit = viewport.egui_winit.as_mut().unwrap(); - let raw_input = egui_winit.take_egui_input(window, viewport.ids, &all_viewports); + let raw_input = egui_winit.take_egui_input(window, viewport.ids, viewport_infos); self.integration.pre_update(window); @@ -786,6 +792,7 @@ mod glow_integration { ids: ViewportIdPair, class: ViewportClass, builder: ViewportBuilder, + info: ViewportInfo, /// The user-callback that shows the ui. /// None for immediate viewports. @@ -796,6 +803,19 @@ mod glow_integration { egui_winit: Option, } + impl Viewport { + /// Update the stored `ViewportInfo`. + pub fn update_viewport_info(&mut self) { + let Some(window) = &self.window else { + return; + }; + let Some(egui_winit) = &self.egui_winit else { + return; + }; + egui_winit.update_viewport_info(&mut self.info, window); + } + } + /// This struct will contain both persistent and temporary glutin state. /// /// Platform Quirks: @@ -950,6 +970,7 @@ mod glow_integration { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder: viewport_builder, + info: Default::default(), viewport_ui_cb: None, gl_surface: None, window: window.map(Rc::new), @@ -1237,6 +1258,7 @@ mod glow_integration { ids, class, builder, + info: Default::default(), viewport_ui_cb, window: None, egui_winit: None, @@ -1548,7 +1570,16 @@ mod glow_integration { let input = { let mut glutin = glutin.borrow_mut(); - let all_viewports = glutin.viewports.keys().copied().collect(); + let Some(viewport) = glutin.viewports.get_mut(&ids.this) else { + return; + }; + viewport.update_viewport_info(); + + let viewport_infos = glutin + .viewports + .iter() + .map(|(id, viewport)| (*id, viewport.info.clone())) + .collect(); let Some(viewport) = glutin.viewports.get_mut(&ids.this) else { return; @@ -1561,7 +1592,7 @@ mod glow_integration { return; }; - let mut input = winit_state.take_egui_input(window, ids, &all_viewports); + let mut input = winit_state.take_egui_input(window, ids, viewport_infos); input.time = Some(beginning.elapsed().as_secs_f64()); input }; @@ -1818,7 +1849,7 @@ mod wgpu_integration { use egui::{ DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportClass, ViewportIdMap, - ViewportIdPair, ViewportIdSet, ViewportOutput, + ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput, }; use egui_winit::{create_winit_window_builder, process_viewport_commands}; @@ -1828,10 +1859,9 @@ mod wgpu_integration { pub struct Viewport { ids: ViewportIdPair, - class: ViewportClass, - builder: ViewportBuilder, + info: ViewportInfo, /// `None` for sync viewports. viewport_ui_cb: Option>, @@ -1878,6 +1908,17 @@ mod wgpu_integration { } } } + + /// Update the stored `ViewportInfo`. + pub fn update_viewport_info(&mut self) { + let Some(window) = &self.window else { + return; + }; + let Some(egui_winit) = &self.egui_winit else { + return; + }; + egui_winit.update_viewport_info(&mut self.info, window); + } } pub type Viewports = ViewportIdMap; @@ -2073,6 +2114,7 @@ mod wgpu_integration { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder, + info: Default::default(), viewport_ui_cb: None, window: Some(Rc::new(window)), egui_winit: Some(egui_winit), @@ -2163,7 +2205,14 @@ mod wgpu_integration { viewport_from_window, } = &mut *shared.borrow_mut(); - let all_viewports = viewports.keys().copied().collect(); + if let Some(viewport) = viewports.get_mut(&ids.this) { + viewport.update_viewport_info(); + } + + let viewport_infos = viewports + .iter() + .map(|(id, viewport)| (*id, viewport.info.clone())) + .collect(); let viewport = initialize_or_update_viewport( viewports, @@ -2183,7 +2232,7 @@ mod wgpu_integration { return; }; - let mut input = winit_state.take_egui_input(window, ids, &all_viewports); + let mut input = winit_state.take_egui_input(window, ids, viewport_infos); input.time = Some(beginning.elapsed().as_secs_f64()); input }; @@ -2467,6 +2516,15 @@ mod wgpu_integration { viewports, painter, .. } = &mut *shared_lock; + if let Some(viewport) = viewports.get_mut(&viewport_id) { + viewport.update_viewport_info(); + } + + let viewport_infos = viewports + .iter() + .map(|(id, viewport)| (*id, viewport.info.clone())) + .collect(); + let Some(viewport) = viewports.get(&viewport_id) else { return EventResult::Wait; }; @@ -2482,8 +2540,6 @@ mod wgpu_integration { return EventResult::Wait; } - let all_viewports = viewports.keys().copied().collect(); - let Some(viewport) = viewports.get_mut(&viewport_id) else { return EventResult::Wait; }; @@ -2508,7 +2564,7 @@ mod wgpu_integration { let raw_input = egui_winit.as_mut().unwrap().take_egui_input( window, ViewportIdPair::from_self_and_parent(viewport_id, ids.parent), - &all_viewports, + viewport_infos, ); integration.pre_update(window); @@ -2763,6 +2819,7 @@ mod wgpu_integration { ids, class, builder, + info: Default::default(), viewport_ui_cb, window: None, egui_winit: None, diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index e36f23baae5..0b0fdc8a202 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -15,7 +15,7 @@ pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; use egui::{ - Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdPair, ViewportIdSet, + Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportInfo, }; pub use winit; @@ -27,11 +27,11 @@ pub use window_settings::WindowSettings; use raw_window_handle::HasRawDisplayHandle; -pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 { +pub fn native_pixels_per_point(window: &Window) -> f32 { window.scale_factor() as f32 } -pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 { +pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 { let size = window.inner_size(); egui::vec2(size.width as f32, size.height as f32) } @@ -136,7 +136,7 @@ impl State { #[cfg(feature = "accesskit")] pub fn init_accesskit + Send>( &mut self, - window: &winit::window::Window, + window: &Window, event_loop_proxy: winit::event_loop::EventLoopProxy, initial_tree_update_factory: impl 'static + FnOnce() -> accesskit::TreeUpdate + Send, ) { @@ -181,13 +181,25 @@ impl State { &self.egui_input } + /// Update the given viewport info with the current state of the window. + /// + /// Call before [`Self::update_viewport_info`] + pub fn update_viewport_info(&self, info: &mut ViewportInfo, window: &Window) { + update_viewport_info(info, window, self.pixels_per_point()); + } + /// Prepare for a new frame by extracting the accumulated input, + /// /// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect). + /// + /// You need to set [`egui::RawInput::viewports`] yourself though. + /// Use [`Self::update_viewport_info`] to update the info for each + /// viewport. pub fn take_egui_input( &mut self, - window: &winit::window::Window, + window: &Window, ids: ViewportIdPair, - all_viewports: &ViewportIdSet, + viewport_infos: ViewportIdMap, ) -> egui::RawInput { crate::profile_function!(); @@ -211,19 +223,9 @@ impl State { && screen_size_in_points.y > 0.0) .then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points)); - // Prune dead viewports: - self.egui_input - .viewports - .retain(|id, _| all_viewports.contains(id)); - - // Update info about our current viewport: - let viewport_info = self.egui_input.viewports.entry(ids.this).or_default(); - viewport_info.parent = Some(ids.parent); - update_viewport_info(viewport_info, window, pixels_per_point); - // Tell egui which viewport is now active: self.egui_input.viewport_ids = ids; - + self.egui_input.viewports = viewport_infos; self.egui_input.take() } @@ -692,7 +694,7 @@ impl State { /// * pub fn handle_platform_output( &mut self, - window: &winit::window::Window, + window: &Window, viewport_id: ViewportId, egui_ctx: &egui::Context, platform_output: egui::PlatformOutput, @@ -740,7 +742,7 @@ impl State { } } - fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) { + fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) { if self.current_cursor_icon == Some(cursor_icon) { // Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing. // On other platforms: just early-out to save CPU. @@ -764,11 +766,7 @@ impl State { } } -fn update_viewport_info( - viewport_info: &mut ViewportInfo, - window: &winit::window::Window, - pixels_per_point: f32, -) { +fn update_viewport_info(viewport_info: &mut ViewportInfo, window: &Window, pixels_per_point: f32) { crate::profile_function!(); let has_a_position = match window.is_minimized() { @@ -1044,7 +1042,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option, - window: &winit::window::Window, + window: &Window, is_viewport_focused: bool, ) { crate::profile_function!(); @@ -1318,5 +1316,5 @@ mod profiling_scopes { pub(crate) use profiling_scopes::*; use winit::{ dpi::{LogicalPosition, LogicalSize}, - window::{CursorGrabMode, WindowButtons, WindowLevel}, + window::{CursorGrabMode, Window, WindowButtons, WindowLevel}, }; diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 7879b71ad6c..caa58706741 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -1078,7 +1078,9 @@ impl RawInput { for (id, viewport) in viewports { ui.group(|ui| { ui.label(format!("Viewport {id:?}")); - viewport.ui(ui); + ui.push_id(id, |ui| { + viewport.ui(ui); + }); }); } ui.label(format!("screen_rect: {screen_rect:?} points")); diff --git a/crates/egui_demo_lib/src/demo/extra_viewport.rs b/crates/egui_demo_lib/src/demo/extra_viewport.rs index e38dd72acd5..0e83f40505c 100644 --- a/crates/egui_demo_lib/src/demo/extra_viewport.rs +++ b/crates/egui_demo_lib/src/demo/extra_viewport.rs @@ -59,7 +59,9 @@ fn viewport_contet(ui: &mut egui::Ui, ctx: &egui::Context, open: &mut bool) { for (id, viewport) in viewports { ui.group(|ui| { ui.label(format!("viewport {id:?}")); - viewport.ui(ui); + ui.push_id(id, |ui| { + viewport.ui(ui); + }); }); } }); From bf4222c19c153f088ca4e5adf60843f7ed8c7636 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 10:45:46 +0100 Subject: [PATCH 08/36] Refactor code --- crates/eframe/src/native/epi_integration.rs | 5 +- crates/eframe/src/native/run.rs | 79 +++++++++------------ crates/egui-winit/src/lib.rs | 11 +-- 3 files changed, 39 insertions(+), 56 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index ade1535a78e..b82611e620d 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -437,8 +437,9 @@ impl EpiIntegration { self.egui_ctx .memory_mut(|mem| mem.set_everything_is_visible(true)); - let viewports = std::iter::once((ViewportId::ROOT, ViewportInfo::default())).collect(); - let raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT, viewports); + let mut raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT); + raw_input.viewports = + std::iter::once((ViewportId::ROOT, ViewportInfo::default())).collect(); self.pre_update(window); let full_output = self.update(app, None, raw_input); self.post_update(app, window); diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index fa807296125..f60459f6a53 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -530,20 +530,22 @@ mod glow_integration { let mut glutin = self.glutin.borrow_mut(); let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); viewport.update_viewport_info(); - let viewport_infos = glutin - .viewports - .iter() - .map(|(id, viewport)| (*id, viewport.info.clone())) - .collect(); - let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); let window = viewport.window.as_ref().unwrap(); let egui_winit = viewport.egui_winit.as_mut().unwrap(); - let raw_input = egui_winit.take_egui_input(window, viewport.ids, viewport_infos); + let mut raw_input = egui_winit.take_egui_input(window, viewport.ids); + let viewport_ui_cb = viewport.viewport_ui_cb.clone(); self.integration.pre_update(window); - (raw_input, viewport.viewport_ui_cb.clone()) + raw_input.time = Some(self.integration.beginning.elapsed().as_secs_f64()); + raw_input.viewports = glutin + .viewports + .iter() + .map(|(id, viewport)| (*id, viewport.info.clone())) + .collect(); + + (raw_input, viewport_ui_cb) }; // ------------------------------------------------------------ @@ -1574,17 +1576,6 @@ mod glow_integration { return; }; viewport.update_viewport_info(); - - let viewport_infos = glutin - .viewports - .iter() - .map(|(id, viewport)| (*id, viewport.info.clone())) - .collect(); - - let Some(viewport) = glutin.viewports.get_mut(&ids.this) else { - return; - }; - let Some(winit_state) = &mut viewport.egui_winit else { return; }; @@ -1592,9 +1583,14 @@ mod glow_integration { return; }; - let mut input = winit_state.take_egui_input(window, ids, viewport_infos); - input.time = Some(beginning.elapsed().as_secs_f64()); - input + let mut raw_input = winit_state.take_egui_input(window, ids); + raw_input.viewports = glutin + .viewports + .iter() + .map(|(id, viewport)| (*id, viewport.info.clone())) + .collect(); + raw_input.time = Some(beginning.elapsed().as_secs_f64()); + raw_input }; // --------------------------------------------------- @@ -2205,15 +2201,6 @@ mod wgpu_integration { viewport_from_window, } = &mut *shared.borrow_mut(); - if let Some(viewport) = viewports.get_mut(&ids.this) { - viewport.update_viewport_info(); - } - - let viewport_infos = viewports - .iter() - .map(|(id, viewport)| (*id, viewport.info.clone())) - .collect(); - let viewport = initialize_or_update_viewport( viewports, ids, @@ -2222,17 +2209,21 @@ mod wgpu_integration { None, None, ); - if viewport.window.is_none() { viewport.init_window(viewport_from_window, painter, event_loop); } + viewport.update_viewport_info(); let (Some(window), Some(winit_state)) = (&viewport.window, &mut viewport.egui_winit) else { return; }; - let mut input = winit_state.take_egui_input(window, ids, viewport_infos); + let mut input = winit_state.take_egui_input(window, ids); + input.viewports = viewports + .iter() + .map(|(id, viewport)| (*id, viewport.info.clone())) + .collect(); input.time = Some(beginning.elapsed().as_secs_f64()); input }; @@ -2516,15 +2507,6 @@ mod wgpu_integration { viewports, painter, .. } = &mut *shared_lock; - if let Some(viewport) = viewports.get_mut(&viewport_id) { - viewport.update_viewport_info(); - } - - let viewport_infos = viewports - .iter() - .map(|(id, viewport)| (*id, viewport.info.clone())) - .collect(); - let Some(viewport) = viewports.get(&viewport_id) else { return EventResult::Wait; }; @@ -2543,6 +2525,7 @@ mod wgpu_integration { let Some(viewport) = viewports.get_mut(&viewport_id) else { return EventResult::Wait; }; + viewport.update_viewport_info(); let Viewport { ids, @@ -2551,6 +2534,7 @@ mod wgpu_integration { egui_winit, .. } = viewport; + let viewport_ui_cb = viewport_ui_cb.clone(); let Some(window) = window else { return EventResult::Wait; @@ -2561,15 +2545,20 @@ mod wgpu_integration { log::warn!("Failed to set window: {err}"); } - let raw_input = egui_winit.as_mut().unwrap().take_egui_input( + let mut raw_input = egui_winit.as_mut().unwrap().take_egui_input( window, ViewportIdPair::from_self_and_parent(viewport_id, ids.parent), - viewport_infos, ); integration.pre_update(window); - (viewport_ui_cb.clone(), raw_input) + raw_input.time = Some(integration.beginning.elapsed().as_secs_f64()); + raw_input.viewports = viewports + .iter() + .map(|(id, viewport)| (*id, viewport.info.clone())) + .collect(); + + (viewport_ui_cb, raw_input) }; // ------------------------------------------------------------ diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 0b0fdc8a202..0994cac0d7e 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -15,8 +15,7 @@ pub use egui; #[cfg(feature = "accesskit")] use egui::accesskit; use egui::{ - Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, - ViewportInfo, + Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdPair, ViewportInfo, }; pub use winit; @@ -195,12 +194,7 @@ impl State { /// You need to set [`egui::RawInput::viewports`] yourself though. /// Use [`Self::update_viewport_info`] to update the info for each /// viewport. - pub fn take_egui_input( - &mut self, - window: &Window, - ids: ViewportIdPair, - viewport_infos: ViewportIdMap, - ) -> egui::RawInput { + pub fn take_egui_input(&mut self, window: &Window, ids: ViewportIdPair) -> egui::RawInput { crate::profile_function!(); let pixels_per_point = self.pixels_per_point(); @@ -225,7 +219,6 @@ impl State { // Tell egui which viewport is now active: self.egui_input.viewport_ids = ids; - self.egui_input.viewports = viewport_infos; self.egui_input.take() } From a73a194d879cd30f998caea33e41120284cfcc72 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 10:55:40 +0100 Subject: [PATCH 09/36] Remove dead code --- crates/eframe/src/native/run.rs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index f60459f6a53..121eb7659ac 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -2358,20 +2358,6 @@ mod wgpu_integration { Ok(match event { winit::event::Event::Resumed => { let running = if let Some(running) = &self.running { - if !running - .shared - .borrow() - .viewports - .contains_key(&ViewportId::ROOT) - { - create_window( - event_loop, - running.integration.frame.storage(), - &self.app_name, - &mut self.native_options, - )?; - running.set_window(ViewportId::ROOT)?; - } running } else { let storage = epi_integration::create_storage( @@ -2444,18 +2430,6 @@ mod wgpu_integration { } impl WgpuWinitRunning { - fn set_window(&self, id: ViewportId) -> Result<(), egui_wgpu::WgpuError> { - crate::profile_function!(); - let mut shared = self.shared.borrow_mut(); - let SharedState { - viewports, painter, .. - } = &mut *shared; - if let Some(Viewport { window, .. }) = viewports.get(&id) { - return pollster::block_on(painter.set_window(id, window.as_deref())); - } - Ok(()) - } - fn save_and_destroy(&mut self) { crate::profile_function!(); From d95cf23fdbdf8f037cf95c7e23f9a569b397dac2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 11:09:45 +0100 Subject: [PATCH 10/36] Add minimized/maximized to ViewportInfo --- crates/eframe/src/native/run.rs | 61 ++++++++++++++++++++++++--------- crates/egui-winit/src/lib.rs | 11 ++++-- crates/egui/src/data/input.rs | 16 +++++++++ crates/egui_glow/src/winit.rs | 8 +++-- 4 files changed, 75 insertions(+), 21 deletions(-) diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 121eb7659ac..bb93f6d2897 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -960,9 +960,12 @@ mod glow_integration { let mut viewport_from_window = HashMap::default(); let mut window_from_viewport = ViewportIdMap::default(); + let mut info = ViewportInfo::default(); if let Some(window) = &window { viewport_from_window.insert(window.id(), ViewportId::ROOT); window_from_viewport.insert(ViewportId::ROOT, window.id()); + info.minimized = window.is_minimized(); + info.maximized = Some(window.is_maximized()); } let mut viewports = ViewportIdMap::default(); @@ -972,7 +975,7 @@ mod glow_integration { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder: viewport_builder, - info: Default::default(), + info, viewport_ui_cb: None, gl_surface: None, window: window.map(Rc::new), @@ -1042,13 +1045,14 @@ mod glow_integration { window } else { log::trace!("Window doesn't exist yet. Creating one now with finalize_window"); - viewport - .window - .insert(Rc::new(glutin_winit::finalize_window( - event_loop, - create_winit_window_builder(&viewport.builder), - &self.gl_config, - )?)) + let window = glutin_winit::finalize_window( + event_loop, + create_winit_window_builder(&viewport.builder), + &self.gl_config, + )?; + viewport.info.minimized = window.is_minimized(); + viewport.info.maximized = Some(window.is_maximized()); + viewport.window.insert(Rc::new(window)) }; { @@ -1217,6 +1221,7 @@ mod glow_integration { if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(viewport_id); egui_winit::process_viewport_commands( + &mut viewport.info, commands, window, is_viewport_focused, @@ -1288,7 +1293,12 @@ mod glow_integration { viewport.egui_winit = None; } else if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(ids.this); - process_viewport_commands(delta_commands, window, is_viewport_focused); + process_viewport_commands( + &mut viewport.info, + delta_commands, + window, + is_viewport_focused, + ); } entry.into_mut() @@ -1882,22 +1892,25 @@ mod wgpu_integration { let viewport_id = self.ids.this; match create_winit_window_builder(&self.builder).build(event_loop) { - Ok(new_window) => { - windows_id.insert(new_window.id(), viewport_id); + Ok(window) => { + windows_id.insert(window.id(), viewport_id); if let Err(err) = - pollster::block_on(painter.set_window(viewport_id, Some(&new_window))) + pollster::block_on(painter.set_window(viewport_id, Some(&window))) { log::error!("on set_window: viewport_id {viewport_id:?} {err}"); } self.egui_winit = Some(egui_winit::State::new( event_loop, - Some(new_window.scale_factor() as f32), + Some(window.scale_factor() as f32), painter.max_texture_side(), )); - self.window = Some(Rc::new(new_window)); + self.info.minimized = window.is_minimized(); + self.info.maximized = Some(window.is_maximized()); + + self.window = Some(Rc::new(window)); } Err(err) => { log::error!("Failed to create window: {err}"); @@ -2110,7 +2123,11 @@ mod wgpu_integration { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder, - info: Default::default(), + info: ViewportInfo { + minimized: window.is_minimized(), + maximized: Some(window.is_maximized()), + ..Default::default() + }, viewport_ui_cb: None, window: Some(Rc::new(window)), egui_winit: Some(egui_winit), @@ -2753,7 +2770,12 @@ mod wgpu_integration { if let Some(viewport) = viewports.get_mut(&viewport_id) { if let Some(window) = viewport.window.as_ref() { let is_viewport_focused = focused_viewport == Some(viewport_id); - egui_winit::process_viewport_commands(commands, window, is_viewport_focused); + egui_winit::process_viewport_commands( + &mut viewport.info, + commands, + window, + is_viewport_focused, + ); } } } @@ -2809,7 +2831,12 @@ mod wgpu_integration { viewport.egui_winit = None; } else if let Some(window) = &viewport.window { let is_viewport_focused = focused_viewport == Some(ids.this); - process_viewport_commands(delta_commands, window, is_viewport_focused); + process_viewport_commands( + &mut viewport.info, + delta_commands, + window, + is_viewport_focused, + ); } entry.into_mut() diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 0994cac0d7e..34e52dc1c14 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1034,6 +1034,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option, window: &Window, is_viewport_focused: bool, @@ -1111,8 +1112,14 @@ pub fn process_viewport_commands( WindowButtons::empty() }, ), - ViewportCommand::Minimized(v) => window.set_minimized(v), - ViewportCommand::Maximized(v) => window.set_maximized(v), + ViewportCommand::Minimized(v) => { + window.set_minimized(v); + info.minimized = Some(v); + } + ViewportCommand::Maximized(v) => { + window.set_maximized(v); + info.maximized = Some(v); + } ViewportCommand::Fullscreen(v) => { window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None))); } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index caa58706741..47eac5d50ec 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -181,6 +181,12 @@ pub struct ViewportInfo { /// This is the content rectangle plus decoration chrome. pub outer_rect: Option, + /// Are we minimized? + pub minimized: Option, + + /// Are we maximized? + pub maximized: Option, + /// Are we in fullscreen mode? pub fullscreen: Option, @@ -204,6 +210,8 @@ impl ViewportInfo { monitor_size, inner_rect, outer_rect, + minimized, + maximized, fullscreen, focused, } = self; @@ -237,6 +245,14 @@ impl ViewportInfo { ui.label(opt_rect_as_string(outer_rect)); ui.end_row(); + ui.label("Minimized:"); + ui.label(opt_as_str(minimized)); + ui.end_row(); + + ui.label("Maximized:"); + ui.label(opt_as_str(maximized)); + ui.end_row(); + ui.label("Fullscreen:"); ui.label(opt_as_str(fullscreen)); ui.end_row(); diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index ad47d6ebcef..6f20404a042 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -12,6 +12,8 @@ pub struct EguiGlow { pub egui_winit: egui_winit::State, pub painter: crate::Painter, + viewport_info: egui::ViewportInfo, + // output from the last update: shapes: Vec, pixels_per_point: f32, @@ -43,6 +45,7 @@ impl EguiGlow { egui_ctx: Default::default(), egui_winit, painter, + viewport_info: Default::default(), shapes: Default::default(), pixels_per_point, textures_delta: Default::default(), @@ -50,7 +53,8 @@ impl EguiGlow { } pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) -> EventResponse { - self.egui_winit.on_event(&self.egui_ctx, event) + self.egui_winit + .on_event(&self.egui_ctx, event, ViewportId::ROOT) } /// Call [`Self::paint`] later to paint. @@ -71,7 +75,7 @@ impl EguiGlow { log::warn!("Multiple viewports not yet supported by EguiGlow"); } for (_, ViewportOutput { commands, .. }) in viewport_output { - egui_winit::process_viewport_commands(commands, window, true); + egui_winit::process_viewport_commands(&mut self.viewport_info, commands, window, true); } self.egui_winit.handle_platform_output( From b55e06d0879fc64f5665a3fbdab0af9b1d130e65 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 11:16:28 +0100 Subject: [PATCH 11/36] Use viewport commands in custom_window_frame example --- examples/custom_window_frame/src/main.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 8ea32fbf74a..6fdd37bdd05 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -2,7 +2,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use eframe::egui; +use eframe::egui::{self, ViewportCommand}; fn main() -> Result<(), eframe::Error> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). @@ -112,9 +112,11 @@ fn title_bar_ui( // Interact with the title bar (drag to move window): if title_bar_response.double_clicked() { - frame.set_maximized(!frame.info().window_info.maximized); + let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false)); + ui.ctx() + .send_viewport_command(ViewportCommand::Maximized(!is_maximized)); } else if title_bar_response.is_pointer_button_down_on() { - frame.drag_window(); + ui.ctx().send_viewport_command(ViewportCommand::StartDrag); } ui.allocate_ui_at_rect(title_bar_rect, |ui| { @@ -140,19 +142,22 @@ fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) { frame.close(); } - if frame.info().window_info.maximized { + let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false)); + if is_maximized { let maximized_response = ui .add(Button::new(RichText::new("🗗").size(button_height))) .on_hover_text("Restore window"); if maximized_response.clicked() { - frame.set_maximized(false); + ui.ctx() + .send_viewport_command(ViewportCommand::Maximized(false)); } } else { let maximized_response = ui .add(Button::new(RichText::new("🗗").size(button_height))) .on_hover_text("Maximize window"); if maximized_response.clicked() { - frame.set_maximized(true); + ui.ctx() + .send_viewport_command(ViewportCommand::Maximized(true)); } } @@ -160,6 +165,7 @@ fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) { .add(Button::new(RichText::new("🗕").size(button_height))) .on_hover_text("Minimize the window"); if minimized_response.clicked() { - frame.set_minimized(true); + ui.ctx() + .send_viewport_command(ViewportCommand::Minimized(true)); } } From 2c7d6fb40268fe23021a2c24f49bc58b121dee28 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 13:20:09 +0100 Subject: [PATCH 12/36] Use viewport commands in the demo app --- crates/egui_demo_app/src/backend_panel.rs | 75 +++++------------------ crates/egui_demo_app/src/wrap_app.rs | 3 +- 2 files changed, 16 insertions(+), 62 deletions(-) diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index fb0d9ae4979..23ababe29ea 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -158,13 +158,14 @@ impl BackendPanel { { ui.horizontal(|ui| { { - let mut fullscreen = frame.info().window_info.fullscreen; + let mut fullscreen = ui.input(|i| i.viewport().fullscreen.unwrap_or(false)); if ui .checkbox(&mut fullscreen, "🗖 Fullscreen (F11)") .on_hover_text("Fullscreen the window") .changed() { - frame.set_fullscreen(fullscreen); + ui.ctx() + .send_viewport_command(egui::ViewportCommand::Fullscreen(fullscreen)); } } @@ -173,25 +174,26 @@ impl BackendPanel { .on_hover_text("Resize the window to be small like a phone.") .clicked() { - // frame.set_window_size(egui::vec2(375.0, 812.0)); // iPhone 12 mini - frame.set_window_size(egui::vec2(375.0, 667.0)); // iPhone SE 2nd gen - frame.set_fullscreen(false); + // let size = egui::vec2(375.0, 812.0); // iPhone 12 mini + let size = egui::vec2(375.0, 667.0); // iPhone SE 2nd gen + + ui.ctx() + .send_viewport_command(egui::ViewportCommand::InnerSize(size)); + ui.ctx() + .send_viewport_command(egui::ViewportCommand::Fullscreen(false)); ui.close_menu(); } }); - if !frame.info().window_info.fullscreen + let fullscreen = ui.input(|i| i.viewport().fullscreen.unwrap_or(false)); + if !fullscreen && ui .button("Drag me to drag window") .is_pointer_button_down_on() { - frame.drag_window(); + ui.ctx() + .send_viewport_command(egui::ViewportCommand::StartDrag); } - - ui.button("Native window info (hover me)") - .on_hover_ui(|ui| { - window_info_ui(ui, &frame.info().window_info); - }); } } @@ -285,55 +287,6 @@ impl BackendPanel { } } -#[cfg(not(target_arch = "wasm32"))] -fn window_info_ui(ui: &mut egui::Ui, window_info: &eframe::WindowInfo) { - let eframe::WindowInfo { - position, - fullscreen, - minimized, - maximized, - focused, - size, - monitor_size, - } = window_info; - - egui::Grid::new("window_info_grid") - .num_columns(2) - .show(ui, |ui| { - if let Some(egui::Pos2 { x, y }) = position { - ui.label("Position:"); - ui.monospace(format!("{x:.0}, {y:.0}")); - ui.end_row(); - } - - ui.label("Fullscreen:"); - ui.label(fullscreen.to_string()); - ui.end_row(); - - ui.label("Minimized:"); - ui.label(minimized.to_string()); - ui.end_row(); - - ui.label("Maximized:"); - ui.label(maximized.to_string()); - ui.end_row(); - - ui.label("Focused:"); - ui.label(focused.to_string()); - ui.end_row(); - - ui.label("Window size:"); - ui.monospace(format!("{x:.0} x {y:.0}", x = size.x, y = size.y)); - ui.end_row(); - - if let Some(egui::Vec2 { x, y }) = monitor_size { - ui.label("Monitor size:"); - ui.monospace(format!("{x:.0} x {y:.0}")); - ui.end_row(); - } - }); -} - // ---------------------------------------------------------------------------- #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index fe32733ad66..73d0339462f 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -259,7 +259,8 @@ impl eframe::App for WrapApp { #[cfg(not(target_arch = "wasm32"))] if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) { - frame.set_fullscreen(!frame.info().window_info.fullscreen); + let fullscreen = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false)); + ctx.send_viewport_command(egui::ViewportCommand::Fullscreen(!fullscreen)); } let mut cmd = Command::Nothing; From 9712ff143f68aacd9170a4ff665a494bab035808 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 13:23:19 +0100 Subject: [PATCH 13/36] Code simplification --- crates/eframe/src/native/run.rs | 40 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index bb93f6d2897..ab2e6e82ad0 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -1208,7 +1208,7 @@ mod glow_integration { { let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent); - initialize_or_update_viewport( + let viewport = initialize_or_update_viewport( &mut self.viewports, ids, class, @@ -1217,16 +1217,14 @@ mod glow_integration { focused_viewport, ); - if let Some(viewport) = self.viewports.get_mut(&viewport_id) { - if let Some(window) = &viewport.window { - let is_viewport_focused = focused_viewport == Some(viewport_id); - egui_winit::process_viewport_commands( - &mut viewport.info, - commands, - window, - is_viewport_focused, - ); - } + if let Some(window) = &viewport.window { + let is_viewport_focused = focused_viewport == Some(viewport_id); + egui_winit::process_viewport_commands( + &mut viewport.info, + commands, + window, + is_viewport_focused, + ); } } @@ -2758,7 +2756,7 @@ mod wgpu_integration { { let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent); - initialize_or_update_viewport( + let viewport = initialize_or_update_viewport( viewports, ids, class, @@ -2767,16 +2765,14 @@ mod wgpu_integration { focused_viewport, ); - if let Some(viewport) = viewports.get_mut(&viewport_id) { - if let Some(window) = viewport.window.as_ref() { - let is_viewport_focused = focused_viewport == Some(viewport_id); - egui_winit::process_viewport_commands( - &mut viewport.info, - commands, - window, - is_viewport_focused, - ); - } + if let Some(window) = viewport.window.as_ref() { + let is_viewport_focused = focused_viewport == Some(viewport_id); + egui_winit::process_viewport_commands( + &mut viewport.info, + commands, + window, + is_viewport_focused, + ); } } } From 6aa25d79325dc937e26505fe0a50f83ed31d9b45 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 13:29:58 +0100 Subject: [PATCH 14/36] Repaint viewports we send events to --- crates/egui/src/context.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index e3626b2b4c5..ef5c18106bb 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2564,6 +2564,7 @@ impl Context { /// This lets you affect another viewport, e.g. resizing its window. pub fn send_viewport_command_to(&self, id: ViewportId, command: ViewportCommand) { self.write(|ctx| ctx.viewport_for(id).commands.push(command)); + self.request_repaint_of(id); } /// This creates a new native window, if possible. From d3113dc26ddda9e9e921836b3d4bee3d70ac15f2 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 13:37:04 +0100 Subject: [PATCH 15/36] Replace eframe::Frame::close with viewport command --- crates/eframe/src/native/epi_integration.rs | 5 ++++ crates/egui-winit/src/lib.rs | 9 ++++--- crates/egui/src/viewport.rs | 6 +++++ crates/egui_demo_app/src/backend_panel.rs | 2 +- examples/confirm_exit/src/main.rs | 4 ++-- examples/custom_window_frame/src/main.rs | 26 +++++++-------------- examples/serial_windows/src/main.rs | 4 ++-- 7 files changed, 30 insertions(+), 26 deletions(-) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index b82611e620d..5919bb09303 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -520,6 +520,11 @@ impl EpiIntegration { viewport_ui_cb(egui_ctx); } else { // Root viewport + if egui_ctx.input(|i| i.viewport().close_requested) { + self.close = app.on_close_event(); + log::debug!("App::on_close_event returned {}", self.close); + } + crate::profile_scope!("App::update"); app.update(egui_ctx, &mut self.frame); } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 34e52dc1c14..0a77f6d4278 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1045,7 +1045,10 @@ pub fn process_viewport_commands( for command in commands { match command { - egui::ViewportCommand::StartDrag => { + ViewportCommand::Close => { + info.close_requested = true; + } + ViewportCommand::StartDrag => { // if this is not checked on x11 the input will be permanently taken until the app is killed! if is_viewport_focused { if let Err(err) = window.drag_window() { @@ -1053,12 +1056,12 @@ pub fn process_viewport_commands( } } } - egui::ViewportCommand::InnerSize(size) => { + ViewportCommand::InnerSize(size) => { let width = size.x.max(1.0); let height = size.y.max(1.0); window.set_inner_size(LogicalSize::new(width, height)); } - egui::ViewportCommand::BeginResize(direction) => { + ViewportCommand::BeginResize(direction) => { if let Err(err) = window.drag_resize_window(match direction { egui::viewport::ResizeDirection::North => ResizeDirection::North, egui::viewport::ResizeDirection::South => ResizeDirection::South, diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index c3fa7cec009..c09f38025d7 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -663,6 +663,12 @@ pub enum ResizeDirection { #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ViewportCommand { + /// Request this viewport to be closed. + /// + /// For the root viewport, this usually results in the application shutting down. + /// For other viewports, the [`ViewportInfo::close_requested`] flag will be set. + Close, + /// Set the title Title(String), diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 23ababe29ea..bd92b3c3a2a 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -117,7 +117,7 @@ impl BackendPanel { { ui.separator(); if ui.button("Quit").clicked() { - frame.close(); + ui.ctx().send_viewport_command(egui::ViewportCommand::Close); } } diff --git a/examples/confirm_exit/src/main.rs b/examples/confirm_exit/src/main.rs index 05b45bd0b4e..bc97e145bb9 100644 --- a/examples/confirm_exit/src/main.rs +++ b/examples/confirm_exit/src/main.rs @@ -27,7 +27,7 @@ impl eframe::App for MyApp { self.allowed_to_close } - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Try to close the window"); }); @@ -45,7 +45,7 @@ impl eframe::App for MyApp { if ui.button("Yes!").clicked() { self.allowed_to_close = true; - frame.close(); + ui.ctx().send_viewport_command(egui::ViewportCommand::Close); } }); }); diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 6fdd37bdd05..9ce06f3c821 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -30,8 +30,8 @@ impl eframe::App for MyApp { egui::Rgba::TRANSPARENT.to_array() // Make sure we don't paint anything behind the rounded corners } - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - custom_window_frame(ctx, frame, "egui with custom frame", |ui| { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + custom_window_frame(ctx, "egui with custom frame", |ui| { ui.label("This is just the contents of the window."); ui.horizontal(|ui| { ui.label("egui theme:"); @@ -41,12 +41,7 @@ impl eframe::App for MyApp { } } -fn custom_window_frame( - ctx: &egui::Context, - frame: &mut eframe::Frame, - title: &str, - add_contents: impl FnOnce(&mut egui::Ui), -) { +fn custom_window_frame(ctx: &egui::Context, title: &str, add_contents: impl FnOnce(&mut egui::Ui)) { use egui::*; let panel_frame = egui::Frame { @@ -66,7 +61,7 @@ fn custom_window_frame( rect.max.y = rect.min.y + title_bar_height; rect }; - title_bar_ui(ui, frame, title_bar_rect, title); + title_bar_ui(ui, title_bar_rect, title); // Add the contents: let content_rect = { @@ -80,12 +75,7 @@ fn custom_window_frame( }); } -fn title_bar_ui( - ui: &mut egui::Ui, - frame: &mut eframe::Frame, - title_bar_rect: eframe::epaint::Rect, - title: &str, -) { +fn title_bar_ui(ui: &mut egui::Ui, title_bar_rect: eframe::epaint::Rect, title: &str) { use egui::*; let painter = ui.painter(); @@ -124,13 +114,13 @@ fn title_bar_ui( ui.spacing_mut().item_spacing.x = 0.0; ui.visuals_mut().button_frame = false; ui.add_space(8.0); - close_maximize_minimize(ui, frame); + close_maximize_minimize(ui); }); }); } /// Show some close/maximize/minimize buttons for the native window. -fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) { +fn close_maximize_minimize(ui: &mut egui::Ui) { use egui::{Button, RichText}; let button_height = 12.0; @@ -139,7 +129,7 @@ fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) { .add(Button::new(RichText::new("❌").size(button_height))) .on_hover_text("Close the window"); if close_response.clicked() { - frame.close(); + ui.ctx().send_viewport_command(egui::ViewportCommand::Close); } let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false)); diff --git a/examples/serial_windows/src/main.rs b/examples/serial_windows/src/main.rs index 5e85d646781..03f89dab98e 100644 --- a/examples/serial_windows/src/main.rs +++ b/examples/serial_windows/src/main.rs @@ -46,7 +46,7 @@ struct MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { let label_text = if self.has_next { "When this window is closed the next will be opened after a short delay" @@ -56,7 +56,7 @@ impl eframe::App for MyApp { ui.label(label_text); if ui.button("Close").clicked() { eprintln!("Pressed Close button"); - frame.close(); + ui.ctx().send_viewport_command(egui::ViewportCommand::Close); } }); } From a856744e1f8e73542cbea9ba66971ba4e6b1345b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 15:53:34 +0100 Subject: [PATCH 16/36] Add a TODO --- crates/egui-winit/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 0a77f6d4278..5eabb2021e8 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1049,7 +1049,11 @@ pub fn process_viewport_commands( info.close_requested = true; } ViewportCommand::StartDrag => { - // if this is not checked on x11 the input will be permanently taken until the app is killed! + // If `is_viewport_focused` is not checked on x11 the input will be permanently taken until the app is killed! + + // TODO: check that the left mouse-button was pressed down recently, + // or we will have bugs on Windows. + // See https://github.com/emilk/egui/pull/1108 if is_viewport_focused { if let Err(err) = window.drag_window() { log::warn!("{command:?}: {err}"); From 4122fcf8813b13731306fb1a8c3d1e916249f871 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 16:04:04 +0100 Subject: [PATCH 17/36] Refactor user attention request --- crates/egui-winit/src/lib.rs | 13 +++++++------ crates/egui/src/data/output.rs | 1 + crates/egui/src/viewport.rs | 10 ++-------- examples/user_attention/src/main.rs | 20 +++++++++++--------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 5eabb2021e8..5f79186b2ba 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1156,14 +1156,15 @@ pub fn process_viewport_commands( egui::viewport::IMEPurpose::Normal => winit::window::ImePurpose::Normal, }), ViewportCommand::RequestUserAttention(a) => { - window.request_user_attention(a.map(|a| match a { - egui::viewport::UserAttentionType::Critical => { - winit::window::UserAttentionType::Critical + window.request_user_attention(match a { + egui::UserAttentionType::Reset => None, + egui::UserAttentionType::Critical => { + Some(winit::window::UserAttentionType::Critical) } - egui::viewport::UserAttentionType::Informational => { - winit::window::UserAttentionType::Informational + egui::UserAttentionType::Informational => { + Some(winit::window::UserAttentionType::Informational) } - })); + }); } ViewportCommand::SetTheme(t) => window.set_theme(match t { egui::SystemTheme::Light => Some(winit::window::Theme::Light), diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index 2e94b5f9953..20b782f7a7f 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -210,6 +210,7 @@ impl OpenUrl { /// /// [user_attention_type]: https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum UserAttentionType { /// Request an elevated amount of animations and flair for the window and the task bar or dock icon. Critical, diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index c09f38025d7..378ea94aef6 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -636,13 +636,6 @@ pub enum CursorGrab { Locked, } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -pub enum UserAttentionType { - Informational, - Critical, -} - #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ResizeDirection { @@ -729,7 +722,8 @@ pub enum ViewportCommand { IMEAllowed(bool), IMEPurpose(IMEPurpose), - RequestUserAttention(Option), + /// Bring attention to the window. + RequestUserAttention(crate::UserAttentionType), SetTheme(SystemTheme), diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 8134062fdfa..995f30b42dd 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -1,14 +1,12 @@ -use eframe::{ - egui::{Button, CentralPanel, Context, UserAttentionType}, - CreationContext, NativeOptions, -}; +use eframe::{egui, CreationContext, NativeOptions}; +use egui::{Button, CentralPanel, Context, UserAttentionType}; use std::time::{Duration, SystemTime}; fn main() -> eframe::Result<()> { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). let native_options = NativeOptions { - initial_window_size: Some(eframe::egui::vec2(400., 200.)), + initial_window_size: Some(egui::vec2(400., 200.)), ..Default::default() }; eframe::run_native( @@ -54,11 +52,13 @@ impl Application { } impl eframe::App for Application { - fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) { + fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { if let Some(request_at) = self.request_at { if request_at < SystemTime::now() { self.request_at = None; - frame.request_user_attention(self.attention); + ctx.send_viewport_command(egui::ViewportCommand::RequestUserAttention( + self.attention, + )); if self.auto_reset { self.auto_reset = false; self.reset_at = Some(SystemTime::now() + Self::attention_reset_timeout()); @@ -69,7 +69,9 @@ impl eframe::App for Application { if let Some(reset_at) = self.reset_at { if reset_at < SystemTime::now() { self.reset_at = None; - frame.request_user_attention(UserAttentionType::Reset); + ctx.send_viewport_command(egui::ViewportCommand::RequestUserAttention( + UserAttentionType::Reset, + )); } } @@ -77,7 +79,7 @@ impl eframe::App for Application { ui.vertical(|ui| { ui.horizontal(|ui| { ui.label("Attention type:"); - eframe::egui::ComboBox::new("attention", "") + egui::ComboBox::new("attention", "") .selected_text(repr(self.attention)) .show_ui(ui, |ui| { for kind in [ From 3dffaf813d16fc23e9c8af9394d4340df3894354 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 16:07:37 +0100 Subject: [PATCH 18/36] Remove most commands from `eframe::Frame` --- crates/eframe/src/epi/mod.rs | 205 -------------------- crates/eframe/src/native/epi_integration.rs | 190 ++---------------- crates/eframe/src/native/run.rs | 16 +- crates/egui/src/viewport.rs | 37 +++- 4 files changed, 56 insertions(+), 392 deletions(-) diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index aff7c7d3802..bb2c3fd7658 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -892,26 +892,6 @@ impl Frame { self.wgpu_render_state.as_ref() } - /// Tell `eframe` to close the desktop window. - /// - /// The window will not close immediately, but at the end of the this frame. - /// - /// Calling this will likely result in the app quitting, unless - /// you have more code after the call to [`crate::run_native`]. - #[cfg(not(target_arch = "wasm32"))] - #[doc(alias = "exit")] - #[doc(alias = "quit")] - pub fn close(&mut self) { - log::debug!("eframe::Frame::close called"); - self.output.close = true; - } - - /// Minimize or unminimize window. (native only) - #[cfg(not(target_arch = "wasm32"))] - pub fn set_minimized(&mut self, minimized: bool) { - self.output.minimized = Some(minimized); - } - /// Bring the window into focus (native only). Has no effect on Wayland, or if the window is minimized or invisible. /// /// This method puts the window on top of other applications and takes input focus away from them, @@ -921,106 +901,6 @@ impl Frame { self.output.focus = Some(true); } - /// If the window is unfocused, attract the user's attention (native only). - /// - /// Typically, this means that the window will flash on the taskbar, or bounce, until it is interacted with. - /// - /// When the window comes into focus, or if `None` is passed, the attention request will be automatically reset. - /// - /// See [winit's documentation][user_attention_details] for platform-specific effect details. - /// - /// [user_attention_details]: https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html - #[cfg(not(target_arch = "wasm32"))] - pub fn request_user_attention(&mut self, kind: egui::UserAttentionType) { - self.output.attention = Some(kind); - } - - /// Maximize or unmaximize window. (native only) - #[cfg(not(target_arch = "wasm32"))] - pub fn set_maximized(&mut self, maximized: bool) { - self.output.maximized = Some(maximized); - } - - /// Tell `eframe` to close the desktop window. - #[cfg(not(target_arch = "wasm32"))] - #[deprecated = "Renamed `close`"] - pub fn quit(&mut self) { - self.close(); - } - - /// Set the desired inner size of the window (in egui points). - #[cfg(not(target_arch = "wasm32"))] - pub fn set_window_size(&mut self, size: egui::Vec2) { - self.output.window_size = Some(size); - self.info.window_info.size = size; // so that subsequent calls see the updated value - } - - /// Set the desired title of the window. - #[cfg(not(target_arch = "wasm32"))] - pub fn set_window_title(&mut self, title: &str) { - self.output.window_title = Some(title.to_owned()); - } - - /// Set whether to show window decorations (i.e. a frame around you app). - /// - /// If false it will be difficult to move and resize the app. - #[cfg(not(target_arch = "wasm32"))] - pub fn set_decorations(&mut self, decorated: bool) { - self.output.decorated = Some(decorated); - } - - /// Turn borderless fullscreen on/off (native only). - #[cfg(not(target_arch = "wasm32"))] - pub fn set_fullscreen(&mut self, fullscreen: bool) { - self.output.fullscreen = Some(fullscreen); - self.info.window_info.fullscreen = fullscreen; // so that subsequent calls see the updated value - } - - /// set the position of the outer window. - #[cfg(not(target_arch = "wasm32"))] - pub fn set_window_pos(&mut self, pos: egui::Pos2) { - self.output.window_pos = Some(pos); - self.info.window_info.position = Some(pos); // so that subsequent calls see the updated value - } - - /// When called, the native window will follow the - /// movement of the cursor while the primary mouse button is down. - /// - /// Does not work on the web. - #[cfg(not(target_arch = "wasm32"))] - pub fn drag_window(&mut self) { - self.output.drag_window = true; - } - - /// Set the visibility of the window. - #[cfg(not(target_arch = "wasm32"))] - pub fn set_visible(&mut self, visible: bool) { - self.output.visible = Some(visible); - } - - /// On desktop: Set the window always on top. - /// - /// (Wayland desktop currently not supported) - #[cfg(not(target_arch = "wasm32"))] - pub fn set_always_on_top(&mut self, always_on_top: bool) { - self.output.always_on_top = Some(always_on_top); - } - - /// On desktop: Set the window to be centered. - /// - /// (Wayland desktop currently not supported) - #[cfg(not(target_arch = "wasm32"))] - pub fn set_centered(&mut self) { - if let Some(monitor_size) = self.info.window_info.monitor_size { - let inner_size = self.info.window_info.size; - if monitor_size.x > 1.0 && monitor_size.y > 1.0 { - let x = (monitor_size.x - inner_size.x) / 2.0; - let y = (monitor_size.y - inner_size.y) / 2.0; - self.set_window_pos(egui::Pos2 { x, y }); - } - } - } - /// for integrations only: call once per frame #[cfg(any(feature = "glow", feature = "wgpu"))] pub(crate) fn take_app_output(&mut self) -> backend::AppOutput { @@ -1038,39 +918,6 @@ pub struct WebInfo { /// Information about the URL. pub location: Location, } - -/// Information about the application's main window, if available. -#[cfg(not(target_arch = "wasm32"))] -#[derive(Clone, Debug)] -pub struct WindowInfo { - /// Coordinates of the window's outer top left corner, relative to the top left corner of the first display. - /// - /// Unit: egui points (logical pixels). - /// - /// `None` = unknown. - pub position: Option, - - /// Are we in fullscreen mode? - pub fullscreen: bool, - - /// Are we minimized? - pub minimized: bool, - - /// Are we maximized? - pub maximized: bool, - - /// Is the window focused and able to receive input? - /// - /// This should be the same as [`egui::InputState::focused`]. - pub focused: bool, - - /// Window inner size in egui points (logical pixels). - pub size: egui::Vec2, - - /// Current monitor size in egui points (logical pixels) - pub monitor_size: Option, -} - /// Information about the URL. /// /// Everything has been percent decoded (`%20` -> ` ` etc). @@ -1144,10 +991,6 @@ pub struct IntegrationInfo { /// The OS native pixels-per-point pub native_pixels_per_point: Option, - - /// The position and size of the native window. - #[cfg(not(target_arch = "wasm32"))] - pub window_info: WindowInfo, } // ---------------------------------------------------------------------------- @@ -1220,58 +1063,10 @@ pub(crate) mod backend { #[derive(Clone, Debug, Default)] #[must_use] pub struct AppOutput { - /// Set to `true` to close the native window (which often quits the app). - #[cfg(not(target_arch = "wasm32"))] - pub close: bool, - - /// Set to some size to resize the outer window (e.g. glium window) to this size. - #[cfg(not(target_arch = "wasm32"))] - pub window_size: Option, - - /// Set to some string to rename the outer window (e.g. glium window) to this title. - #[cfg(not(target_arch = "wasm32"))] - pub window_title: Option, - - /// Set to some bool to change window decorations. - #[cfg(not(target_arch = "wasm32"))] - pub decorated: Option, - - /// Set to some bool to change window fullscreen. - #[cfg(not(target_arch = "wasm32"))] // TODO: implement fullscreen on web - pub fullscreen: Option, - - /// Set to true to drag window while primary mouse button is down. - #[cfg(not(target_arch = "wasm32"))] - pub drag_window: bool, - - /// Set to some position to move the outer window (e.g. glium window) to this position - #[cfg(not(target_arch = "wasm32"))] - pub window_pos: Option, - - /// Set to some bool to change window visibility. - #[cfg(not(target_arch = "wasm32"))] - pub visible: Option, - - /// Set to some bool to tell the window always on top. - #[cfg(not(target_arch = "wasm32"))] - pub always_on_top: Option, - - /// Set to some bool to minimize or unminimize window. - #[cfg(not(target_arch = "wasm32"))] - pub minimized: Option, - - /// Set to some bool to maximize or unmaximize window. - #[cfg(not(target_arch = "wasm32"))] - pub maximized: Option, - /// Set to some bool to focus window. #[cfg(not(target_arch = "wasm32"))] pub focus: Option, - /// Set to request a user's attention to the native window. - #[cfg(not(target_arch = "wasm32"))] - pub attention: Option, - #[cfg(not(target_arch = "wasm32"))] pub screenshot_requested: bool, } diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 5919bb09303..6fd05433b22 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -8,60 +8,9 @@ use egui::{ DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId, ViewportIdPair, ViewportInfo, }; -use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings}; +use egui_winit::{EventResponse, WindowSettings}; -use crate::{epi, Theme, WindowInfo}; - -#[derive(Default)] -pub struct WindowState { - // We cannot simply call `winit::Window::is_minimized/is_maximized` - // because that deadlocks on mac. - pub minimized: bool, - pub maximized: bool, -} - -pub fn read_window_info( - window: &winit::window::Window, - pixels_per_point: f32, - window_state: &WindowState, -) -> WindowInfo { - let position = window - .outer_position() - .ok() - .map(|pos| pos.to_logical::(pixels_per_point.into())) - .map(|pos| egui::Pos2 { x: pos.x, y: pos.y }); - - let monitor = window.current_monitor().is_some(); - let monitor_size = if monitor { - let size = window - .current_monitor() - .unwrap() - .size() - .to_logical::(pixels_per_point.into()); - Some(egui::vec2(size.width, size.height)) - } else { - None - }; - - let size = window - .inner_size() - .to_logical::(pixels_per_point.into()); - - // NOTE: calling window.is_minimized() or window.is_maximized() deadlocks on Mac. - - WindowInfo { - position, - fullscreen: window.fullscreen().is_some(), - minimized: window_state.minimized, - maximized: window_state.maximized, - focused: window.has_focus(), - size: egui::Vec2 { - x: size.width, - y: size.height, - }, - monitor_size, - } -} +use crate::{epi, Theme}; pub fn window_builder( event_loop: &EventLoopWindowTarget, @@ -211,94 +160,16 @@ fn largest_monitor_point_size(event_loop: &EventLoopWindowTarget) -> egui: } } -pub fn handle_app_output( - window: &winit::window::Window, - current_pixels_per_point: f32, - app_output: epi::backend::AppOutput, - window_state: &mut WindowState, -) { +pub fn handle_app_output(window: &winit::window::Window, app_output: &epi::backend::AppOutput) { crate::profile_function!(); let epi::backend::AppOutput { - close: _, - window_size, - window_title, - decorated, - fullscreen, - drag_window, - window_pos, - visible: _, // handled in post_present - always_on_top, screenshot_requested: _, // handled by the rendering backend, - minimized, - maximized, focus, - attention, } = app_output; - if let Some(decorated) = decorated { - window.set_decorations(decorated); - } - - if let Some(window_size) = window_size { - window.set_inner_size( - winit::dpi::PhysicalSize { - width: (current_pixels_per_point * window_size.x).round(), - height: (current_pixels_per_point * window_size.y).round(), - } - .to_logical::(native_pixels_per_point(window) as f64), - ); - } - - if let Some(fullscreen) = fullscreen { - window.set_fullscreen(fullscreen.then_some(winit::window::Fullscreen::Borderless(None))); - } - - if let Some(window_title) = window_title { - window.set_title(&window_title); - } - - if let Some(window_pos) = window_pos { - window.set_outer_position(winit::dpi::LogicalPosition { - x: window_pos.x as f64, - y: window_pos.y as f64, - }); - } - - if drag_window { - window.drag_window().ok(); - } - - if let Some(always_on_top) = always_on_top { - use winit::window::WindowLevel; - window.set_window_level(if always_on_top { - WindowLevel::AlwaysOnTop - } else { - WindowLevel::Normal - }); - } - - if let Some(minimized) = minimized { - window.set_minimized(minimized); - window_state.minimized = minimized; - } - - if let Some(maximized) = maximized { - window.set_maximized(maximized); - window_state.maximized = maximized; - } - - if !window.has_focus() { - if focus == Some(true) { - window.focus_window(); - } else if let Some(attention) = attention { - use winit::window::UserAttentionType; - window.request_user_attention(match attention { - egui::UserAttentionType::Reset => None, - egui::UserAttentionType::Critical => Some(UserAttentionType::Critical), - egui::UserAttentionType::Informational => Some(UserAttentionType::Informational), - }); - } + if *focus == Some(true) && !window.has_focus() { + window.focus_window(); } } @@ -330,7 +201,6 @@ pub struct EpiIntegration { close: bool, can_drag_window: bool, - window_state: WindowState, follow_system_theme: bool, #[cfg(feature = "persistence")] persist_window: bool, @@ -357,22 +227,13 @@ impl EpiIntegration { let native_pixels_per_point = window.scale_factor() as f32; - let window_state = WindowState { - minimized: window.is_minimized().unwrap_or(false), - maximized: window.is_maximized(), - }; - let frame = epi::Frame { info: epi::IntegrationInfo { system_theme, cpu_usage: None, native_pixels_per_point: Some(native_pixels_per_point), - window_info: read_window_info(window, egui_ctx.pixels_per_point(), &window_state), - }, - output: epi::backend::AppOutput { - visible: Some(true), - ..Default::default() }, + output: epi::backend::AppOutput::default(), storage, #[cfg(feature = "glow")] gl, @@ -395,7 +256,6 @@ impl EpiIntegration { pending_full_output: Default::default(), close: false, can_drag_window: false, - window_state, follow_system_theme: native_options.follow_system_theme, #[cfg(feature = "persistence")] persist_window: native_options.persist_window, @@ -440,9 +300,9 @@ impl EpiIntegration { let mut raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT); raw_input.viewports = std::iter::once((ViewportId::ROOT, ViewportInfo::default())).collect(); - self.pre_update(window); + self.pre_update(); let full_output = self.update(app, None, raw_input); - self.post_update(app, window); + self.post_update(window); self.pending_full_output.append(full_output); // Handle it next frame self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge. self.egui_ctx.clear_animations(); @@ -493,13 +353,9 @@ impl EpiIntegration { egui_winit.on_event(&self.egui_ctx, event, viewport_id) } - pub fn pre_update(&mut self, window: &winit::window::Window) { + pub fn pre_update(&mut self) { self.frame_start = Instant::now(); - self.app_icon_setter.update(); - - self.frame.info.window_info = - read_window_info(window, self.egui_ctx.pixels_per_point(), &self.window_state); } /// Run user code - this can create immediate viewports, so hold no locks over this! @@ -534,29 +390,14 @@ impl EpiIntegration { std::mem::take(&mut self.pending_full_output) } - pub fn post_update(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { + pub fn post_update(&mut self, window: &winit::window::Window) { let app_output = { - let mut app_output = self.frame.take_app_output(); - app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108 - self.can_drag_window = false; - if app_output.close { - self.close = app.on_close_event(); - log::debug!("App::on_close_event returned {}", self.close); - } - self.frame.output.visible = app_output.visible; // this is handled by post_present + let app_output = self.frame.take_app_output(); self.frame.output.screenshot_requested = app_output.screenshot_requested; - if self.frame.output.attention.is_some() { - self.frame.output.attention = None; - } app_output }; - handle_app_output( - window, - self.egui_ctx.pixels_per_point(), - app_output, - &mut self.window_state, - ); + handle_app_output(window, &app_output); let frame_time = self.frame_start.elapsed().as_secs_f64() as f32; self.frame.info.cpu_usage = Some(frame_time); @@ -569,13 +410,6 @@ impl EpiIntegration { app.post_rendering(window_size_px, &self.frame); } - pub fn post_present(&mut self, window: &winit::window::Window) { - if let Some(visible) = self.frame.output.visible.take() { - crate::profile_scope!("window.set_visible"); - window.set_visible(visible); - } - } - pub fn handle_platform_output( &mut self, window: &winit::window::Window, diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index ab2e6e82ad0..cf4534b1647 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -536,7 +536,7 @@ mod glow_integration { let mut raw_input = egui_winit.take_egui_input(window, viewport.ids); let viewport_ui_cb = viewport.viewport_ui_cb.clone(); - self.integration.pre_update(window); + self.integration.pre_update(); raw_input.time = Some(self.integration.beginning.elapsed().as_secs_f64()); raw_input.viewports = glutin @@ -588,7 +588,7 @@ mod glow_integration { let gl_surface = viewport.gl_surface.as_ref().unwrap(); let egui_winit = viewport.egui_winit.as_mut().unwrap(); - integration.post_update(app.as_mut(), window); + integration.post_update(window); integration.handle_platform_output(window, viewport_id, platform_output, egui_winit); let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point); @@ -638,8 +638,6 @@ mod glow_integration { } } - integration.post_present(window); - // give it time to settle: #[cfg(feature = "__screenshot")] if integration.egui_ctx.frame_nr() == 2 { @@ -727,6 +725,7 @@ mod glow_integration { return EventResult::Exit; } } + _ => {} } @@ -2539,7 +2538,7 @@ mod wgpu_integration { ViewportIdPair::from_self_and_parent(viewport_id, ids.parent), ); - integration.pre_update(window); + integration.pre_update(); raw_input.time = Some(integration.beginning.elapsed().as_secs_f64()); raw_input.viewports = viewports @@ -2580,7 +2579,7 @@ mod wgpu_integration { return EventResult::Wait; }; - integration.post_update(app.as_mut(), window); + integration.post_update(window); let FullOutput { platform_output, @@ -2609,7 +2608,6 @@ mod wgpu_integration { } integration.post_rendering(app.as_mut(), window); - integration.post_present(window); let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); @@ -2677,6 +2675,7 @@ mod wgpu_integration { winit::event::WindowEvent::Focused(new_focused) => { *focused_viewport = new_focused.then(|| viewport_id).flatten(); } + winit::event::WindowEvent::Resized(physical_size) => { // Resize with 0 width and height is used by winit to signal a minimize event on Windows. // See: https://github.com/rust-windowing/winit/issues/208 @@ -2692,6 +2691,7 @@ mod wgpu_integration { } } } + winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { use std::num::NonZeroU32; if let (Some(width), Some(height), Some(viewport_id)) = ( @@ -2703,10 +2703,12 @@ mod wgpu_integration { shared.painter.on_window_resized(viewport_id, width, height); } } + winit::event::WindowEvent::CloseRequested if integration.should_close() => { log::debug!("Received WindowEvent::CloseRequested"); return EventResult::Exit; } + _ => {} }; diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 378ea94aef6..a942ed58463 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -662,7 +662,7 @@ pub enum ViewportCommand { /// For other viewports, the [`ViewportInfo::close_requested`] flag will be set. Close, - /// Set the title + /// Set the window title. Title(String), /// Turn the window transparent or not. @@ -708,21 +708,36 @@ pub enum ViewportCommand { maximize: bool, }, Minimized(bool), + + /// Maximize or unmaximize window. Maximized(bool), + + /// Turn borderless fullscreen on/off. Fullscreen(bool), /// Show window decorations, i.e. the chrome around the content /// with the title bar, close buttons, resize handles, etc. Decorations(bool), + /// Set window to be always-on-top, always-on-bottom, or neither. WindowLevel(WindowLevel), + + /// The the window icon. WindowIcon(Option>), IMEPosition(Pos2), IMEAllowed(bool), IMEPurpose(IMEPurpose), - /// Bring attention to the window. + /// If the window is unfocused, attract the user's attention (native only). + /// + /// Typically, this means that the window will flash on the taskbar, or bounce, until it is interacted with. + /// + /// When the window comes into focus, or if `None` is passed, the attention request will be automatically reset. + /// + /// See [winit's documentation][user_attention_details] for platform-specific effect details. + /// + /// [user_attention_details]: https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html RequestUserAttention(crate::UserAttentionType), SetTheme(SystemTheme), @@ -739,6 +754,24 @@ pub enum ViewportCommand { CursorHitTest(bool), } +impl ViewportCommand { + /// Construct a command to center the viewport on the monitor, if possible. + pub fn center_on_screen(ctx: &crate::Context) -> Option { + ctx.input(|i| { + let outer_rect = i.viewport().outer_rect?; + let size = outer_rect.size(); + let monitor_size = i.viewport().monitor_size?; + if 1.0 < monitor_size.x && 1.0 < monitor_size.y { + let x = (monitor_size.x - size.x) / 2.0; + let y = (monitor_size.y - size.y) / 2.0; + Some(Self::OuterPosition([x, y].into())) + } else { + None + } + }) + } +} + /// Describes a viewport, i.e. a native window. #[derive(Clone)] pub struct ViewportOutput { From 5d6a30f7b54728c5752f204006436de3e9aee7ac Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 16:11:52 +0100 Subject: [PATCH 19/36] Make Focus a viewport command --- crates/eframe/src/epi/mod.rs | 13 --------- crates/eframe/src/native/epi_integration.rs | 30 +++++---------------- crates/eframe/src/native/run.rs | 4 +-- crates/egui-winit/src/lib.rs | 5 ++++ crates/egui/src/viewport.rs | 8 ++++++ 5 files changed, 22 insertions(+), 38 deletions(-) diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index bb2c3fd7658..4503ddbf417 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -892,15 +892,6 @@ impl Frame { self.wgpu_render_state.as_ref() } - /// Bring the window into focus (native only). Has no effect on Wayland, or if the window is minimized or invisible. - /// - /// This method puts the window on top of other applications and takes input focus away from them, - /// which, if unexpected, will disturb the user. - #[cfg(not(target_arch = "wasm32"))] - pub fn focus(&mut self) { - self.output.focus = Some(true); - } - /// for integrations only: call once per frame #[cfg(any(feature = "glow", feature = "wgpu"))] pub(crate) fn take_app_output(&mut self) -> backend::AppOutput { @@ -1063,10 +1054,6 @@ pub(crate) mod backend { #[derive(Clone, Debug, Default)] #[must_use] pub struct AppOutput { - /// Set to some bool to focus window. - #[cfg(not(target_arch = "wasm32"))] - pub focus: Option, - #[cfg(not(target_arch = "wasm32"))] pub screenshot_requested: bool, } diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 6fd05433b22..4ba8b9b5b70 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -10,7 +10,7 @@ use egui::{ }; use egui_winit::{EventResponse, WindowSettings}; -use crate::{epi, Theme}; +use crate::{backend::AppOutput, epi, Theme}; pub fn window_builder( event_loop: &EventLoopWindowTarget, @@ -160,19 +160,6 @@ fn largest_monitor_point_size(event_loop: &EventLoopWindowTarget) -> egui: } } -pub fn handle_app_output(window: &winit::window::Window, app_output: &epi::backend::AppOutput) { - crate::profile_function!(); - - let epi::backend::AppOutput { - screenshot_requested: _, // handled by the rendering backend, - focus, - } = app_output; - - if *focus == Some(true) && !window.has_focus() { - window.focus_window(); - } -} - // ---------------------------------------------------------------------------- /// For loading/saving app state and/or egui memory to disk. @@ -302,7 +289,7 @@ impl EpiIntegration { std::iter::once((ViewportId::ROOT, ViewportInfo::default())).collect(); self.pre_update(); let full_output = self.update(app, None, raw_input); - self.post_update(window); + self.post_update(); self.pending_full_output.append(full_output); // Handle it next frame self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge. self.egui_ctx.clear_animations(); @@ -390,14 +377,11 @@ impl EpiIntegration { std::mem::take(&mut self.pending_full_output) } - pub fn post_update(&mut self, window: &winit::window::Window) { - let app_output = { - let app_output = self.frame.take_app_output(); - self.frame.output.screenshot_requested = app_output.screenshot_requested; - app_output - }; - - handle_app_output(window, &app_output); + pub fn post_update(&mut self) { + let AppOutput { + screenshot_requested, + } = self.frame.take_app_output(); + self.frame.output.screenshot_requested = screenshot_requested; let frame_time = self.frame_start.elapsed().as_secs_f64() as f32; self.frame.info.cpu_usage = Some(frame_time); diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index cf4534b1647..9c6dde9684f 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -588,7 +588,7 @@ mod glow_integration { let gl_surface = viewport.gl_surface.as_ref().unwrap(); let egui_winit = viewport.egui_winit.as_mut().unwrap(); - integration.post_update(window); + integration.post_update(); integration.handle_platform_output(window, viewport_id, platform_output, egui_winit); let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point); @@ -2579,7 +2579,7 @@ mod wgpu_integration { return EventResult::Wait; }; - integration.post_update(window); + integration.post_update(); let FullOutput { platform_output, diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 5f79186b2ba..e8320162d41 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1155,6 +1155,11 @@ pub fn process_viewport_commands( egui::viewport::IMEPurpose::Terminal => winit::window::ImePurpose::Terminal, egui::viewport::IMEPurpose::Normal => winit::window::ImePurpose::Normal, }), + ViewportCommand::Focus => { + if !window.has_focus() { + window.focus_window(); + } + } ViewportCommand::RequestUserAttention(a) => { window.request_user_attention(match a { egui::UserAttentionType::Reset => None, diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index a942ed58463..8942f1e26ce 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -729,6 +729,14 @@ pub enum ViewportCommand { IMEAllowed(bool), IMEPurpose(IMEPurpose), + /// Bring the window into focus (native only). + /// + /// This command puts the window on top of other applications and takes input focus away from them, + /// which, if unexpected, will disturb the user. + /// + /// Has no effect on Wayland, or if the window is minimized or invisible. + Focus, + /// If the window is unfocused, attract the user's attention (native only). /// /// Typically, this means that the window will flash on the taskbar, or bounce, until it is interacted with. From 77a5f8ba0cc54a62bc19f92ebeea2aa56e3825be Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 16:51:38 +0100 Subject: [PATCH 20/36] Fix: make sure window is visible --- crates/eframe/src/native/epi_integration.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 4ba8b9b5b70..22ac6d0a2fd 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -180,6 +180,7 @@ pub struct EpiIntegration { pub frame: epi::Frame, last_auto_save: Instant, pub beginning: Instant, + is_first_frame: bool, pub frame_start: Instant, pub egui_ctx: egui::Context, pending_full_output: egui::FullOutput, @@ -248,6 +249,7 @@ impl EpiIntegration { persist_window: native_options.persist_window, app_icon_setter, beginning: Instant::now(), + is_first_frame: true, frame_start: Instant::now(), } } @@ -392,6 +394,11 @@ impl EpiIntegration { let inner_size = window.inner_size(); let window_size_px = [inner_size.width, inner_size.height]; app.post_rendering(window_size_px, &self.frame); + + if std::mem::take(&mut self.is_first_frame) { + // We keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279 + window.set_visible(true); + } } pub fn handle_platform_output( From 1bda14e5ed5a4c9f1bdae3a0e2f33af43da67b28 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 16:52:41 +0100 Subject: [PATCH 21/36] Handle screenshots via `ViewportCommand` --- crates/eframe/src/epi/mod.rs | 37 +-------------------- crates/eframe/src/native/epi_integration.rs | 8 +---- crates/eframe/src/native/run.rs | 20 +++++++---- crates/egui-winit/src/lib.rs | 4 +++ crates/egui/src/viewport.rs | 5 +++ examples/save_plot/src/main.rs | 2 +- examples/screenshot/src/main.rs | 8 ++--- 7 files changed, 30 insertions(+), 54 deletions(-) diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index 4503ddbf417..118afa429d1 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -732,9 +732,6 @@ pub struct Frame { /// Information about the integration. pub(crate) info: IntegrationInfo, - /// Where the app can issue commands back to the integration. - pub(crate) output: backend::AppOutput, - /// A place where you can store custom data in a way that persists when you restart the app. pub(crate) storage: Option>, @@ -799,19 +796,6 @@ impl Frame { self.storage.as_deref() } - /// Request the current frame's pixel data. Needs to be retrieved by calling [`Frame::screenshot`] - /// during [`App::post_rendering`]. - #[cfg(not(target_arch = "wasm32"))] - pub fn request_screenshot(&mut self) { - self.output.screenshot_requested = true; - } - - /// Cancel a request made with [`Frame::request_screenshot`]. - #[cfg(not(target_arch = "wasm32"))] - pub fn cancel_screenshot_request(&mut self) { - self.output.screenshot_requested = false; - } - /// During [`App::post_rendering`], use this to retrieve the pixel data that was requested during /// [`App::update`] via [`Frame::request_screenshot`]. /// @@ -831,7 +815,7 @@ impl Frame { /// impl eframe::App for MyApp { /// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { /// // In real code the app would render something here - /// frame.request_screenshot(); + /// ctx.send_viewport_command(egui::ViewportCommand::Screenshot); /// // Things that are added to the frame after the call to /// // request_screenshot() will still be included. /// } @@ -891,12 +875,6 @@ impl Frame { pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> { self.wgpu_render_state.as_ref() } - - /// for integrations only: call once per frame - #[cfg(any(feature = "glow", feature = "wgpu"))] - pub(crate) fn take_app_output(&mut self) -> backend::AppOutput { - std::mem::take(&mut self.output) - } } /// Information about the web environment (if applicable). @@ -1045,16 +1023,3 @@ pub fn set_value(storage: &mut dyn Storage, key: &str, valu /// [`Storage`] key used for app pub const APP_KEY: &str = "app"; - -// ---------------------------------------------------------------------------- - -/// You only need to look here if you are writing a backend for `epi`. -pub(crate) mod backend { - /// Action that can be taken by the user app. - #[derive(Clone, Debug, Default)] - #[must_use] - pub struct AppOutput { - #[cfg(not(target_arch = "wasm32"))] - pub screenshot_requested: bool, - } -} diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 22ac6d0a2fd..9b771f1428b 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -10,7 +10,7 @@ use egui::{ }; use egui_winit::{EventResponse, WindowSettings}; -use crate::{backend::AppOutput, epi, Theme}; +use crate::{epi, Theme}; pub fn window_builder( event_loop: &EventLoopWindowTarget, @@ -221,7 +221,6 @@ impl EpiIntegration { cpu_usage: None, native_pixels_per_point: Some(native_pixels_per_point), }, - output: epi::backend::AppOutput::default(), storage, #[cfg(feature = "glow")] gl, @@ -380,11 +379,6 @@ impl EpiIntegration { } pub fn post_update(&mut self) { - let AppOutput { - screenshot_requested, - } = self.frame.take_app_output(); - self.frame.output.screenshot_requested = screenshot_requested; - let frame_time = self.frame_start.elapsed().as_secs_f64() as f32; self.frame.info.cpu_usage = Some(frame_time); } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 9c6dde9684f..4550ee2f5a4 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -618,9 +618,8 @@ mod glow_integration { ); { - let screenshot_requested = &mut integration.frame.output.screenshot_requested; - if *screenshot_requested { - *screenshot_requested = false; + let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); + if screenshot_requested { let screenshot = painter.read_screen_rgba(screen_size_in_pixels); integration.frame.screenshot.set(Some(screenshot)); } @@ -794,6 +793,7 @@ mod glow_integration { class: ViewportClass, builder: ViewportBuilder, info: ViewportInfo, + screenshot_requested: bool, /// The user-callback that shows the ui. /// None for immediate viewports. @@ -975,6 +975,7 @@ mod glow_integration { class: ViewportClass::Root, builder: viewport_builder, info, + screenshot_requested: false, viewport_ui_cb: None, gl_surface: None, window: window.map(Rc::new), @@ -1223,6 +1224,7 @@ mod glow_integration { commands, window, is_viewport_focused, + &mut viewport.screenshot_requested, ); } } @@ -1263,6 +1265,7 @@ mod glow_integration { class, builder, info: Default::default(), + screenshot_requested: false, viewport_ui_cb, window: None, egui_winit: None, @@ -1295,6 +1298,7 @@ mod glow_integration { delta_commands, window, is_viewport_focused, + &mut viewport.screenshot_requested, ); } @@ -1865,6 +1869,7 @@ mod wgpu_integration { class: ViewportClass, builder: ViewportBuilder, info: ViewportInfo, + screenshot_requested: bool, /// `None` for sync viewports. viewport_ui_cb: Option>, @@ -2125,6 +2130,7 @@ mod wgpu_integration { maximized: Some(window.is_maximized()), ..Default::default() }, + screenshot_requested: false, viewport_ui_cb: None, window: Some(Rc::new(window)), egui_winit: Some(egui_winit), @@ -2594,16 +2600,15 @@ mod wgpu_integration { { let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point); - let screenshot_requested = &mut integration.frame.output.screenshot_requested; + let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); let screenshot = painter.paint_and_update_textures( viewport_id, pixels_per_point, app.clear_color(&integration.egui_ctx.style().visuals), &clipped_primitives, &textures_delta, - *screenshot_requested, + screenshot_requested, ); - *screenshot_requested = false; integration.frame.screenshot.set(screenshot); } @@ -2774,6 +2779,7 @@ mod wgpu_integration { commands, window, is_viewport_focused, + &mut viewport.screenshot_requested, ); } } @@ -2803,6 +2809,7 @@ mod wgpu_integration { class, builder, info: Default::default(), + screenshot_requested: false, viewport_ui_cb, window: None, egui_winit: None, @@ -2834,6 +2841,7 @@ mod wgpu_integration { delta_commands, window, is_viewport_focused, + &mut viewport.screenshot_requested, ); } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index e8320162d41..572c7365698 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -1038,6 +1038,7 @@ pub fn process_viewport_commands( commands: impl IntoIterator, window: &Window, is_viewport_focused: bool, + screenshot_requested: &mut bool, ) { crate::profile_function!(); @@ -1197,6 +1198,9 @@ pub fn process_viewport_commands( log::warn!("{command:?}: {err}"); } } + ViewportCommand::Screenshot => { + *screenshot_requested = true; + } } } } diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 8942f1e26ce..d32bd87eb0a 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -760,6 +760,11 @@ pub enum ViewportCommand { CursorVisible(bool), CursorHitTest(bool), + + /// Take a screenshot. + /// + /// In `eframe`, the results are returned in `eframe::Frame::screenshot`. + Screenshot, } impl ViewportCommand { diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index a13e071aa29..cab5d76b60a 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -28,7 +28,7 @@ impl eframe::App for MyApp { let mut plot_rect = None; egui::CentralPanel::default().show(ctx, |ui| { if ui.button("Save Plot").clicked() { - frame.request_screenshot(); + ctx.send_viewport_command(egui::ViewportCommand::Screenshot); } let my_plot = Plot::new("My Plot").legend(Legend::default()); diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 498d5013e0d..d75253d8da2 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -24,7 +24,7 @@ struct MyApp { } impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { egui::CentralPanel::default().show(ctx, |ui| { if let Some(screenshot) = self.screenshot.take() { self.texture = Some(ui.ctx().load_texture( @@ -42,7 +42,7 @@ impl eframe::App for MyApp { if ui.button("save to 'top_left.png'").clicked() { self.save_to_file = true; - frame.request_screenshot(); + ctx.send_viewport_command(egui::ViewportCommand::Screenshot); } ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| { @@ -55,9 +55,9 @@ impl eframe::App for MyApp { } else { ctx.set_visuals(egui::Visuals::light()); }; - frame.request_screenshot(); + ctx.send_viewport_command(egui::ViewportCommand::Screenshot); } else if ui.button("take screenshot!").clicked() { - frame.request_screenshot(); + ctx.send_viewport_command(egui::ViewportCommand::Screenshot); } }); }); From 01c273a3f531f673227c674a5b4758af18719ed5 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:10:15 +0100 Subject: [PATCH 22/36] Return screenshots as an egui Event --- crates/eframe/src/epi/mod.rs | 53 --------------------- crates/eframe/src/native/epi_integration.rs | 1 - crates/eframe/src/native/run.rs | 18 ++++++- crates/egui-winit/src/lib.rs | 7 +++ crates/egui/src/data/input.rs | 8 ++++ crates/egui/src/viewport.rs | 2 +- examples/save_plot/src/main.rs | 28 ++++++----- examples/screenshot/src/main.rs | 51 +++++++++++--------- 8 files changed, 77 insertions(+), 91 deletions(-) diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index 118afa429d1..556e31301be 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -743,11 +743,6 @@ pub struct Frame { #[cfg(feature = "wgpu")] pub(crate) wgpu_render_state: Option, - /// If [`Frame::request_screenshot`] was called during a frame, this field will store the screenshot - /// such that it can be retrieved during [`App::post_rendering`] with [`Frame::screenshot`] - #[cfg(not(target_arch = "wasm32"))] - pub(crate) screenshot: std::cell::Cell>, - /// Raw platform window handle #[cfg(not(target_arch = "wasm32"))] pub(crate) raw_window_handle: RawWindowHandle, @@ -796,54 +791,6 @@ impl Frame { self.storage.as_deref() } - /// During [`App::post_rendering`], use this to retrieve the pixel data that was requested during - /// [`App::update`] via [`Frame::request_screenshot`]. - /// - /// Returns None if: - /// * Called in [`App::update`] - /// * [`Frame::request_screenshot`] wasn't called on this frame during [`App::update`] - /// * The rendering backend doesn't support this feature (yet). Currently implemented for wgpu and glow, but not with wasm as target. - /// * Wgpu's GL target is active (not yet supported) - /// * Retrieving the data was unsuccessful in some way. - /// - /// See also [`egui::ColorImage::region`] - /// - /// ## Example generating a capture of everything within a square of 100 pixels located at the top left of the app and saving it with the [`image`](crates.io/crates/image) crate: - /// ``` - /// struct MyApp; - /// - /// impl eframe::App for MyApp { - /// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - /// // In real code the app would render something here - /// ctx.send_viewport_command(egui::ViewportCommand::Screenshot); - /// // Things that are added to the frame after the call to - /// // request_screenshot() will still be included. - /// } - /// - /// fn post_rendering(&mut self, _window_size: [u32; 2], frame: &eframe::Frame) { - /// if let Some(screenshot) = frame.screenshot() { - /// let pixels_per_point = frame.info().native_pixels_per_point; - /// let region = egui::Rect::from_two_pos( - /// egui::Pos2::ZERO, - /// egui::Pos2{ x: 100., y: 100. }, - /// ); - /// let top_left_corner = screenshot.region(®ion, pixels_per_point); - /// image::save_buffer( - /// "top_left.png", - /// top_left_corner.as_raw(), - /// top_left_corner.width() as u32, - /// top_left_corner.height() as u32, - /// image::ColorType::Rgba8, - /// ).unwrap(); - /// } - /// } - /// } - /// ``` - #[cfg(not(target_arch = "wasm32"))] - pub fn screenshot(&self) -> Option { - self.screenshot.take() - } - /// A place where you can store custom data in a way that persists when you restart the app. pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> { self.storage.as_deref_mut() diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 9b771f1428b..c9c5f36b61b 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -226,7 +226,6 @@ impl EpiIntegration { gl, #[cfg(feature = "wgpu")] wgpu_render_state, - screenshot: std::cell::Cell::new(None), raw_display_handle: window.raw_display_handle(), raw_window_handle: window.raw_window_handle(), }; diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 4550ee2f5a4..06b051ae42a 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -621,7 +621,13 @@ mod glow_integration { let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested); if screenshot_requested { let screenshot = painter.read_screen_rgba(screen_size_in_pixels); - integration.frame.screenshot.set(Some(screenshot)); + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Screenshot { + viewport_id, + image: screenshot.into(), + }); } integration.post_rendering(app.as_mut(), window); } @@ -2609,7 +2615,15 @@ mod wgpu_integration { &textures_delta, screenshot_requested, ); - integration.frame.screenshot.set(screenshot); + if let Some(screenshot) = screenshot { + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Screenshot { + viewport_id, + image: screenshot.into(), + }); + } } integration.post_rendering(app.as_mut(), window); diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 572c7365698..36cdcee41fd 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -180,6 +180,13 @@ impl State { &self.egui_input } + /// The current input state. + /// This is changed by [`Self::on_event`] and cleared by [`Self::take_egui_input`]. + #[inline] + pub fn egui_input_mut(&mut self) -> &mut egui::RawInput { + &mut self.egui_input + } + /// Update the given viewport info with the current state of the window. /// /// Call before [`Self::update_viewport_info`] diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 47eac5d50ec..f2ba51088b1 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -1,5 +1,7 @@ //! The input needed by egui. +use epaint::ColorImage; + use crate::{emath::*, ViewportIdMap, ViewportIdPair}; /// What the integrations provides to egui at the start of each frame. @@ -445,6 +447,12 @@ pub enum Event { /// An assistive technology (e.g. screen reader) requested an action. #[cfg(feature = "accesskit")] AccessKitActionRequest(accesskit::ActionRequest), + + /// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`]. + Screenshot { + viewport_id: crate::ViewportId, + image: std::sync::Arc, + }, } /// Mouse button (or similar for touch input) diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index d32bd87eb0a..7d5be982f0b 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -763,7 +763,7 @@ pub enum ViewportCommand { /// Take a screenshot. /// - /// In `eframe`, the results are returned in `eframe::Frame::screenshot`. + /// The results are returned in `crate::Event::Screenshot`. Screenshot, } diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index cab5d76b60a..a3fe35d1b17 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -1,7 +1,9 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +use std::sync::Arc; + use eframe::egui; -use eframe::egui::ColorImage; +use egui::ColorImage; use egui_plot::{Legend, Line, Plot, PlotPoints}; fn main() -> Result<(), eframe::Error> { @@ -19,9 +21,7 @@ fn main() -> Result<(), eframe::Error> { } #[derive(Default)] -struct MyApp { - screenshot: Option, -} +struct MyApp {} impl eframe::App for MyApp { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { @@ -42,7 +42,17 @@ impl eframe::App for MyApp { plot_rect = Some(inner.response.rect); }); - if let (Some(screenshot), Some(plot_location)) = (self.screenshot.take(), plot_rect) { + // Check for returned screenshot: + let screenshot = ctx.input(|i| { + for event in &i.raw.events { + if let egui::Event::Screenshot { image, .. } = event { + return Some(image.clone()); + } + } + None + }); + + if let (Some(screenshot), Some(plot_location)) = (screenshot, plot_rect) { if let Some(mut path) = rfd::FileDialog::new().save_file() { path.set_extension("png"); @@ -60,14 +70,8 @@ impl eframe::App for MyApp { image::ColorType::Rgba8, ) .unwrap(); + eprintln!("Image saved to {path:?}."); } } } - - fn post_rendering(&mut self, _screen_size_px: [u32; 2], frame: &eframe::Frame) { - // this is inspired by the Egui screenshot example - if let Some(screenshot) = frame.screenshot() { - self.screenshot = Some(screenshot); - } - } } diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index d75253d8da2..9d35d73591a 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release +use std::sync::Arc; + use eframe::egui::{self, ColorImage}; fn main() -> Result<(), eframe::Error> { @@ -19,7 +21,7 @@ fn main() -> Result<(), eframe::Error> { struct MyApp { continuously_take_screenshots: bool, texture: Option, - screenshot: Option, + screenshot: Option>, save_to_file: bool, } @@ -68,28 +70,33 @@ impl eframe::App for MyApp { ui.spinner(); } + // Check for returned screenshot: + ui.input(|i| { + for event in &i.raw.events { + if let egui::Event::Screenshot { image, .. } = event { + if self.save_to_file { + let pixels_per_point = i.pixels_per_point(); + let region = egui::Rect::from_two_pos( + egui::Pos2::ZERO, + egui::Pos2 { x: 100., y: 100. }, + ); + let top_left_corner = image.region(®ion, Some(pixels_per_point)); + image::save_buffer( + "top_left.png", + top_left_corner.as_raw(), + top_left_corner.width() as u32, + top_left_corner.height() as u32, + image::ColorType::Rgba8, + ) + .unwrap(); + self.save_to_file = false; + } + self.screenshot = Some(image.clone()); + } + } + }); + ctx.request_repaint(); }); } - - fn post_rendering(&mut self, _window_size: [u32; 2], frame: &eframe::Frame) { - if let Some(screenshot) = frame.screenshot() { - if self.save_to_file { - let pixels_per_point = frame.info().native_pixels_per_point; - let region = - egui::Rect::from_two_pos(egui::Pos2::ZERO, egui::Pos2 { x: 100., y: 100. }); - let top_left_corner = screenshot.region(®ion, pixels_per_point); - image::save_buffer( - "top_left.png", - top_left_corner.as_raw(), - top_left_corner.width() as u32, - top_left_corner.height() as u32, - image::ColorType::Rgba8, - ) - .unwrap(); - self.save_to_file = false; - } - self.screenshot = Some(screenshot); - } - } } From 1e02144231ef042988c811321679ef099f0da458 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:14:50 +0100 Subject: [PATCH 23/36] Shorten `send_viewport_command` -> `send_viewport_cmd` --- crates/egui/src/context.rs | 6 +++--- crates/egui/src/viewport.rs | 4 ++-- crates/egui_demo_app/src/backend_panel.rs | 11 +++++------ crates/egui_demo_app/src/wrap_app.rs | 2 +- examples/confirm_exit/src/main.rs | 2 +- examples/custom_window_frame/src/main.rs | 14 ++++++-------- examples/save_plot/src/main.rs | 2 +- examples/screenshot/src/main.rs | 6 +++--- examples/serial_windows/src/main.rs | 2 +- examples/test_viewports/src/main.rs | 2 +- examples/user_attention/src/main.rs | 6 ++---- 11 files changed, 26 insertions(+), 31 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index ef5c18106bb..a6255dce46e 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2555,14 +2555,14 @@ impl Context { /// Send a command to the current viewport. /// /// This lets you affect the current viewport, e.g. resizing the window. - pub fn send_viewport_command(&self, command: ViewportCommand) { - self.send_viewport_command_to(self.viewport_id(), command); + pub fn send_viewport_cmd(&self, command: ViewportCommand) { + self.send_viewport_cmd_to(self.viewport_id(), command); } /// Send a command to a speicfic viewport. /// /// This lets you affect another viewport, e.g. resizing its window. - pub fn send_viewport_command_to(&self, id: ViewportId, command: ViewportCommand) { + pub fn send_viewport_cmd_to(&self, id: ViewportId, command: ViewportCommand) { self.write(|ctx| ctx.viewport_for(id).commands.push(command)); self.request_repaint_of(id); } diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 7d5be982f0b..44d10a8ae40 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -38,7 +38,7 @@ //! //! ## Using the viewports //! Only one viewport is active at any one time, identified with [`Context::viewport_id`]. -//! You can send commands to other viewports using [`Context::send_viewport_command_to`]. +//! You can send commands to other viewports using [`Context::send_viewport_cmd_to`]. //! //! There is an example in . //! @@ -648,7 +648,7 @@ pub enum ResizeDirection { SouthWest, } -/// You can send a [`ViewportCommand`] to the viewport with [`Context::send_viewport_command`]. +/// You can send a [`ViewportCommand`] to the viewport with [`Context::send_viewport_cmd`]. /// /// All coordinates are in logical points. /// diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index bd92b3c3a2a..04039383be6 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -117,7 +117,7 @@ impl BackendPanel { { ui.separator(); if ui.button("Quit").clicked() { - ui.ctx().send_viewport_command(egui::ViewportCommand::Close); + ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); } } @@ -165,7 +165,7 @@ impl BackendPanel { .changed() { ui.ctx() - .send_viewport_command(egui::ViewportCommand::Fullscreen(fullscreen)); + .send_viewport_cmd(egui::ViewportCommand::Fullscreen(fullscreen)); } } @@ -178,9 +178,9 @@ impl BackendPanel { let size = egui::vec2(375.0, 667.0); // iPhone SE 2nd gen ui.ctx() - .send_viewport_command(egui::ViewportCommand::InnerSize(size)); + .send_viewport_cmd(egui::ViewportCommand::InnerSize(size)); ui.ctx() - .send_viewport_command(egui::ViewportCommand::Fullscreen(false)); + .send_viewport_cmd(egui::ViewportCommand::Fullscreen(false)); ui.close_menu(); } }); @@ -191,8 +191,7 @@ impl BackendPanel { .button("Drag me to drag window") .is_pointer_button_down_on() { - ui.ctx() - .send_viewport_command(egui::ViewportCommand::StartDrag); + ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag); } } } diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 73d0339462f..6e8dd87d60b 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -260,7 +260,7 @@ impl eframe::App for WrapApp { #[cfg(not(target_arch = "wasm32"))] if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) { let fullscreen = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false)); - ctx.send_viewport_command(egui::ViewportCommand::Fullscreen(!fullscreen)); + ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!fullscreen)); } let mut cmd = Command::Nothing; diff --git a/examples/confirm_exit/src/main.rs b/examples/confirm_exit/src/main.rs index bc97e145bb9..33d799d3874 100644 --- a/examples/confirm_exit/src/main.rs +++ b/examples/confirm_exit/src/main.rs @@ -45,7 +45,7 @@ impl eframe::App for MyApp { if ui.button("Yes!").clicked() { self.allowed_to_close = true; - ui.ctx().send_viewport_command(egui::ViewportCommand::Close); + ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); } }); }); diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 9ce06f3c821..98b24497f26 100644 --- a/examples/custom_window_frame/src/main.rs +++ b/examples/custom_window_frame/src/main.rs @@ -104,9 +104,9 @@ fn title_bar_ui(ui: &mut egui::Ui, title_bar_rect: eframe::epaint::Rect, title: if title_bar_response.double_clicked() { let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false)); ui.ctx() - .send_viewport_command(ViewportCommand::Maximized(!is_maximized)); + .send_viewport_cmd(ViewportCommand::Maximized(!is_maximized)); } else if title_bar_response.is_pointer_button_down_on() { - ui.ctx().send_viewport_command(ViewportCommand::StartDrag); + ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag); } ui.allocate_ui_at_rect(title_bar_rect, |ui| { @@ -129,7 +129,7 @@ fn close_maximize_minimize(ui: &mut egui::Ui) { .add(Button::new(RichText::new("❌").size(button_height))) .on_hover_text("Close the window"); if close_response.clicked() { - ui.ctx().send_viewport_command(egui::ViewportCommand::Close); + ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); } let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false)); @@ -139,15 +139,14 @@ fn close_maximize_minimize(ui: &mut egui::Ui) { .on_hover_text("Restore window"); if maximized_response.clicked() { ui.ctx() - .send_viewport_command(ViewportCommand::Maximized(false)); + .send_viewport_cmd(ViewportCommand::Maximized(false)); } } else { let maximized_response = ui .add(Button::new(RichText::new("🗗").size(button_height))) .on_hover_text("Maximize window"); if maximized_response.clicked() { - ui.ctx() - .send_viewport_command(ViewportCommand::Maximized(true)); + ui.ctx().send_viewport_cmd(ViewportCommand::Maximized(true)); } } @@ -155,7 +154,6 @@ fn close_maximize_minimize(ui: &mut egui::Ui) { .add(Button::new(RichText::new("🗕").size(button_height))) .on_hover_text("Minimize the window"); if minimized_response.clicked() { - ui.ctx() - .send_viewport_command(ViewportCommand::Minimized(true)); + ui.ctx().send_viewport_cmd(ViewportCommand::Minimized(true)); } } diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index a3fe35d1b17..a0be087cc0f 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -28,7 +28,7 @@ impl eframe::App for MyApp { let mut plot_rect = None; egui::CentralPanel::default().show(ctx, |ui| { if ui.button("Save Plot").clicked() { - ctx.send_viewport_command(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); } let my_plot = Plot::new("My Plot").legend(Legend::default()); diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index 9d35d73591a..91f2f92cfc0 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -44,7 +44,7 @@ impl eframe::App for MyApp { if ui.button("save to 'top_left.png'").clicked() { self.save_to_file = true; - ctx.send_viewport_command(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); } ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| { @@ -57,9 +57,9 @@ impl eframe::App for MyApp { } else { ctx.set_visuals(egui::Visuals::light()); }; - ctx.send_viewport_command(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); } else if ui.button("take screenshot!").clicked() { - ctx.send_viewport_command(egui::ViewportCommand::Screenshot); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); } }); }); diff --git a/examples/serial_windows/src/main.rs b/examples/serial_windows/src/main.rs index 03f89dab98e..6edccc5e0a5 100644 --- a/examples/serial_windows/src/main.rs +++ b/examples/serial_windows/src/main.rs @@ -56,7 +56,7 @@ impl eframe::App for MyApp { ui.label(label_text); if ui.button("Close").clicked() { eprintln!("Pressed Close button"); - ui.ctx().send_viewport_command(egui::ViewportCommand::Close); + ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close); } }); } diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index 062f1f1cdb7..b933cee0dd8 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -263,7 +263,7 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc>]) { if ctx.viewport_id() != ctx.parent_viewport_id() { let parent = ctx.parent_viewport_id(); if ui.button("Set parent pos 0,0").clicked() { - ctx.send_viewport_command_to( + ctx.send_viewport_cmd_to( parent, egui::ViewportCommand::OuterPosition(egui::pos2(0.0, 0.0)), ); diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 995f30b42dd..7478eb072cd 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -56,9 +56,7 @@ impl eframe::App for Application { if let Some(request_at) = self.request_at { if request_at < SystemTime::now() { self.request_at = None; - ctx.send_viewport_command(egui::ViewportCommand::RequestUserAttention( - self.attention, - )); + ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention(self.attention)); if self.auto_reset { self.auto_reset = false; self.reset_at = Some(SystemTime::now() + Self::attention_reset_timeout()); @@ -69,7 +67,7 @@ impl eframe::App for Application { if let Some(reset_at) = self.reset_at { if reset_at < SystemTime::now() { self.reset_at = None; - ctx.send_viewport_command(egui::ViewportCommand::RequestUserAttention( + ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention( UserAttentionType::Reset, )); } From 7075d93faea45c95c3da501dfa340f63e60ebb0c Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:26:20 +0100 Subject: [PATCH 24/36] Fix web build --- crates/eframe/src/web/app_runner.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 6c93924f879..30c4572662d 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -78,7 +78,6 @@ impl AppRunner { let frame = epi::Frame { info, - output: Default::default(), storage: Some(Box::new(storage)), #[cfg(feature = "glow")] @@ -198,11 +197,6 @@ impl AppRunner { self.textures_delta.append(textures_delta); let clipped_primitives = self.egui_ctx.tessellate(shapes, pixels_per_point); - { - let app_output = self.frame.take_app_output(); - let epi::backend::AppOutput {} = app_output; - } - self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32); clipped_primitives From f91268f4e1246e76606f7be0680ab1a7f734de9a Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:28:50 +0100 Subject: [PATCH 25/36] Web backend: warn when a command is not handled --- crates/eframe/src/web/app_runner.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 30c4572662d..54ff4727fbf 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -191,7 +191,14 @@ impl AppRunner { if viewport_output.len() > 1 { log::warn!("Multiple viewports not yet supported on the web"); } - // TODO(emilk): handle some of the command in `viewport_output`, like setting the title and icon? + for viewport_output in viewport_output.values() { + for command in &viewport_output.commands { + // TODO(emilk): handle some of the commands + log::warn!( + "Unhandled egui viewport command: {command:?} - not implemented in web backend" + ); + } + } self.handle_platform_output(platform_output); self.textures_delta.append(textures_delta); From c0f6fae10790e1f0382f48c2a61787d17f36f704 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:29:16 +0100 Subject: [PATCH 26/36] Fix typos --- crates/egui/src/input_state.rs | 2 +- crates/egui_demo_lib/src/demo/extra_viewport.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/input_state.rs b/crates/egui/src/input_state.rs index 86c836eb72c..79aff9a9c11 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -231,7 +231,7 @@ impl InputState { } } - /// Infor about the acitve viewport + /// Info about the active viewport pub fn viewport(&self) -> &ViewportInfo { self.raw.viewports.get(&self.raw.viewport_ids.this).expect("Failed to find current viewport in egui RawInput. This is the fault of the egui backend") } diff --git a/crates/egui_demo_lib/src/demo/extra_viewport.rs b/crates/egui_demo_lib/src/demo/extra_viewport.rs index 0e83f40505c..d413def6900 100644 --- a/crates/egui_demo_lib/src/demo/extra_viewport.rs +++ b/crates/egui_demo_lib/src/demo/extra_viewport.rs @@ -34,7 +34,7 @@ impl super::Demo for ExtraViewport { } else { egui::CentralPanel::default().show(ctx, |ui| { ui.push_id(id, |ui| { - viewport_contet(ui, ctx, open); + viewport_content(ui, ctx, open); }) }); } @@ -43,7 +43,7 @@ impl super::Demo for ExtraViewport { } } -fn viewport_contet(ui: &mut egui::Ui, ctx: &egui::Context, open: &mut bool) { +fn viewport_content(ui: &mut egui::Ui, ctx: &egui::Context, open: &mut bool) { ui.label("egui and eframe supports having multiple native windows like this, which egui calls 'viewports'."); ui.label(format!( From 3b0482cffc3d14544d6b1f9d5d2a9c2c99a34991 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:31:41 +0100 Subject: [PATCH 27/36] Build fixes --- crates/egui_glow/src/winit.rs | 12 +++++++++++- examples/save_plot/src/main.rs | 3 --- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index 6f20404a042..23eaa899298 100644 --- a/crates/egui_glow/src/winit.rs +++ b/crates/egui_glow/src/winit.rs @@ -75,7 +75,17 @@ impl EguiGlow { log::warn!("Multiple viewports not yet supported by EguiGlow"); } for (_, ViewportOutput { commands, .. }) in viewport_output { - egui_winit::process_viewport_commands(&mut self.viewport_info, commands, window, true); + let mut screenshot_requested = false; + egui_winit::process_viewport_commands( + &mut self.viewport_info, + commands, + window, + true, + &mut screenshot_requested, + ); + if screenshot_requested { + log::warn!("Screenshot not yet supported by EguiGlow"); + } } self.egui_winit.handle_platform_output( diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index a0be087cc0f..24841349c4c 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -1,9 +1,6 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release -use std::sync::Arc; - use eframe::egui; -use egui::ColorImage; use egui_plot::{Legend, Line, Plot, PlotPoints}; fn main() -> Result<(), eframe::Error> { From d8c01980958fd51131182e70a2674221ea185dd7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:36:25 +0100 Subject: [PATCH 28/36] Remove `App::post_rendering` --- crates/eframe/src/epi/mod.rs | 5 ----- crates/eframe/src/native/epi_integration.rs | 6 +----- crates/eframe/src/native/run.rs | 4 ++-- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index 556e31301be..02ecfaa5f23 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -230,11 +230,6 @@ pub trait App { fn warm_up_enabled(&self) -> bool { false } - - /// Called each time after the rendering the UI. - /// - /// Can be used to access pixel data with [`Frame::screenshot`] - fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) {} } /// Selects the level of hardware graphics acceleration. diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index c9c5f36b61b..879fdaa854f 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -382,12 +382,8 @@ impl EpiIntegration { self.frame.info.cpu_usage = Some(frame_time); } - pub fn post_rendering(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) { + pub fn post_rendering(&mut self, window: &winit::window::Window) { crate::profile_function!(); - let inner_size = window.inner_size(); - let window_size_px = [inner_size.width, inner_size.height]; - app.post_rendering(window_size_px, &self.frame); - if std::mem::take(&mut self.is_first_frame) { // We keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279 window.set_visible(true); diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 06b051ae42a..191970ef4ad 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -629,7 +629,7 @@ mod glow_integration { image: screenshot.into(), }); } - integration.post_rendering(app.as_mut(), window); + integration.post_rendering(window); } { @@ -2626,7 +2626,7 @@ mod wgpu_integration { } } - integration.post_rendering(app.as_mut(), window); + integration.post_rendering(window); let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); From eeaaa4089f7fd1e31576ac4510bb8526c4edf4d7 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:37:19 +0100 Subject: [PATCH 29/36] Fix some doclinks --- crates/egui/src/context.rs | 4 ++-- crates/egui/src/data/input.rs | 2 +- crates/egui/src/viewport.rs | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index a6255dce46e..26b0661ffbb 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -2574,7 +2574,7 @@ impl Context { /// You need to call this each frame when the child viewport should exist. /// /// You can check if the user wants to close the viewport by checking the - /// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewports`]. + /// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewport`]. /// /// The given callback will be called whenever the child viewport needs repainting, /// e.g. on an event or when [`Self::request_repaint`] is called. @@ -2634,7 +2634,7 @@ impl Context { /// You need to call this each frame when the child viewport should exist. /// /// You can check if the user wants to close the viewport by checking the - /// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewports`]. + /// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewport`]. /// /// The given ui function will be called immediately. /// This may only be called on the main thread. diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index f2ba51088b1..62747d3f7b3 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -194,7 +194,7 @@ pub struct ViewportInfo { /// Is the window focused and able to receive input? /// - /// This should be the same as [`InputState::focused`]. + /// This should be the same as [`RawInput::focused`]. pub focused: Option, } diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 44d10a8ae40..74ad7e8e4e5 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -43,7 +43,8 @@ //! There is an example in . //! //! ## For integrations -//! * There is a [`crate::RawInput::viewport`] with information about the current viewport. +//! * There is a [`crate::InputState::viewport`] with information about the current viewport. +//! * There is a [`crate::RawInput::viewports`] with information about all viewports. //! * The repaint callback set by [`Context::set_request_repaint_callback`] points to which viewport should be repainted. //! * [`crate::FullOutput::viewport_output`] is a list of viewports which should result in their own independent windows. //! * To support immediate viewports you need to call [`Context::set_immediate_viewport_renderer`]. @@ -659,7 +660,7 @@ pub enum ViewportCommand { /// Request this viewport to be closed. /// /// For the root viewport, this usually results in the application shutting down. - /// For other viewports, the [`ViewportInfo::close_requested`] flag will be set. + /// For other viewports, the [`crate::ViewportInfo::close_requested`] flag will be set. Close, /// Set the window title. From 618caee49a653583ecffacb0fca76bbc009ba1b0 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:37:28 +0100 Subject: [PATCH 30/36] check.sh: check fast things first --- scripts/check.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/check.sh b/scripts/check.sh index 4ceb47d4991..7cd5b9fc7bc 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -16,6 +16,12 @@ cargo install cargo-cranky # Uses lints defined in Cranky.toml. See https://gith export RUSTFLAGS="--cfg=web_sys_unstable_apis -D warnings" export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454 +# Fast checks first: +typos +cargo fmt --all -- --check +cargo doc --lib --no-deps --all-features +cargo doc --document-private-items --no-deps --all-features + cargo check --all-targets cargo check --all-targets --all-features cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown @@ -23,10 +29,6 @@ cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown --all-feature cargo cranky --all-targets --all-features -- -D warnings cargo test --all-targets --all-features cargo test --doc # slow - checks all doc-tests -cargo fmt --all -- --check - -cargo doc --lib --no-deps --all-features -cargo doc --document-private-items --no-deps --all-features (cd crates/eframe && cargo check --no-default-features --features "glow") (cd crates/eframe && cargo check --no-default-features --features "wgpu") From 66c5a901a7f131f40ece90afd7d2d1ff6f8653f9 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 17:55:37 +0100 Subject: [PATCH 31/36] Some vertical spacing --- crates/eframe/src/epi/mod.rs | 1 + scripts/check.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index 02ecfaa5f23..e6485764514 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -829,6 +829,7 @@ pub struct WebInfo { /// Information about the URL. pub location: Location, } + /// Information about the URL. /// /// Everything has been percent decoded (`%20` -> ` ` etc). diff --git a/scripts/check.sh b/scripts/check.sh index 7cd5b9fc7bc..76dcef6be1a 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -18,6 +18,7 @@ export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454 # Fast checks first: typos +./scripts/lint.py cargo fmt --all -- --check cargo doc --lib --no-deps --all-features cargo doc --document-private-items --no-deps --all-features From 3934b955a5a56f67cfa322fdf9d0efcd629ee2bb Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 18:16:06 +0100 Subject: [PATCH 32/36] Move `native_pixels_per_point` to `RawInput` --- crates/eframe/src/epi/mod.rs | 3 --- crates/eframe/src/native/epi_integration.rs | 5 +---- crates/eframe/src/web/app_runner.rs | 1 + crates/egui-winit/src/lib.rs | 1 + crates/egui/src/data/input.rs | 22 ++++++++++++++++++++- crates/egui/src/gui_zoom.rs | 15 +++++--------- crates/egui_demo_app/src/backend_panel.rs | 6 +++--- crates/egui_demo_app/src/wrap_app.rs | 2 +- examples/save_plot/src/main.rs | 6 +++--- examples/test_viewports/src/main.rs | 2 +- 10 files changed, 37 insertions(+), 26 deletions(-) diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index e6485764514..0d1a80fcea4 100644 --- a/crates/eframe/src/epi/mod.rs +++ b/crates/eframe/src/epi/mod.rs @@ -900,9 +900,6 @@ pub struct IntegrationInfo { /// Seconds of cpu usage (in seconds) of UI code on the previous frame. /// `None` if this is the first frame. pub cpu_usage: Option, - - /// The OS native pixels-per-point - pub native_pixels_per_point: Option, } // ---------------------------------------------------------------------------- diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 879fdaa854f..c906f3c38e0 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -213,13 +213,10 @@ impl EpiIntegration { let memory = load_egui_memory(storage.as_deref()).unwrap_or_default(); egui_ctx.memory_mut(|mem| *mem = memory); - let native_pixels_per_point = window.scale_factor() as f32; - let frame = epi::Frame { info: epi::IntegrationInfo { system_theme, cpu_usage: None, - native_pixels_per_point: Some(native_pixels_per_point), }, storage, #[cfg(feature = "glow")] @@ -327,7 +324,7 @@ impl EpiIntegration { .. } => self.can_drag_window = true, WindowEvent::ScaleFactorChanged { scale_factor, .. } => { - self.frame.info.native_pixels_per_point = Some(*scale_factor as _); + egui_winit.egui_input_mut().native_pixels_per_point = Some(*scale_factor as _); } WindowEvent::ThemeChanged(winit_theme) if self.follow_system_theme => { let theme = theme_from_winit_theme(*winit_theme); diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 54ff4727fbf..a5a31881519 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -113,6 +113,7 @@ impl AppRunner { }; runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side()); + runner.input.native_pixels_per_point = Some(super::native_pixels_per_point()); Ok(runner) } diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 36cdcee41fd..de814d0abb9 100644 --- a/crates/egui-winit/src/lib.rs +++ b/crates/egui-winit/src/lib.rs @@ -226,6 +226,7 @@ impl State { // Tell egui which viewport is now active: self.egui_input.viewport_ids = ids; + self.egui_input.native_pixels_per_point = Some(native_pixels_per_point(window)); self.egui_input.take() } diff --git a/crates/egui/src/data/input.rs b/crates/egui/src/data/input.rs index 62747d3f7b3..088afc9d2c8 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -32,10 +32,19 @@ pub struct RawInput { pub screen_rect: Option, /// Also known as device pixel ratio, > 1 for high resolution screens. + /// /// If text looks blurry you probably forgot to set this. /// Set this the first frame, whenever it changes, or just on every frame. pub pixels_per_point: Option, + /// The OS native pixels-per-point. + /// + /// 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. + pub native_pixels_per_point: Option, + /// Maximum size of one side of the font texture. /// /// Ask your graphics drivers about this. This corresponds to `GL_MAX_TEXTURE_SIZE`. @@ -84,6 +93,7 @@ impl Default for RawInput { viewports: Default::default(), screen_rect: None, pixels_per_point: None, + native_pixels_per_point: None, max_texture_side: None, time: None, predicted_dt: 1.0 / 60.0, @@ -106,7 +116,8 @@ impl RawInput { viewport_ids: self.viewport_ids, viewports: self.viewports.clone(), screen_rect: self.screen_rect.take(), - pixels_per_point: self.pixels_per_point.take(), + pixels_per_point: self.pixels_per_point.take(), // take the diff + native_pixels_per_point: self.native_pixels_per_point, // copy max_texture_side: self.max_texture_side.take(), time: self.time.take(), predicted_dt: self.predicted_dt, @@ -125,6 +136,7 @@ impl RawInput { viewports, screen_rect, pixels_per_point, + native_pixels_per_point, max_texture_side, time, predicted_dt, @@ -139,6 +151,7 @@ impl RawInput { self.viewports = viewports; self.screen_rect = screen_rect.or(self.screen_rect); self.pixels_per_point = pixels_per_point.or(self.pixels_per_point); + self.native_pixels_per_point = native_pixels_per_point.or(self.native_pixels_per_point); self.max_texture_side = max_texture_side.or(self.max_texture_side); self.time = time; // use latest time self.predicted_dt = predicted_dt; // use latest dt @@ -1085,6 +1098,7 @@ impl RawInput { viewports, screen_rect, pixels_per_point, + native_pixels_per_point, max_texture_side, time, predicted_dt, @@ -1112,6 +1126,12 @@ impl RawInput { .on_hover_text( "Also called HDPI factor.\nNumber of physical pixels per each logical pixel.", ); + ui.label(format!( + "native_pixels_per_point: {native_pixels_per_point:?}" + )) + .on_hover_text( + "Also called HDPI factor.\nNumber of physical pixels per each logical pixel.", + ); ui.label(format!("max_texture_side: {max_texture_side:?}")); if let Some(time) = time { ui.label(format!("time: {time:.3} s")); diff --git a/crates/egui/src/gui_zoom.rs b/crates/egui/src/gui_zoom.rs index 1bee63880c7..e56556b7cd4 100644 --- a/crates/egui/src/gui_zoom.rs +++ b/crates/egui/src/gui_zoom.rs @@ -15,19 +15,14 @@ pub mod kb_shortcuts { /// Let the user scale the GUI (change `Context::pixels_per_point`) by pressing /// Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser. /// -/// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as: -/// ```ignore +/// ``` /// // On web, the browser controls the gui zoom. -/// if !frame.is_web() { -/// egui::gui_zoom::zoom_with_keyboard_shortcuts( -/// ctx, -/// frame.info().native_pixels_per_point, -/// ); -/// } +/// #[cfg(not(target_arch = "wasm32"))] +/// egui::gui_zoom::zoom_with_keyboard_shortcuts(ct); /// ``` -pub fn zoom_with_keyboard_shortcuts(ctx: &Context, native_pixels_per_point: Option) { +pub fn zoom_with_keyboard_shortcuts(ctx: &Context) { if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_RESET)) { - if let Some(native_pixels_per_point) = native_pixels_per_point { + if let Some(native_pixels_per_point) = ctx.input(|i| i.raw.native_pixels_per_point) { ctx.set_pixels_per_point(native_pixels_per_point); } } else { diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 04039383be6..cacec3e3f75 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -151,7 +151,7 @@ impl BackendPanel { // On web, the browser controls `pixels_per_point`. let integration_controls_pixels_per_point = frame.is_web(); if !integration_controls_pixels_per_point { - self.pixels_per_point_ui(ui, frame.info()); + self.pixels_per_point_ui(ui); } #[cfg(not(target_arch = "wasm32"))] @@ -196,7 +196,7 @@ impl BackendPanel { } } - fn pixels_per_point_ui(&mut self, ui: &mut egui::Ui, info: &eframe::IntegrationInfo) { + fn pixels_per_point_ui(&mut self, ui: &mut egui::Ui) { let pixels_per_point = self .pixels_per_point .get_or_insert_with(|| ui.ctx().pixels_per_point()); @@ -224,7 +224,7 @@ impl BackendPanel { reset = true; } - if let Some(native_pixels_per_point) = info.native_pixels_per_point { + if let Some(native_pixels_per_point) = ui.input(|i| i.raw.native_pixels_per_point) { let enabled = ui.ctx().pixels_per_point() != native_pixels_per_point; if ui .add_enabled(enabled, egui::Button::new("Reset")) diff --git a/crates/egui_demo_app/src/wrap_app.rs b/crates/egui_demo_app/src/wrap_app.rs index 6e8dd87d60b..145dc1f6577 100644 --- a/crates/egui_demo_app/src/wrap_app.rs +++ b/crates/egui_demo_app/src/wrap_app.rs @@ -285,7 +285,7 @@ impl eframe::App for WrapApp { // On web, the browser controls `pixels_per_point`. if !frame.is_web() { - egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx, frame.info().native_pixels_per_point); + egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx); } self.run_cmd(ctx, cmd); diff --git a/examples/save_plot/src/main.rs b/examples/save_plot/src/main.rs index 24841349c4c..065ab9755de 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -21,7 +21,7 @@ fn main() -> Result<(), eframe::Error> { struct MyApp {} impl eframe::App for MyApp { - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { let mut plot_rect = None; egui::CentralPanel::default().show(ctx, |ui| { if ui.button("Save Plot").clicked() { @@ -56,8 +56,8 @@ impl eframe::App for MyApp { // for a full size application, we should put this in a different thread, // so that the GUI doesn't lag during saving - let pixels_per_point = frame.info().native_pixels_per_point; - let plot = screenshot.region(&plot_location, pixels_per_point); + let pixels_per_point = ctx.pixels_per_point(); + let plot = screenshot.region(&plot_location, Some(pixels_per_point)); // save the plot to png image::save_buffer( &path, diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index b933cee0dd8..16394c4af24 100644 --- a/examples/test_viewports/src/main.rs +++ b/examples/test_viewports/src/main.rs @@ -258,7 +258,7 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc>]) { data.insert_temp(container_id.with("pixels_per_point"), tmp_pixels_per_point); }); } - egui::gui_zoom::zoom_with_keyboard_shortcuts(&ctx, None); + egui::gui_zoom::zoom_with_keyboard_shortcuts(&ctx); if ctx.viewport_id() != ctx.parent_viewport_id() { let parent = ctx.parent_viewport_id(); From 26b2382f1f9527e71676ceffe68907f5f4647a8e Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 18:22:14 +0100 Subject: [PATCH 33/36] Fix web build --- crates/eframe/src/web/app_runner.rs | 3 +-- scripts/check.sh | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index a5a31881519..cca5397dced 100644 --- a/crates/eframe/src/web/app_runner.rs +++ b/crates/eframe/src/web/app_runner.rs @@ -49,7 +49,6 @@ impl AppRunner { }, system_theme, cpu_usage: None, - native_pixels_per_point: Some(super::native_pixels_per_point()), }; let storage = LocalStorage::default(); @@ -113,7 +112,7 @@ impl AppRunner { }; runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side()); - runner.input.native_pixels_per_point = Some(super::native_pixels_per_point()); + runner.input.raw.native_pixels_per_point = Some(super::native_pixels_per_point()); Ok(runner) } diff --git a/scripts/check.sh b/scripts/check.sh index 76dcef6be1a..729112419b5 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -23,11 +23,13 @@ cargo fmt --all -- --check cargo doc --lib --no-deps --all-features cargo doc --document-private-items --no-deps --all-features +cargo cranky --all-targets --all-features -- -D warnings +./scripts/clippy_wasm.sh + cargo check --all-targets cargo check --all-targets --all-features cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features -cargo cranky --all-targets --all-features -- -D warnings cargo test --all-targets --all-features cargo test --doc # slow - checks all doc-tests From 7652041be3119c206d11f3f533f6d3c77a6f4fcd Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 18:23:13 +0100 Subject: [PATCH 34/36] Update check.sh --- scripts/check.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/check.sh b/scripts/check.sh index 729112419b5..0db17bb6dc9 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -58,8 +58,6 @@ cargo test --doc # slow - checks all doc-tests ./scripts/wasm_bindgen_check.sh -cargo cranky --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings - ./scripts/cargo_deny.sh # TODO(emilk): consider using https://github.com/taiki-e/cargo-hack or https://github.com/frewsxcv/cargo-all-features From e53b7a72a7eb34bb41e102b0cf9c496a2f617257 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 18:41:57 +0100 Subject: [PATCH 35/36] Fix doctest --- crates/egui/src/gui_zoom.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/gui_zoom.rs b/crates/egui/src/gui_zoom.rs index e56556b7cd4..a8fdf4b1fa6 100644 --- a/crates/egui/src/gui_zoom.rs +++ b/crates/egui/src/gui_zoom.rs @@ -16,9 +16,10 @@ pub mod kb_shortcuts { /// Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser. /// /// ``` +/// # let ctx = &egui::Context::default(); /// // On web, the browser controls the gui zoom. /// #[cfg(not(target_arch = "wasm32"))] -/// egui::gui_zoom::zoom_with_keyboard_shortcuts(ct); +/// egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx); /// ``` pub fn zoom_with_keyboard_shortcuts(ctx: &Context) { if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_RESET)) { From 271064dd00b185ad9e1bc2289f1a9527696abe24 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 18 Nov 2023 19:09:17 +0100 Subject: [PATCH 36/36] Add docs about the new stuff --- crates/egui/src/viewport.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index 74ad7e8e4e5..62034cb0120 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -38,10 +38,23 @@ //! //! ## Using the viewports //! Only one viewport is active at any one time, identified with [`Context::viewport_id`]. -//! You can send commands to other viewports using [`Context::send_viewport_cmd_to`]. +//! You can modify the current (change the title, resize the window, etc) by sending +//! a [`ViewportCommand`] to it using [`Context::send_viewport_cmd`]. +//! You can interact with other viewports using [`Context::send_viewport_cmd_to`]. //! //! There is an example in . //! +//! You can find all available viewports in [`crate::RawInput::viewports`] and the active viewport in +//! [`crate::InputState::viewport`]: +//! +//! ```no_run +//! # let ctx = &egui::Context::default(); +//! ctx.input(|i| { +//! dbg!(&i.viewport()); // Current viewport +//! dbg!(&i.raw.viewports); // All viewports +//! }); +//! ``` +//! //! ## For integrations //! * There is a [`crate::InputState::viewport`] with information about the current viewport. //! * There is a [`crate::RawInput::viewports`] with information about all viewports.