mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 15:13:12 -04:00
Some more work for multiples windows support
This commit is contained in:
@@ -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<E>(
|
||||
title: &str,
|
||||
native_options: &epi::NativeOptions,
|
||||
window_settings: Option<WindowSettings>,
|
||||
) -> winit::window::WindowBuilder {
|
||||
) -> WindowBuilder {
|
||||
let epi::NativeOptions {
|
||||
maximized,
|
||||
decorated,
|
||||
@@ -97,16 +97,14 @@ pub fn window_builder<E>(
|
||||
..
|
||||
} = 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<E>(
|
||||
}
|
||||
|
||||
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<E>(
|
||||
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<E>(
|
||||
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<E>(event_loop: &EventLoopWindowTarget<E>) -> egui:
|
||||
}
|
||||
}
|
||||
|
||||
fn load_icon(icon_data: epi::IconData) -> Option<winit::window::Icon> {
|
||||
pub fn load_icon(icon_data: epi::IconData) -> Option<winit::window::Icon> {
|
||||
winit::window::Icon::from_rgba(icon_data.rgba, icon_data.width, icon_data.height).ok()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<UserEvent>, 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<glutin::surface::Surface<glutin::surface::WindowSurface>>,
|
||||
window: Option<winit::window::Window>,
|
||||
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<UserEvent>,
|
||||
) -> Result<Self> {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -6,6 +6,115 @@ use epaint::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Hash, PartialEq, Clone, Default)]
|
||||
pub struct WindowBuilder {
|
||||
pub title: String,
|
||||
pub name: Option<String>,
|
||||
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<u8>)>,
|
||||
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<String>) -> 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<u8>)>) -> 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<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||
) -> Option<InnerResponse<Option<R>>> {
|
||||
let window_id =
|
||||
ctx.data_mut(|data| *data.get_temp_mut_or(self.area.id.with("window_id"), 0));
|
||||
let Window {
|
||||
title,
|
||||
open,
|
||||
|
||||
@@ -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<String, (WindowBuilder, u64, bool)>,
|
||||
current_window_id: u64,
|
||||
|
||||
/// Written to during the frame.
|
||||
layer_rects_this_frame: ahash::HashMap<LayerId, Vec<(Id, Rect)>>,
|
||||
|
||||
@@ -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<T: Send + Sync>() {}
|
||||
|
||||
@@ -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<epaint::ClippedShape>,
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user