diff --git a/crates/eframe/src/epi/mod.rs b/crates/eframe/src/epi/mod.rs index aff7c7d3802..0d1a80fcea4 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. @@ -732,9 +727,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>, @@ -746,11 +738,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, @@ -799,67 +786,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`]. - /// - /// 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 - /// frame.request_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() @@ -891,141 +817,6 @@ impl Frame { pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> { 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, - /// which, if unexpected, will disturb the user. - #[cfg(not(target_arch = "wasm32"))] - pub fn focus(&mut self) { - 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 { - std::mem::take(&mut self.output) - } } /// Information about the web environment (if applicable). @@ -1039,38 +830,6 @@ pub struct WebInfo { 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). @@ -1141,13 +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, - - /// The position and size of the native window. - #[cfg(not(target_arch = "wasm32"))] - pub window_info: WindowInfo, } // ---------------------------------------------------------------------------- @@ -1211,68 +963,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 { - /// 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 a984258e84f..c906f3c38e0 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -4,61 +4,13 @@ use winit::event_loop::EventLoopWindowTarget; use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _}; -use egui::{DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId, ViewportIdPair}; -use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings}; +use egui::{ + DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId, ViewportIdPair, + ViewportInfo, +}; +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, @@ -208,97 +160,6 @@ 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, -) { - 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), - }); - } - } -} - // ---------------------------------------------------------------------------- /// For loading/saving app state and/or egui memory to disk. @@ -313,10 +174,13 @@ 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, pub beginning: Instant, + is_first_frame: bool, pub frame_start: Instant, pub egui_ctx: egui::Context, pending_full_output: egui::FullOutput, @@ -325,7 +189,6 @@ pub struct EpiIntegration { close: bool, can_drag_window: bool, - window_state: WindowState, follow_system_theme: bool, #[cfg(feature = "persistence")] persist_window: bool, @@ -350,30 +213,16 @@ 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 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() }, storage, #[cfg(feature = "glow")] 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(), }; @@ -390,12 +239,12 @@ 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, app_icon_setter, beginning: Instant::now(), + is_first_frame: true, frame_start: Instant::now(), } } @@ -432,10 +281,12 @@ 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); - self.pre_update(window); + 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(); let full_output = self.update(app, None, raw_input); - self.post_update(app, 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(); @@ -473,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); @@ -483,16 +334,12 @@ 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) { + 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! @@ -513,6 +360,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); } @@ -522,45 +374,16 @@ 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) { - 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 - 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, - ); - + pub fn post_update(&mut self) { let frame_time = self.frame_start.elapsed().as_secs_f64() as f32; 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); - } - - 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); + 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 3b67a21a19a..191970ef4ad 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: @@ -527,14 +529,23 @@ mod glow_integration { let (raw_input, viewport_ui_cb) = { let mut glutin = self.glutin.borrow_mut(); let viewport = glutin.viewports.get_mut(&viewport_id).unwrap(); + viewport.update_viewport_info(); 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 mut raw_input = egui_winit.take_egui_input(window, viewport.ids); + let viewport_ui_cb = viewport.viewport_ui_cb.clone(); + + self.integration.pre_update(); - self.integration.pre_update(window); + 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.viewport_ui_cb.clone()) + (raw_input, viewport_ui_cb) }; // ------------------------------------------------------------ @@ -577,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(); integration.handle_platform_output(window, viewport_id, platform_output, egui_winit); let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point); @@ -607,13 +618,18 @@ 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)); + egui_winit + .egui_input_mut() + .events + .push(egui::Event::Screenshot { + viewport_id, + image: screenshot.into(), + }); } - integration.post_rendering(app.as_mut(), window); + integration.post_rendering(window); } { @@ -627,8 +643,6 @@ mod glow_integration { } } - integration.post_present(window); - // give it time to settle: #[cfg(feature = "__screenshot")] if integration.egui_ctx.frame_nr() == 2 { @@ -716,6 +730,7 @@ mod glow_integration { return EventResult::Exit; } } + _ => {} } @@ -783,6 +798,8 @@ mod glow_integration { ids: ViewportIdPair, class: ViewportClass, builder: ViewportBuilder, + info: ViewportInfo, + screenshot_requested: bool, /// The user-callback that shows the ui. /// None for immediate viewports. @@ -793,6 +810,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: @@ -935,9 +965,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(); @@ -947,6 +980,8 @@ mod glow_integration { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder: viewport_builder, + info, + screenshot_requested: false, viewport_ui_cb: None, gl_surface: None, window: window.map(Rc::new), @@ -1016,13 +1051,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)) }; { @@ -1178,7 +1214,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, @@ -1187,15 +1223,15 @@ mod glow_integration { focused_viewport, ); - if let Some(viewport) = self.viewports.get(&viewport_id) { - if let Some(window) = &viewport.window { - let is_viewport_focused = focused_viewport == Some(viewport_id); - egui_winit::process_viewport_commands( - 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, + &mut viewport.screenshot_requested, + ); } } @@ -1234,6 +1270,8 @@ mod glow_integration { ids, class, builder, + info: Default::default(), + screenshot_requested: false, viewport_ui_cb, window: None, egui_winit: None, @@ -1261,7 +1299,13 @@ 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, + &mut viewport.screenshot_requested, + ); } entry.into_mut() @@ -1378,7 +1422,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, @@ -1548,7 +1592,7 @@ mod glow_integration { let Some(viewport) = glutin.viewports.get_mut(&ids.this) else { return; }; - + viewport.update_viewport_info(); let Some(winit_state) = &mut viewport.egui_winit else { return; }; @@ -1556,9 +1600,14 @@ mod glow_integration { return; }; - let mut input = winit_state.take_egui_input(window, ids); - 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 }; // --------------------------------------------------- @@ -1813,18 +1862,20 @@ 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 { ids: ViewportIdPair, - class: ViewportClass, - builder: ViewportBuilder, + info: ViewportInfo, + screenshot_requested: bool, /// `None` for sync viewports. viewport_ui_cb: Option>, @@ -1849,28 +1900,42 @@ 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}"); } } } + + /// 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; @@ -1888,7 +1953,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 +2053,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, @@ -2066,6 +2131,12 @@ mod wgpu_integration { ids: ViewportIdPair::ROOT, class: ViewportClass::Root, builder, + info: ViewportInfo { + minimized: window.is_minimized(), + maximized: Some(window.is_maximized()), + ..Default::default() + }, + screenshot_requested: false, viewport_ui_cb: None, window: Some(Rc::new(window)), egui_winit: Some(egui_winit), @@ -2155,6 +2226,7 @@ mod wgpu_integration { painter, viewport_from_window, } = &mut *shared.borrow_mut(); + let viewport = initialize_or_update_viewport( viewports, ids, @@ -2163,10 +2235,10 @@ 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 { @@ -2174,6 +2246,10 @@ mod wgpu_integration { }; 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 }; @@ -2308,20 +2384,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( @@ -2394,18 +2456,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!(); @@ -2475,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, @@ -2483,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; @@ -2493,14 +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), ); - integration.pre_update(window); + integration.pre_update(); - (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) }; // ------------------------------------------------------------ @@ -2533,7 +2591,7 @@ mod wgpu_integration { return EventResult::Wait; }; - integration.post_update(app.as_mut(), window); + integration.post_update(); let FullOutput { platform_output, @@ -2548,21 +2606,27 @@ 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); + 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); - integration.post_present(window); + integration.post_rendering(window); let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect(); @@ -2630,6 +2694,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 @@ -2645,6 +2710,7 @@ mod wgpu_integration { } } } + winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { use std::num::NonZeroU32; if let (Some(width), Some(height), Some(viewport_id)) = ( @@ -2656,10 +2722,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; } + _ => {} }; @@ -2709,7 +2777,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, @@ -2718,12 +2786,15 @@ mod wgpu_integration { focused_viewport, ); - if let Some(window) = viewports - .get(&viewport_id) - .and_then(|vp| vp.window.as_ref()) - { + 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, + &mut viewport.screenshot_requested, + ); } } } @@ -2751,6 +2822,8 @@ mod wgpu_integration { ids, class, builder, + info: Default::default(), + screenshot_requested: false, viewport_ui_cb, window: None, egui_winit: None, @@ -2777,7 +2850,13 @@ 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, + &mut viewport.screenshot_requested, + ); } entry.into_mut() diff --git a/crates/eframe/src/web/app_runner.rs b/crates/eframe/src/web/app_runner.rs index 6c93924f879..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(); @@ -78,7 +77,6 @@ impl AppRunner { let frame = epi::Frame { info, - output: Default::default(), storage: Some(Box::new(storage)), #[cfg(feature = "glow")] @@ -114,6 +112,7 @@ impl AppRunner { }; runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side()); + runner.input.raw.native_pixels_per_point = Some(super::native_pixels_per_point()); Ok(runner) } @@ -192,17 +191,19 @@ 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); 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 diff --git a/crates/egui-winit/src/lib.rs b/crates/egui-winit/src/lib.rs index 52b7e718241..de814d0abb9 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; @@ -24,11 +26,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) } @@ -133,7 +135,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, ) { @@ -178,13 +180,28 @@ 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`] + 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). - pub fn take_egui_input( - &mut self, - window: &winit::window::Window, - ids: ViewportIdPair, - ) -> egui::RawInput { + /// + /// 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) -> egui::RawInput { crate::profile_function!(); let pixels_per_point = self.pixels_per_point(); @@ -207,58 +224,9 @@ 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 - }; - + // 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() } @@ -269,6 +237,7 @@ impl State { &mut self, egui_ctx: &egui::Context, event: &winit::event::WindowEvent<'_>, + viewport_id: ViewportId, ) -> EventResponse { crate::profile_function!(); @@ -453,7 +422,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, @@ -724,7 +695,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, @@ -772,7 +743,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. @@ -796,6 +767,82 @@ impl State { } } +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() { + 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); + + 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.title = Some(window.title()); + 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) { #[cfg(feature = "webbrowser")] if let Err(err) = webbrowser::open(_url) { @@ -995,9 +1042,11 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option, - window: &winit::window::Window, + window: &Window, is_viewport_focused: bool, + screenshot_requested: &mut bool, ) { crate::profile_function!(); @@ -1005,20 +1054,27 @@ pub fn process_viewport_commands( for command in commands { match command { - egui::ViewportCommand::StartDrag => { - // if this is not checked on x11 the input will be permanently taken until the app is killed! + ViewportCommand::Close => { + info.close_requested = true; + } + ViewportCommand::StartDrag => { + // 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}"); } } } - 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, @@ -1031,7 +1087,9 @@ pub fn process_viewport_commands( log::warn!("{command:?}: {err}"); } } - ViewportCommand::Title(title) => window.set_title(&title), + ViewportCommand::Title(title) => { + window.set_title(&title); + } ViewportCommand::Transparent(v) => window.set_transparent(v), ViewportCommand::Visible(v) => window.set_visible(v), ViewportCommand::OuterPosition(pos) => { @@ -1070,8 +1128,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))); } @@ -1100,15 +1164,21 @@ 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(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), @@ -1136,6 +1206,9 @@ pub fn process_viewport_commands( log::warn!("{command:?}: {err}"); } } + ViewportCommand::Screenshot => { + *screenshot_requested = true; + } } } } @@ -1268,5 +1341,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/context.rs b/crates/egui/src/context.rs index d12056a1b91..26b0661ffbb 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(); @@ -2555,15 +2555,16 @@ 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); } /// This creates a new native window, if possible. @@ -2572,6 +2573,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::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. /// This means it may be called multiple times, for instance while the @@ -2629,6 +2633,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::viewport`]. + /// /// 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 b08732536b3..088afc9d2c8 100644 --- a/crates/egui/src/data/input.rs +++ b/crates/egui/src/data/input.rs @@ -1,6 +1,8 @@ //! The input needed by egui. -use crate::{emath::*, ViewportIdPair}; +use epaint::ColorImage; + +use crate::{emath::*, ViewportIdMap, ViewportIdPair}; /// What the integrations provides to egui at the start of each frame. /// @@ -13,8 +15,11 @@ use crate::{emath::*, 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 @@ -27,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`. @@ -75,9 +89,11 @@ 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, + native_pixels_per_point: None, max_texture_side: None, time: None, predicted_dt: 1.0 / 60.0, @@ -97,9 +113,11 @@ 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(), + 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, @@ -114,9 +132,11 @@ impl RawInput { /// Add on new input. pub fn append(&mut self, newer: Self) { let Self { - viewport, + viewport_ids, + viewports, screen_rect, pixels_per_point, + native_pixels_per_point, max_texture_side, time, predicted_dt, @@ -127,9 +147,11 @@ 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.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 @@ -143,23 +165,50 @@ impl RawInput { /// Information about the current viewport, /// given as input each frame. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +/// +/// `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, - - /// Viewport inner position and size, only the drowable area - /// unit = physical pixels - pub inner_rect_px: Option, + /// Parent viewport, if known. + pub parent: Option, - /// Viewport outer position and size, drowable area + decorations - /// unit = physical pixels - pub outer_rect_px: Option, + /// 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. + 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, + + /// Are we minimized? + pub minimized: Option, + + /// Are we maximized? + pub maximized: Option, + + /// Are we in fullscreen mode? + pub fullscreen: Option, + + /// Is the window focused and able to receive input? + /// + /// This should be the same as [`RawInput::focused`]. + pub focused: Option, } impl ViewportInfo { @@ -169,15 +218,74 @@ impl ViewportInfo { pub fn ui(&self, ui: &mut crate::Ui) { let Self { - ids, - inner_rect_px, - outer_rect_px, + parent, + title, close_requested, + pixels_per_point, + monitor_size, + inner_rect, + outer_rect, + minimized, + maximized, + fullscreen, + focused, } = 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!("close_requested: {close_requested:?}")); + + 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("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(); + + 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:?}")) + } + }); } } @@ -352,6 +460,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) @@ -980,8 +1094,11 @@ fn format_kb_shortcut() { impl RawInput { pub fn ui(&self, ui: &mut crate::Ui) { let Self { + viewport_ids, + viewports, screen_rect, pixels_per_point, + native_pixels_per_point, max_texture_side, time, predicted_dt, @@ -990,15 +1107,31 @@ 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:?}")); + ui.push_id(id, |ui| { + viewport.ui(ui); + }); + }); + } ui.label(format!("screen_rect: {screen_rect:?} points")); ui.label(format!("pixels_per_point: {pixels_per_point:?}")) .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/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/gui_zoom.rs b/crates/egui/src/gui_zoom.rs index 1bee63880c7..a8fdf4b1fa6 100644 --- a/crates/egui/src/gui_zoom.rs +++ b/crates/egui/src/gui_zoom.rs @@ -15,19 +15,15 @@ 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 +/// ``` +/// # let ctx = &egui::Context::default(); /// // 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(ctx); /// ``` -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/src/input_state.rs b/crates/egui/src/input_state.rs index ac03c1ccdd9..79aff9a9c11 100644 --- a/crates/egui/src/input_state.rs +++ b/crates/egui/src/input_state.rs @@ -231,6 +231,11 @@ impl InputState { } } + /// 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") + } + #[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/crates/egui/src/viewport.rs b/crates/egui/src/viewport.rs index c3fa7cec009..62034cb0120 100644 --- a/crates/egui/src/viewport.rs +++ b/crates/egui/src/viewport.rs @@ -38,12 +38,26 @@ //! //! ## 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 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::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`]. @@ -636,13 +650,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 { @@ -655,7 +662,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. /// @@ -663,7 +670,13 @@ pub enum ResizeDirection { #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum ViewportCommand { - /// Set the title + /// Request this viewport to be closed. + /// + /// For the root viewport, this usually results in the application shutting down. + /// For other viewports, the [`crate::ViewportInfo::close_requested`] flag will be set. + Close, + + /// Set the window title. Title(String), /// Turn the window transparent or not. @@ -709,21 +722,45 @@ 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), - RequestUserAttention(Option), + /// 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. + /// + /// 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), @@ -737,6 +774,29 @@ pub enum ViewportCommand { CursorVisible(bool), CursorHitTest(bool), + + /// Take a screenshot. + /// + /// The results are returned in `crate::Event::Screenshot`. + Screenshot, +} + +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. diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index fb0d9ae4979..cacec3e3f75 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_cmd(egui::ViewportCommand::Close); } } @@ -151,20 +151,21 @@ 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"))] { 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_cmd(egui::ViewportCommand::Fullscreen(fullscreen)); } } @@ -173,29 +174,29 @@ 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_cmd(egui::ViewportCommand::InnerSize(size)); + ui.ctx() + .send_viewport_cmd(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_cmd(egui::ViewportCommand::StartDrag); } - - ui.button("Native window info (hover me)") - .on_hover_ui(|ui| { - window_info_ui(ui, &frame.info().window_info); - }); } } - 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()); @@ -223,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")) @@ -285,55 +286,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..145dc1f6577 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_cmd(egui::ViewportCommand::Fullscreen(!fullscreen)); } let mut cmd = Command::Nothing; @@ -284,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/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..d413def6900 --- /dev/null +++ b/crates/egui_demo_lib/src/demo/extra_viewport.rs @@ -0,0 +1,72 @@ +#[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_content(ui, ctx, open); + }) + }); + } + }, + ); + } +} + +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!( + "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:?}")); + ui.push_id(id, |ui| { + 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; diff --git a/crates/egui_glow/src/winit.rs b/crates/egui_glow/src/winit.rs index ad47d6ebcef..23eaa899298 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,17 @@ 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); + 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/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, + } + } +} diff --git a/examples/confirm_exit/src/main.rs b/examples/confirm_exit/src/main.rs index 05b45bd0b4e..33d799d3874 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_cmd(egui::ViewportCommand::Close); } }); }); diff --git a/examples/custom_window_frame/src/main.rs b/examples/custom_window_frame/src/main.rs index 8ea32fbf74a..98b24497f26 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`). @@ -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(); @@ -112,9 +102,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_cmd(ViewportCommand::Maximized(!is_maximized)); } else if title_bar_response.is_pointer_button_down_on() { - frame.drag_window(); + ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag); } ui.allocate_ui_at_rect(title_bar_rect, |ui| { @@ -122,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; @@ -137,22 +129,24 @@ 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_cmd(egui::ViewportCommand::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_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() { - frame.set_maximized(true); + ui.ctx().send_viewport_cmd(ViewportCommand::Maximized(true)); } } @@ -160,6 +154,6 @@ 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_cmd(ViewportCommand::Minimized(true)); } } 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/save_plot/src/main.rs b/examples/save_plot/src/main.rs index a13e071aa29..065ab9755de 100644 --- a/examples/save_plot/src/main.rs +++ b/examples/save_plot/src/main.rs @@ -1,7 +1,6 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release use eframe::egui; -use eframe::egui::ColorImage; use egui_plot::{Legend, Line, Plot, PlotPoints}; fn main() -> Result<(), eframe::Error> { @@ -19,16 +18,14 @@ 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) { + 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() { - frame.request_screenshot(); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); } let my_plot = Plot::new("My Plot").legend(Legend::default()); @@ -42,15 +39,25 @@ 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"); // 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, @@ -60,14 +67,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 498d5013e0d..91f2f92cfc0 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,12 +21,12 @@ fn main() -> Result<(), eframe::Error> { struct MyApp { continuously_take_screenshots: bool, texture: Option, - screenshot: Option, + screenshot: Option>, save_to_file: bool, } 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 +44,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_cmd(egui::ViewportCommand::Screenshot); } ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| { @@ -55,9 +57,9 @@ impl eframe::App for MyApp { } else { ctx.set_visuals(egui::Visuals::light()); }; - frame.request_screenshot(); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); } else if ui.button("take screenshot!").clicked() { - frame.request_screenshot(); + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot); } }); }); @@ -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); - } - } } diff --git a/examples/serial_windows/src/main.rs b/examples/serial_windows/src/main.rs index 5e85d646781..6edccc5e0a5 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_cmd(egui::ViewportCommand::Close); } }); } diff --git a/examples/test_viewports/src/main.rs b/examples/test_viewports/src/main.rs index 99908cd906b..16394c4af24 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.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.viewport().outer_rect) { ui.label(format!( "Outer Rect: Pos: {:?}, Size: {:?}", outer_rect.min, @@ -258,12 +258,12 @@ 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(); 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 8134062fdfa..7478eb072cd 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,11 @@ 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_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,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_cmd(egui::ViewportCommand::RequestUserAttention( + UserAttentionType::Reset, + )); } } @@ -77,7 +77,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 [ diff --git a/scripts/check.sh b/scripts/check.sh index 4ceb47d4991..0db17bb6dc9 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -16,17 +16,22 @@ 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 +./scripts/lint.py +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 -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") @@ -53,8 +58,6 @@ cargo doc --document-private-items --no-deps --all-features ./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