diff --git a/crates/eframe/src/native/epi_integration.rs b/crates/eframe/src/native/epi_integration.rs index 4409c1298..1a1e47765 100644 --- a/crates/eframe/src/native/epi_integration.rs +++ b/crates/eframe/src/native/epi_integration.rs @@ -7,7 +7,7 @@ use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _}; #[cfg(feature = "accesskit")] use egui::accesskit; -use egui::NumExt as _; +use egui::{window::WindowBuilder, NumExt as _}; #[cfg(feature = "accesskit")] use egui_winit::accesskit_winit; use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings}; @@ -77,7 +77,7 @@ pub fn window_builder( title: &str, native_options: &epi::NativeOptions, window_settings: Option, -) -> winit::window::WindowBuilder { +) -> WindowBuilder { let epi::NativeOptions { maximized, decorated, @@ -97,16 +97,14 @@ pub fn window_builder( .. } = native_options; - let window_icon = icon_data.clone().and_then(load_icon); - - let mut window_builder = winit::window::WindowBuilder::new() + let mut window_builder = WindowBuilder::default() .with_title(title) .with_decorations(*decorated) - .with_fullscreen(fullscreen.then(|| winit::window::Fullscreen::Borderless(None))) + .with_fullscreen(*fullscreen) .with_maximized(*maximized) .with_resizable(*resizable) .with_transparent(*transparent) - .with_window_icon(window_icon) + .with_window_icon(icon_data.clone().map(|d| (d.width, d.height, d.rgba))) .with_active(*active) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279 // We must also keep the window hidden until AccessKit is initialized. @@ -127,13 +125,13 @@ pub fn window_builder( } if let Some(min_size) = *min_window_size { - window_builder = window_builder.with_min_inner_size(points_to_size(min_size)); + window_builder = window_builder.with_min_inner_size((min_size.x as u32, min_size.y as u32)); } if let Some(max_size) = *max_window_size { - window_builder = window_builder.with_max_inner_size(points_to_size(max_size)); + window_builder = window_builder.with_max_inner_size((max_size.x as u32, max_size.y as u32)); } - window_builder = window_builder_drag_and_drop(window_builder, *drag_and_drop_support); + window_builder = window_builder.with_drag_and_drop(*drag_and_drop_support); let inner_size_points = if let Some(mut window_settings) = window_settings { // Restore pos/size from previous session @@ -144,16 +142,14 @@ pub fn window_builder( window_settings.inner_size_points() } else { if let Some(pos) = *initial_window_pos { - window_builder = window_builder.with_position(winit::dpi::LogicalPosition { - x: pos.x as f64, - y: pos.y as f64, - }); + window_builder = window_builder.with_position((pos.x as i32, pos.y as i32)); } if let Some(initial_window_size) = *initial_window_size { let initial_window_size = initial_window_size.at_most(largest_monitor_point_size(event_loop)); - window_builder = window_builder.with_inner_size(points_to_size(initial_window_size)); + window_builder = window_builder + .with_inner_size((initial_window_size.x as u32, initial_window_size.y as u32)); } *initial_window_size @@ -166,7 +162,7 @@ pub fn window_builder( if monitor_size.width > 0.0 && monitor_size.height > 0.0 { let x = (monitor_size.width - inner_size.x as f64) / 2.0; let y = (monitor_size.height - inner_size.y as f64) / 2.0; - window_builder = window_builder.with_position(winit::dpi::LogicalPosition { x, y }); + window_builder = window_builder.with_position((x as i32, y as i32)); } } } @@ -201,7 +197,7 @@ fn largest_monitor_point_size(event_loop: &EventLoopWindowTarget) -> egui: } } -fn load_icon(icon_data: epi::IconData) -> Option { +pub fn load_icon(icon_data: epi::IconData) -> Option { winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok() } diff --git a/crates/eframe/src/native/run.rs b/crates/eframe/src/native/run.rs index 8ce8f9f96..720448513 100644 --- a/crates/eframe/src/native/run.rs +++ b/crates/eframe/src/native/run.rs @@ -3,7 +3,7 @@ use std::time::Instant; -use egui::epaint::ahash::HashMap; +use egui::{epaint::ahash::HashMap, window::WindowBuilder}; use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _}; use winit::event_loop::{ ControlFlow, EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget, @@ -15,7 +15,7 @@ use egui_winit::winit; use crate::{epi, Result}; -use super::epi_integration::{self, EpiIntegration}; +use super::epi_integration::{self, load_icon, EpiIntegration}; // ---------------------------------------------------------------------------- @@ -400,12 +400,13 @@ fn run_and_exit(event_loop: EventLoop, mut winit_app: impl WinitApp + mod glow_integration { use std::sync::Arc; - use egui::{epaint::ahash::HashMap, NumExt as _}; + use egui::{epaint::ahash::HashMap, window::WindowBuilder, NumExt as _}; use glutin::{ display::GetGlDisplay, prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext}, surface::GlSurface, }; + use winit::dpi::{PhysicalPosition, PhysicalSize}; use super::*; @@ -436,7 +437,7 @@ mod glow_integration { } struct Window { - builder: winit::window::WindowBuilder, + builder: WindowBuilder, gl_surface: Option>, window: Option, window_id: u64, @@ -468,7 +469,7 @@ mod glow_integration { /// #[allow(unsafe_code)] unsafe fn new( - winit_window_builder: winit::window::WindowBuilder, + window_builder: WindowBuilder, native_options: &epi::NativeOptions, event_loop: &EventLoopWindowTarget, ) -> Result { @@ -516,7 +517,7 @@ mod glow_integration { let (window, gl_config) = glutin_winit::DisplayBuilder::new() // we might want to expose this option to users in the future. maybe using an env var or using native_options. .with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150 - .with_window_builder(Some(winit_window_builder.clone())) + .with_window_builder(Some(create_winit_window_builder(&window_builder))) .build( event_loop, config_template_builder.clone(), @@ -581,7 +582,7 @@ mod glow_integration { current_gl_context: None, not_current_gl_context, windows: vec![Window { - builder: winit_window_builder, + builder: window_builder, gl_surface: None, window, window_id: 0, @@ -610,7 +611,7 @@ mod glow_integration { log::debug!("window doesn't exist yet. creating one now with finalize_window"); glutin_winit::finalize_window( event_loop, - self.windows[0].builder.clone(), + create_winit_window_builder(&self.windows[0].builder), &self.gl_config, ) .expect("failed to finalize glutin window") @@ -947,119 +948,167 @@ mod glow_integration { painter, } = running; - let window = gl_window.window(window_index); - - let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); - - egui_glow::painter::clear( - &gl, - screen_size_in_pixels, - app.clear_color(&integration.egui_ctx.style().visuals), - ); - let egui::FullOutput { platform_output, repaint_after, textures_delta, shapes, - } = integration.update(app.as_mut(), window); - - integration.handle_platform_output(window, platform_output); - - let clipped_primitives = { - crate::profile_scope!("tessellate"); - integration.egui_ctx.tessellate(shapes) + mut windows, }; - painter.paint_and_update_textures( - screen_size_in_pixels, - integration.egui_ctx.pixels_per_point(), - &clipped_primitives, - &textures_delta, - ); - - let screenshot_requested = &mut integration.frame.output.screenshot_requested; - - if *screenshot_requested { - *screenshot_requested = false; - let screenshot = painter.read_screen_rgba(screen_size_in_pixels); - integration.frame.screenshot.set(Some(screenshot)); - } - - integration.post_rendering(app.as_mut(), window); - + let control_flow; { - crate::profile_scope!("swap_buffers"); - gl_window.swap_buffers().unwrap(); - } + let window = gl_window.window(window_index); - integration.post_present(window); + let screen_size_in_pixels: [u32; 2] = window.inner_size().into(); - #[cfg(feature = "__screenshot")] - // give it time to settle: - if integration.egui_ctx.frame_nr() == 2 { - if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") { - assert!( + egui_glow::painter::clear( + &gl, + screen_size_in_pixels, + app.clear_color(&integration.egui_ctx.style().visuals), + ); + + egui::FullOutput { + platform_output, + repaint_after, + textures_delta, + shapes, + windows, + } = integration.update(app.as_mut(), window); + + integration.handle_platform_output(window, platform_output); + + let clipped_primitives = { + crate::profile_scope!("tessellate"); + integration.egui_ctx.tessellate(shapes) + }; + + painter.paint_and_update_textures( + screen_size_in_pixels, + integration.egui_ctx.pixels_per_point(), + &clipped_primitives, + &textures_delta, + ); + + let screenshot_requested = + &mut integration.frame.output.screenshot_requested; + + if *screenshot_requested { + *screenshot_requested = false; + let screenshot = painter.read_screen_rgba(screen_size_in_pixels); + integration.frame.screenshot.set(Some(screenshot)); + } + + integration.post_rendering(app.as_mut(), window); + + { + crate::profile_scope!("swap_buffers"); + gl_window.swap_buffers().unwrap(); + } + + integration.post_present(window); + + #[cfg(feature = "__screenshot")] + // give it time to settle: + if integration.egui_ctx.frame_nr() == 2 { + if let Ok(path) = std::env::var("EFRAME_SCREENSHOT_TO") { + assert!( path.ends_with(".png"), "Expected EFRAME_SCREENSHOT_TO to end with '.png', got {path:?}" ); - let screenshot = painter.read_screen_rgba(screen_size_in_pixels); - image::save_buffer( - &path, - screenshot.as_raw(), - screenshot.width() as u32, - screenshot.height() as u32, - image::ColorType::Rgba8, - ) - .unwrap_or_else(|err| { - panic!("Failed to save screenshot to {path:?}: {err}"); - }); - eprintln!("Screenshot saved to {path:?}."); - std::process::exit(0); + let screenshot = painter.read_screen_rgba(screen_size_in_pixels); + image::save_buffer( + &path, + screenshot.as_raw(), + screenshot.width() as u32, + screenshot.height() as u32, + image::ColorType::Rgba8, + ) + .unwrap_or_else(|err| { + panic!("Failed to save screenshot to {path:?}: {err}"); + }); + eprintln!("Screenshot saved to {path:?}."); + std::process::exit(0); + } + } + + control_flow = if integration.should_close() { + EventResult::Exit + } else if repaint_after.is_zero() { + let mut id = None; + for window in gl_window.windows.iter() { + if window.window_id == 0 { + id = window.window.as_ref().map(|w| w.id()); + break; + } + } + EventResult::RepaintNext(id.unwrap()) + } else if let Some(repaint_after_instant) = + std::time::Instant::now().checked_add(repaint_after) + { + // if repaint_after is something huge and can't be added to Instant, + // we will use `ControlFlow::Wait` instead. + // technically, this might lead to some weird corner cases where the user *WANTS* + // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own + // egui backend impl i guess. + + let mut id = None; + for window in gl_window.windows.iter() { + if window.window_id == 0 { + id = window.window.as_ref().map(|w| w.id()); + break; + } + } + EventResult::RepaintAt(id.unwrap(), repaint_after_instant) + } else { + EventResult::Wait + }; + + integration.maybe_autosave(app.as_mut(), window); + + if window.is_minimized() == Some(true) { + // On Mac, a minimized Window uses up all CPU: + // https://github.com/emilk/egui/issues/325 + crate::profile_scope!("bg_sleep"); + std::thread::sleep(std::time::Duration::from_millis(10)); } } - let control_flow = if integration.should_close() { - EventResult::Exit - } else if repaint_after.is_zero() { - let mut id = None; - for window in gl_window.windows.iter() { - if window.window_id == 0 { - id = window.window.as_ref().map(|w| w.id()); - break; + let mut wins = vec![0]; + + windows.retain_mut(|(id, builder)| { + for w in gl_window.windows.iter_mut() { + if w.window_id == *id { + if w.builder != *builder { + if let Some(window) = &mut w.window { + if let Ok(pos) = window.outer_position() { + builder.position = Some((pos.x, pos.y)); + } + } + w.window = None; + w.gl_surface = None; + + w.builder = builder.clone(); + } + wins.push(*id); + return false; } } - EventResult::RepaintNext(id.unwrap()) - } else if let Some(repaint_after_instant) = - std::time::Instant::now().checked_add(repaint_after) - { - // if repaint_after is something huge and can't be added to Instant, - // we will use `ControlFlow::Wait` instead. - // technically, this might lead to some weird corner cases where the user *WANTS* - // winit to use `WaitUntil(MAX_INSTANT)` explicitly. they can roll their own - // egui backend impl i guess. + true + }); - let mut id = None; - for window in gl_window.windows.iter() { - if window.window_id == 0 { - id = window.window.as_ref().map(|w| w.id()); - break; - } - } - EventResult::RepaintAt(id.unwrap(), repaint_after_instant) - } else { - EventResult::Wait - }; - - integration.maybe_autosave(app.as_mut(), window); - - if window.is_minimized() == Some(true) { - // On Mac, a minimized Window uses up all CPU: - // https://github.com/emilk/egui/issues/325 - crate::profile_scope!("bg_sleep"); - std::thread::sleep(std::time::Duration::from_millis(10)); + for (id, builder) in windows { + gl_window.windows.push(Window { + builder, + gl_surface: None, + window: None, + window_id: id, + }); } + gl_window.windows.retain(|w| wins.contains(&w.window_id)); + gl_window.window_maps.retain(|_, id| wins.contains(id)); + control_flow }; @@ -1290,7 +1339,7 @@ mod wgpu_integration { let window_settings = epi_integration::load_window_settings(storage); let window_builder = epi_integration::window_builder(event_loop, title, native_options, window_settings); - let window = window_builder.build(event_loop)?; + let window = create_winit_window_builder(&window_builder).build(event_loop)?; epi_integration::apply_native_options_to_window(&window, native_options); Ok(window) } @@ -1709,3 +1758,48 @@ fn system_theme(window: &winit::window::Window, options: &NativeOptions) -> Opti fn extremely_far_future() -> std::time::Instant { std::time::Instant::now() + std::time::Duration::from_secs(10_000_000_000) } + +fn create_winit_window_builder(builder: &WindowBuilder) -> winit::window::WindowBuilder { + let mut window_builder = winit::window::WindowBuilder::new() + .with_title(builder.title.clone()) + .with_transparent(builder.transparent) + .with_decorations(builder.decorations) + .with_resizable(builder.resizable) + .with_visible(builder.visible) + .with_fullscreen( + builder + .fullscreen + .then(|| winit::window::Fullscreen::Borderless(None)), + ) + .with_active(builder.active); + if let Some(inner_size) = builder.inner_size { + window_builder = window_builder + .with_inner_size(winit::dpi::PhysicalSize::new(inner_size.0, inner_size.1)); + } + if let Some(min_inner_size) = builder.min_inner_size { + window_builder = window_builder.with_min_inner_size(winit::dpi::PhysicalSize::new( + min_inner_size.0, + min_inner_size.1, + )); + } + if let Some(max_inner_size) = builder.max_inner_size { + window_builder = window_builder.with_max_inner_size(winit::dpi::PhysicalSize::new( + max_inner_size.0, + max_inner_size.1, + )); + } + if let Some(position) = builder.position { + window_builder = + window_builder.with_position(winit::dpi::PhysicalPosition::new(position.0, position.1)); + } + + if let Some(icon) = builder.icon.clone() { + window_builder = window_builder.with_window_icon(load_icon(crate::IconData { + rgba: icon.2, + width: icon.0, + height: icon.1, + })) + } + + window_builder +} diff --git a/crates/egui-winit/src/window_settings.rs b/crates/egui-winit/src/window_settings.rs index 0137af22e..41ae107e1 100644 --- a/crates/egui-winit/src/window_settings.rs +++ b/crates/egui-winit/src/window_settings.rs @@ -1,3 +1,5 @@ +use egui::window::WindowBuilder; + /// Can be used to store native window settings (position and size). #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -46,32 +48,20 @@ impl WindowSettings { self.inner_size_points } - pub fn initialize_window( - &self, - mut window: winit::window::WindowBuilder, - ) -> winit::window::WindowBuilder { + pub fn initialize_window(&self, mut window: WindowBuilder) -> WindowBuilder { // If the app last ran on two monitors and only one is now connected, then // the given position is invalid. // If this happens on Mac, the window is clamped into valid area. // If this happens on Windows, the clamping behavior is managed by the function // clamp_window_to_sane_position. if let Some(pos) = self.position { - window = window.with_position(winit::dpi::PhysicalPosition { - x: pos.x as f64, - y: pos.y as f64, - }); + window = window.with_position((pos.x as i32, pos.y as i32)); } if let Some(inner_size_points) = self.inner_size_points { window - .with_inner_size(winit::dpi::LogicalSize { - width: inner_size_points.x as f64, - height: inner_size_points.y as f64, - }) - .with_fullscreen( - self.fullscreen - .then_some(winit::window::Fullscreen::Borderless(None)), - ) + .with_inner_size((inner_size_points.x as u32, inner_size_points.y as u32)) + .with_fullscreen(self.fullscreen) } else { window } diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index 53e8e7e2c..11ea886a6 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -10,7 +10,7 @@ pub mod panel; pub mod popup; pub(crate) mod resize; pub mod scroll_area; -pub(crate) mod window; +pub mod window; pub use { area::Area, diff --git a/crates/egui/src/containers/window.rs b/crates/egui/src/containers/window.rs index d5c3f680f..09e47b2b4 100644 --- a/crates/egui/src/containers/window.rs +++ b/crates/egui/src/containers/window.rs @@ -6,6 +6,115 @@ use epaint::*; use super::*; +#[derive(Hash, PartialEq, Clone, Default)] +pub struct WindowBuilder { + pub title: String, + pub name: Option, + pub position: Option<(i32, i32)>, + pub inner_size: Option<(u32, u32)>, + pub fullscreen: bool, + pub maximized: bool, + pub resizable: bool, + pub transparent: bool, + pub decorations: bool, + pub icon: Option<(u32, u32, Vec)>, + pub active: bool, + pub visible: bool, + pub title_hidden: bool, + pub titlebar_transparent: bool, + pub fullsize_content_view: bool, + pub min_inner_size: Option<(u32, u32)>, + pub max_inner_size: Option<(u32, u32)>, + pub drag_and_drop: bool, +} + +impl WindowBuilder { + pub fn with_title(mut self, title: impl Into) -> Self { + self.title = title.into(); + self + } + + pub fn with_decorations(mut self, decorations: bool) -> Self { + self.decorations = decorations; + self + } + + pub fn with_fullscreen(mut self, fullscreen: bool) -> Self { + self.fullscreen = fullscreen; + self + } + + pub fn with_maximized(mut self, maximized: bool) -> Self { + self.maximized = maximized; + self + } + + pub fn with_resizable(mut self, resizable: bool) -> Self { + self.resizable = resizable; + self + } + + pub fn with_transparent(mut self, transparent: bool) -> Self { + self.transparent = transparent; + self + } + + pub fn with_window_icon(mut self, icon: Option<(u32, u32, Vec)>) -> Self { + self.icon = icon; + self + } + + pub fn with_active(mut self, active: bool) -> Self { + self.active = active; + self + } + + pub fn with_visible(mut self, visible: bool) -> Self { + self.visible = visible; + self + } + + pub fn with_title_hidden(mut self, title_hidden: bool) -> Self { + self.title_hidden = title_hidden; + self + } + + pub fn with_titlebar_transparent(mut self, value: bool) -> Self { + self.titlebar_transparent = value; + self + } + + pub fn with_fullsize_content_view(mut self, value: bool) -> Self { + self.fullsize_content_view = value; + self + } + + pub fn with_inner_size(mut self, value: (u32, u32)) -> Self { + self.inner_size = Some(value); + self + } + + pub fn with_min_inner_size(mut self, value: (u32, u32)) -> Self { + self.min_inner_size = Some(value); + self + } + + pub fn with_max_inner_size(mut self, value: (u32, u32)) -> Self { + self.max_inner_size = Some(value); + self + } + + pub fn with_drag_and_drop(mut self, value: bool) -> Self { + self.drag_and_drop = value; + self + } + + pub fn with_position(mut self, value: (i32, i32)) -> Self { + self.position = Some(value); + self + } +} + /// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default). /// /// You can customize: @@ -287,6 +396,8 @@ impl<'open> Window<'open> { ctx: &Context, add_contents: Box R + 'c>, ) -> Option>> { + let window_id = + ctx.data_mut(|data| *data.get_temp_mut_or(self.area.id.with("window_id"), 0)); let Window { title, open, diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index bcb61d2c0..cbd9d1a9d 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -6,6 +6,7 @@ use crate::{ input_state::*, layers::GraphicLayers, memory::Options, os::OperatingSystem, output::FullOutput, util::IdTypeMap, TextureHandle, *, }; +use ahash::HashMap; use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *}; /// Information given to the backend about when it is time to repaint the ui. @@ -21,6 +22,9 @@ pub struct RequestRepaintInfo { /// This can be compared to [`Context::frame_nr`] to see if we've already /// triggered the painting of the next frame. pub current_frame_nr: u64, + + /// This is used to specify what window to redraw + pub window_id: u64, } // ---------------------------------------------------------------------------- @@ -80,11 +84,11 @@ impl Default for Repaint { } impl Repaint { - fn request_repaint(&mut self) { - self.request_repaint_after(std::time::Duration::ZERO); + fn request_repaint(&mut self, window_id: u64) { + self.request_repaint_after(std::time::Duration::ZERO, window_id); } - fn request_repaint_after(&mut self, after: std::time::Duration) { + fn request_repaint_after(&mut self, after: std::time::Duration, window_id: u64) { if after == std::time::Duration::ZERO { // Do a few extra frames to let things settle. // This is a bit of a hack, and we don't support it for `repaint_after` callbacks yet. @@ -100,6 +104,7 @@ impl Repaint { let info = RequestRepaintInfo { after, current_frame_nr: self.frame_nr, + window_id: window_id, }; (callback)(info); } @@ -155,6 +160,9 @@ struct ContextImpl { repaint: Repaint, + windows: HashMap, + current_window_id: u64, + /// Written to during the frame. layer_rects_this_frame: ahash::HashMap>, @@ -956,7 +964,7 @@ impl Context { /// (this will work on `eframe`). pub fn request_repaint(&self) { // request two frames of repaint, just to cover some corner cases (frame delays): - self.write(|ctx| ctx.repaint.request_repaint()); + self.write(|ctx| ctx.repaint.request_repaint(ctx.current_window_id)); } /// Request repaint after at most the specified duration elapses. @@ -988,9 +996,9 @@ impl Context { /// timeout takes 500 milliseconds AFTER the vsync swap buffer. /// So, its not that we are requesting repaint within X duration. We are rather timing out /// during app idle time where we are not receiving any new input events. - pub fn request_repaint_after(&self, duration: std::time::Duration) { + pub fn request_repaint_after(&self, duration: std::time::Duration, window_id: u64) { // Maybe we can check if duration is ZERO, and call self.request_repaint()? - self.write(|ctx| ctx.repaint.request_repaint_after(duration)); + self.write(|ctx| ctx.repaint.request_repaint_after(duration, window_id)); } /// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`]. @@ -1260,11 +1268,19 @@ impl Context { let repaint_after = self.write(|ctx| ctx.repaint.end_frame()); let shapes = self.drain_paint_lists(); + let windows = self.write(|ctx| { + ctx.windows + .drain() + .map(|(_, (builder, id, _))| (id, builder)) + .collect() + }); + FullOutput { platform_output, repaint_after, textures_delta, shapes, + windows, } } @@ -1869,6 +1885,30 @@ impl Context { } } +use containers::window::WindowBuilder; +/// # Windows +impl Context { + pub fn set_current_window_id(&self, window_id: u64) { + self.write(|ctx| ctx.current_window_id = window_id); + } + pub fn current_window_id(&self) -> u64 { + self.read(|ctx| ctx.current_window_id) + } + + pub fn create_window(&self, window_builder: WindowBuilder) -> u64 { + self.write(|ctx| { + if let Some(window) = ctx.windows.get(&window_builder.title) { + window.1 + } else { + let id = ctx.windows.len() as u64 + 1; + ctx.windows + .insert(window_builder.title.clone(), (window_builder, id, true)); + id + } + }) + } +} + #[test] fn context_impl_send_sync() { fn assert_send_sync() {} diff --git a/crates/egui/src/data/output.rs b/crates/egui/src/data/output.rs index cea031ed0..c9c1d16d3 100644 --- a/crates/egui/src/data/output.rs +++ b/crates/egui/src/data/output.rs @@ -1,6 +1,6 @@ //! All the data egui returns to the backend at the end of each frame. -use crate::WidgetType; +use crate::{window::WindowBuilder, WidgetType}; /// What egui emits each frame from [`crate::Context::run`]. /// @@ -30,6 +30,8 @@ pub struct FullOutput { /// /// You can use [`crate::Context::tessellate`] to turn this into triangles. pub shapes: Vec, + + pub windows: Vec<(u64, WindowBuilder)>, } impl FullOutput { @@ -40,12 +42,14 @@ impl FullOutput { repaint_after, textures_delta, shapes, + mut windows, } = newer; self.platform_output.append(platform_output); self.repaint_after = repaint_after; // if the last frame doesn't need a repaint, then we don't need to repaint self.textures_delta.append(textures_delta); self.shapes = shapes; // Only paint the latest + self.windows.append(&mut windows); } } diff --git a/crates/egui_demo_app/src/backend_panel.rs b/crates/egui_demo_app/src/backend_panel.rs index 33d1cc630..c1e1fc4ad 100644 --- a/crates/egui_demo_app/src/backend_panel.rs +++ b/crates/egui_demo_app/src/backend_panel.rs @@ -281,7 +281,10 @@ impl BackendPanel { let ctx = ui.ctx().clone(); call_after_delay(std::time::Duration::from_secs(2), move || { log::info!("Request a repaint in 3s..."); - ctx.request_repaint_after(std::time::Duration::from_secs(3)); + ctx.request_repaint_after( + std::time::Duration::from_secs(3), + ctx.current_window_id(), + ); }); } }); diff --git a/examples/user_attention/src/main.rs b/examples/user_attention/src/main.rs index 4f7e521d2..736015623 100644 --- a/examples/user_attention/src/main.rs +++ b/examples/user_attention/src/main.rs @@ -128,6 +128,6 @@ impl eframe::App for Application { }); }); - ctx.request_repaint_after(Self::repaint_max_timeout()); + ctx.request_repaint_after(Self::repaint_max_timeout(), ctx.current_window_id()); } }