mirror of
https://github.com/emilk/egui.git
synced 2026-06-28 07:23:13 -04:00
* Closes https://github.com/emilk/egui/issues/1918 * Closes https://github.com/emilk/egui/issues/4437 * Closes https://github.com/emilk/egui/issues/4709 * [x] I have followed the instructions in the PR template Hiya, I need new winit for a specific fix for a android_native_actvity. There are already two PRs, but both don't seem to have a lot of movement, or are entirely complete: https://github.com/emilk/egui/pull/4466 Seems to have gone stale & is missing some bits. https://github.com/emilk/egui/pull/4702 Also seems stale (if less so), and is missing a refactor to run_on_demand. I also *think* the accesskit integration has a mistake and can't be enabled. I've marked them as a co-author on this as I started from this branch. (I think! Haven't done that on git before...). Sorry for the wall of text but just dumping some details / thoughts here: - There's an issue with creating child windows in winit 0.30.1 and up on macOS. The multiple_viewports, "create immediate viewport" example crashes on anything later 0.30.1, with a stack overflow in unsafe code. I've create [a winit issue](https://github.com/rust-windowing/winit/issues/3800), it *might* already be fixed in 0.31.0 but I can't test as 0.31 will likely require another refactoring. For now I have just pinned things to 0.30.0 exatly. - Winit has deprecated run_on_demand, instead requiring the ApplicationHandler interface. In 0.31.0 run_on_demand is removed. I've refactored both the integration and the WinitApp trait to follow this pattern. I've left user_events a bit more opaque, as it seems 0.31.0 is doing a rework of UserEvents too. - I've used the new lazy init approach for access kit from this branch https://github.com/mwcampbell/egui/tree/accesskit-new-lazy-init and marked Matt as co-author, thanks Matt! - There was very similair but not quite the same code for run_and_return and run_and_exit. I've merged them, but looking at the github issues graveyard it seems vey finnicky. I *hope* this is more robust than before but it's a bit scary. - when receiving new_events this also used to check the redraw timing dictionary. That doesn't seem necesarry so left this out, but that is a slight behaviour change? - I have reeneabled serial_windows on macOS. I wondered whether it was fixed after this PR and does seem to be! However, even before this PR it seems to work, so maybe winit has sorted things out before that... Windows also works fine now without the extra hack. - I've done a very basic test of AccessKit on Windows and screen reader seems ok but I'm really not knowleadgable enough to say whether it's all good or not. - I've tested cargo tests & all examples on Windows & macOS, and ran a basic Android app. Still, testing native platforms is wel... hard so if anyone can test linux / iOs / older mac versions / windows 10 would probably be a good idea! - For consistencys sake I've made all event like functions in WinitApp return a `Result<EventResult>`. There's quite a bit of Ok-wrapping now, maybe too annoying? Not sure. Thank you for having a look! # Tested on * [x] macOS * [x] Windows * [x] Wayland (thanks [SiebenCorgie](https://github.com/SiebenCorgie)) * [x] X11 (thanks [crumblingstatue](https://github.com/crumblingstatue)!, [SiebenCorgie](https://github.com/SiebenCorgie)) # TODO * [x] Fix "follow system theme" not working on initial startup (winit issue, pinning to 0.30.2 for now). * [x] Fix `request_repaint_after` --------- Co-authored-by: mwcampbell <mattcampbell@pobox.com> Co-authored-by: j-axa <josef.axa@gmail.com> Co-authored-by: DataTriny <datatriny@gmail.com> Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
214 lines
7.5 KiB
Rust
214 lines
7.5 KiB
Rust
use egui::ViewportBuilder;
|
|
|
|
/// Can be used to store native window settings (position and size).
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
pub struct WindowSettings {
|
|
/// Position of window content in physical pixels.
|
|
inner_position_pixels: Option<egui::Pos2>,
|
|
|
|
/// Position of window frame/titlebar in physical pixels.
|
|
outer_position_pixels: Option<egui::Pos2>,
|
|
|
|
fullscreen: bool,
|
|
|
|
/// Inner size of window in logical pixels
|
|
inner_size_points: Option<egui::Vec2>,
|
|
}
|
|
|
|
impl WindowSettings {
|
|
pub fn from_window(egui_zoom_factor: f32, window: &winit::window::Window) -> Self {
|
|
let inner_size_points = window
|
|
.inner_size()
|
|
.to_logical::<f32>(egui_zoom_factor as f64 * window.scale_factor());
|
|
|
|
let inner_position_pixels = window
|
|
.inner_position()
|
|
.ok()
|
|
.map(|p| egui::pos2(p.x as f32, p.y as f32));
|
|
|
|
let outer_position_pixels = window
|
|
.outer_position()
|
|
.ok()
|
|
.map(|p| egui::pos2(p.x as f32, p.y as f32));
|
|
|
|
Self {
|
|
inner_position_pixels,
|
|
outer_position_pixels,
|
|
|
|
fullscreen: window.fullscreen().is_some(),
|
|
|
|
inner_size_points: Some(egui::vec2(
|
|
inner_size_points.width,
|
|
inner_size_points.height,
|
|
)),
|
|
}
|
|
}
|
|
|
|
pub fn inner_size_points(&self) -> Option<egui::Vec2> {
|
|
self.inner_size_points
|
|
}
|
|
|
|
pub fn initialize_viewport_builder(
|
|
&self,
|
|
egui_zoom_factor: f32,
|
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
mut viewport_builder: ViewportBuilder,
|
|
) -> ViewportBuilder {
|
|
crate::profile_function!();
|
|
|
|
// `WindowBuilder::with_position` expects inner position in Macos, and outer position elsewhere
|
|
// See [`winit::window::WindowBuilder::with_position`] for details.
|
|
let pos_px = if cfg!(target_os = "macos") {
|
|
self.inner_position_pixels
|
|
} else {
|
|
self.outer_position_pixels
|
|
};
|
|
if let Some(pos) = pos_px {
|
|
let monitor_scale_factor = if let Some(inner_size_points) = self.inner_size_points {
|
|
find_active_monitor(egui_zoom_factor, event_loop, inner_size_points, &pos)
|
|
.map_or(1.0, |monitor| monitor.scale_factor() as f32)
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
let scaled_pos = pos / (egui_zoom_factor * monitor_scale_factor);
|
|
viewport_builder = viewport_builder.with_position(scaled_pos);
|
|
}
|
|
|
|
if let Some(inner_size_points) = self.inner_size_points {
|
|
viewport_builder = viewport_builder
|
|
.with_inner_size(inner_size_points)
|
|
.with_fullscreen(self.fullscreen);
|
|
}
|
|
|
|
viewport_builder
|
|
}
|
|
|
|
pub fn initialize_window(&self, window: &winit::window::Window) {
|
|
if cfg!(target_os = "macos") {
|
|
// Mac sometimes has problems restoring the window to secondary monitors
|
|
// using only `WindowBuilder::with_position`, so we need this extra step:
|
|
if let Some(pos) = self.outer_position_pixels {
|
|
window.set_outer_position(winit::dpi::PhysicalPosition { x: pos.x, y: pos.y });
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn clamp_size_to_sane_values(&mut self, largest_monitor_size_points: egui::Vec2) {
|
|
use egui::NumExt as _;
|
|
|
|
if let Some(size) = &mut self.inner_size_points {
|
|
// Prevent ridiculously small windows:
|
|
let min_size = egui::Vec2::splat(64.0);
|
|
*size = size.at_least(min_size);
|
|
|
|
// Make sure we don't try to create a window larger than the largest monitor
|
|
// because on Linux that can lead to a crash.
|
|
*size = size.at_most(largest_monitor_size_points);
|
|
}
|
|
}
|
|
|
|
pub fn clamp_position_to_monitors(
|
|
&mut self,
|
|
egui_zoom_factor: f32,
|
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
) {
|
|
// 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 window becomes invisible to the user 🤦♂️
|
|
// So on Windows we clamp the position to the monitor it is on.
|
|
if !cfg!(target_os = "windows") {
|
|
return;
|
|
}
|
|
|
|
let Some(inner_size_points) = self.inner_size_points else {
|
|
return;
|
|
};
|
|
|
|
if let Some(pos_px) = &mut self.inner_position_pixels {
|
|
clamp_pos_to_monitors(egui_zoom_factor, event_loop, inner_size_points, pos_px);
|
|
}
|
|
if let Some(pos_px) = &mut self.outer_position_pixels {
|
|
clamp_pos_to_monitors(egui_zoom_factor, event_loop, inner_size_points, pos_px);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn find_active_monitor(
|
|
egui_zoom_factor: f32,
|
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
window_size_pts: egui::Vec2,
|
|
position_px: &egui::Pos2,
|
|
) -> Option<winit::monitor::MonitorHandle> {
|
|
crate::profile_function!();
|
|
|
|
let monitors = event_loop.available_monitors();
|
|
|
|
// default to primary monitor, in case the correct monitor was disconnected.
|
|
let Some(mut active_monitor) = event_loop
|
|
.primary_monitor()
|
|
.or_else(|| event_loop.available_monitors().next())
|
|
else {
|
|
return None; // no monitors 🤷
|
|
};
|
|
|
|
for monitor in monitors {
|
|
let window_size_px = window_size_pts * (egui_zoom_factor * monitor.scale_factor() as f32);
|
|
let monitor_x_range = (monitor.position().x - window_size_px.x as i32)
|
|
..(monitor.position().x + monitor.size().width as i32);
|
|
let monitor_y_range = (monitor.position().y - window_size_px.y as i32)
|
|
..(monitor.position().y + monitor.size().height as i32);
|
|
|
|
if monitor_x_range.contains(&(position_px.x as i32))
|
|
&& monitor_y_range.contains(&(position_px.y as i32))
|
|
{
|
|
active_monitor = monitor;
|
|
}
|
|
}
|
|
|
|
Some(active_monitor)
|
|
}
|
|
|
|
fn clamp_pos_to_monitors(
|
|
egui_zoom_factor: f32,
|
|
event_loop: &winit::event_loop::ActiveEventLoop,
|
|
window_size_pts: egui::Vec2,
|
|
position_px: &mut egui::Pos2,
|
|
) {
|
|
crate::profile_function!();
|
|
|
|
let Some(active_monitor) =
|
|
find_active_monitor(egui_zoom_factor, event_loop, window_size_pts, position_px)
|
|
else {
|
|
return; // no monitors 🤷
|
|
};
|
|
|
|
let mut window_size_px =
|
|
window_size_pts * (egui_zoom_factor * active_monitor.scale_factor() as f32);
|
|
// Add size of title bar. This is 32 px by default in Win 10/11.
|
|
if cfg!(target_os = "windows") {
|
|
window_size_px += egui::Vec2::new(
|
|
0.0,
|
|
32.0 * egui_zoom_factor * active_monitor.scale_factor() as f32,
|
|
);
|
|
}
|
|
let monitor_position = egui::Pos2::new(
|
|
active_monitor.position().x as f32,
|
|
active_monitor.position().y as f32,
|
|
);
|
|
let monitor_size_px = egui::Vec2::new(
|
|
active_monitor.size().width as f32,
|
|
active_monitor.size().height as f32,
|
|
);
|
|
|
|
// Window size cannot be negative or the subsequent `clamp` will panic.
|
|
let window_size = (monitor_size_px - window_size_px).max(egui::Vec2::ZERO);
|
|
// To get the maximum position, we get the rightmost corner of the display, then
|
|
// subtract the size of the window to get the bottom right most value window.position
|
|
// can have.
|
|
*position_px = position_px.clamp(monitor_position, monitor_position + window_size);
|
|
}
|