mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 22:30:03 -04:00
Improve multi-viewports across monitors with different scales (#3663)
It required some ugly code in `egui-winit` in order to fix this without breaking sem-ver (I want this to land in a 0.24.1 patch release). I'll clean up in time for 0.25. There is still a font rendering bug when using immediate viewports across multiple viewports, but that's harder to fix.
This commit is contained in:
@@ -38,6 +38,13 @@ pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
|
||||
egui::vec2(size.width as f32, size.height as f32)
|
||||
}
|
||||
|
||||
/// Calculate the `pixels_per_point` for a given window, given the current egui zoom factor
|
||||
pub fn pixels_per_point(egui_ctx: &egui::Context, window: &Window) -> f32 {
|
||||
let native_pixels_per_point = window.scale_factor() as f32;
|
||||
let egui_zoom_factor = egui_ctx.zoom_factor();
|
||||
egui_zoom_factor * native_pixels_per_point
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[must_use]
|
||||
@@ -70,7 +77,7 @@ pub struct State {
|
||||
current_cursor_icon: Option<egui::CursorIcon>,
|
||||
|
||||
/// What egui uses.
|
||||
current_pixels_per_point: f32,
|
||||
current_pixels_per_point: f32, // TODO: remove - calculate with [`pixels_per_point`] instead
|
||||
|
||||
clipboard: clipboard::Clipboard,
|
||||
|
||||
@@ -164,9 +171,8 @@ impl State {
|
||||
self.egui_input.max_texture_side = Some(max_texture_side);
|
||||
}
|
||||
|
||||
/// The number of physical pixels per logical point,
|
||||
/// as configured on the current egui context (see [`egui::Context::pixels_per_point`]).
|
||||
#[inline]
|
||||
#[deprecated = "Use egui_winit::pixels_per_point instead"]
|
||||
pub fn pixels_per_point(&self) -> f32 {
|
||||
self.current_pixels_per_point
|
||||
}
|
||||
@@ -186,10 +192,9 @@ impl State {
|
||||
}
|
||||
|
||||
/// Update the given viewport info with the current state of the window.
|
||||
///
|
||||
/// Call before [`Self::update_viewport_info`]
|
||||
#[deprecated = "Use egui_winit::update_viewport_info instead"]
|
||||
pub fn update_viewport_info(&self, info: &mut ViewportInfo, window: &Window) {
|
||||
update_viewport_info(info, window, self.current_pixels_per_point);
|
||||
update_viewport_info_impl(info, window, self.current_pixels_per_point);
|
||||
}
|
||||
|
||||
/// Prepare for a new frame by extracting the accumulated input,
|
||||
@@ -226,6 +231,12 @@ impl State {
|
||||
self.egui_input.take()
|
||||
}
|
||||
|
||||
// TODO(emilk): remove asap.
|
||||
#[doc(hidden)]
|
||||
pub fn update_pixels_per_point(&mut self, egui_ctx: &egui::Context, window: &Window) {
|
||||
self.current_pixels_per_point = pixels_per_point(egui_ctx, window);
|
||||
}
|
||||
|
||||
/// Call this when there is a new event.
|
||||
///
|
||||
/// The result can be found in [`Self::egui_input`] and be extracted with [`Self::take_egui_input`].
|
||||
@@ -511,8 +522,8 @@ impl State {
|
||||
|
||||
fn on_cursor_moved(&mut self, pos_in_pixels: winit::dpi::PhysicalPosition<f64>) {
|
||||
let pos_in_points = egui::pos2(
|
||||
pos_in_pixels.x as f32 / self.pixels_per_point(),
|
||||
pos_in_pixels.y as f32 / self.pixels_per_point(),
|
||||
pos_in_pixels.x as f32 / self.current_pixels_per_point,
|
||||
pos_in_pixels.y as f32 / self.current_pixels_per_point,
|
||||
);
|
||||
self.pointer_pos_in_points = Some(pos_in_points);
|
||||
|
||||
@@ -549,8 +560,8 @@ impl State {
|
||||
winit::event::TouchPhase::Cancelled => egui::TouchPhase::Cancel,
|
||||
},
|
||||
pos: egui::pos2(
|
||||
touch.location.x as f32 / self.pixels_per_point(),
|
||||
touch.location.y as f32 / self.pixels_per_point(),
|
||||
touch.location.x as f32 / self.current_pixels_per_point,
|
||||
touch.location.y as f32 / self.current_pixels_per_point,
|
||||
),
|
||||
force: match touch.force {
|
||||
Some(winit::event::Force::Normalized(force)) => Some(force as f32),
|
||||
@@ -610,7 +621,7 @@ impl State {
|
||||
y,
|
||||
}) => (
|
||||
egui::MouseWheelUnit::Point,
|
||||
egui::vec2(x as f32, y as f32) / self.pixels_per_point(),
|
||||
egui::vec2(x as f32, y as f32) / self.current_pixels_per_point,
|
||||
),
|
||||
};
|
||||
let modifiers = self.egui_input.modifiers;
|
||||
@@ -626,7 +637,7 @@ impl State {
|
||||
egui::vec2(x, y) * points_per_scroll_line
|
||||
}
|
||||
winit::event::MouseScrollDelta::PixelDelta(delta) => {
|
||||
egui::vec2(delta.x as f32, delta.y as f32) / self.pixels_per_point()
|
||||
egui::vec2(delta.x as f32, delta.y as f32) / self.current_pixels_per_point
|
||||
}
|
||||
};
|
||||
|
||||
@@ -758,7 +769,18 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_viewport_info(viewport_info: &mut ViewportInfo, window: &Window, pixels_per_point: f32) {
|
||||
/// Update the given viewport info with the current state of the window.
|
||||
///
|
||||
/// Call before [`State::take_egui_input`].
|
||||
pub fn update_viewport_info(info: &mut ViewportInfo, egui_ctx: &egui::Context, window: &Window) {
|
||||
update_viewport_info_impl(info, window, pixels_per_point(egui_ctx, window));
|
||||
}
|
||||
|
||||
fn update_viewport_info_impl(
|
||||
viewport_info: &mut ViewportInfo,
|
||||
window: &Window,
|
||||
pixels_per_point: f32,
|
||||
) {
|
||||
crate::profile_function!();
|
||||
|
||||
let has_a_position = match window.is_minimized() {
|
||||
@@ -1072,8 +1094,7 @@ fn process_viewport_command(
|
||||
|
||||
log::debug!("Processing ViewportCommand::{command:?}");
|
||||
|
||||
let egui_zoom_factor = egui_ctx.zoom_factor();
|
||||
let pixels_per_point = egui_zoom_factor * window.scale_factor() as f32;
|
||||
let pixels_per_point = pixels_per_point(egui_ctx, window);
|
||||
|
||||
match command {
|
||||
ViewportCommand::Close => {
|
||||
@@ -1244,6 +1265,26 @@ fn process_viewport_command(
|
||||
}
|
||||
}
|
||||
|
||||
/// Build and intitlaize a window.
|
||||
///
|
||||
/// Wrapper around `create_winit_window_builder` and `apply_viewport_builder_to_window`.
|
||||
pub fn create_window<T>(
|
||||
egui_ctx: &egui::Context,
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
viewport_builder: &ViewportBuilder,
|
||||
) -> Result<Window, winit::error::OsError> {
|
||||
crate::profile_function!();
|
||||
|
||||
let window_builder =
|
||||
create_winit_window_builder(egui_ctx, event_loop, viewport_builder.clone());
|
||||
let window = {
|
||||
crate::profile_scope!("WindowBuilder::build");
|
||||
window_builder.build(event_loop)?
|
||||
};
|
||||
apply_viewport_builder_to_window(egui_ctx, &window, viewport_builder);
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
pub fn create_winit_window_builder<T>(
|
||||
egui_ctx: &egui::Context,
|
||||
event_loop: &EventLoopWindowTarget<T>,
|
||||
@@ -1253,6 +1294,8 @@ pub fn create_winit_window_builder<T>(
|
||||
|
||||
// We set sizes and positions in egui:s own ui points, which depends on the egui
|
||||
// zoom_factor and the native pixels per point, so we need to know that here.
|
||||
// We don't know what monitor the window will appear on though, but
|
||||
// we'll try to fix that after the window is created in the vall to `apply_viewport_builder_to_window`.
|
||||
let native_pixels_per_point = event_loop
|
||||
.primary_monitor()
|
||||
.or_else(|| event_loop.available_monitors().next())
|
||||
@@ -1297,7 +1340,7 @@ pub fn create_winit_window_builder<T>(
|
||||
// wayland:
|
||||
app_id: _app_id,
|
||||
|
||||
mouse_passthrough: _, // handled in `apply_viewport_builder_to_new_window`
|
||||
mouse_passthrough: _, // handled in `apply_viewport_builder_to_window`
|
||||
} = viewport_builder;
|
||||
|
||||
let mut window_builder = winit::window::WindowBuilder::new()
|
||||
@@ -1330,31 +1373,31 @@ pub fn create_winit_window_builder<T>(
|
||||
})
|
||||
.with_active(active.unwrap_or(true));
|
||||
|
||||
if let Some(inner_size) = inner_size {
|
||||
if let Some(size) = inner_size {
|
||||
window_builder = window_builder.with_inner_size(PhysicalSize::new(
|
||||
pixels_per_point * inner_size.x,
|
||||
pixels_per_point * inner_size.y,
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(min_inner_size) = min_inner_size {
|
||||
if let Some(size) = min_inner_size {
|
||||
window_builder = window_builder.with_min_inner_size(PhysicalSize::new(
|
||||
pixels_per_point * min_inner_size.x,
|
||||
pixels_per_point * min_inner_size.y,
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(max_inner_size) = max_inner_size {
|
||||
if let Some(size) = max_inner_size {
|
||||
window_builder = window_builder.with_max_inner_size(PhysicalSize::new(
|
||||
pixels_per_point * max_inner_size.x,
|
||||
pixels_per_point * max_inner_size.y,
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(position) = position {
|
||||
if let Some(pos) = position {
|
||||
window_builder = window_builder.with_position(PhysicalPosition::new(
|
||||
pixels_per_point * position.x,
|
||||
pixels_per_point * position.y,
|
||||
pixels_per_point * pos.x,
|
||||
pixels_per_point * pos.y,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1391,6 +1434,7 @@ pub fn create_winit_window_builder<T>(
|
||||
}
|
||||
|
||||
/// Applies what `create_winit_window_builder` couldn't
|
||||
#[deprecated = "Use apply_viewport_builder_to_window instead"]
|
||||
pub fn apply_viewport_builder_to_new_window(window: &Window, builder: &ViewportBuilder) {
|
||||
if let Some(mouse_passthrough) = builder.mouse_passthrough {
|
||||
if let Err(err) = window.set_cursor_hittest(!mouse_passthrough) {
|
||||
@@ -1399,6 +1443,51 @@ pub fn apply_viewport_builder_to_new_window(window: &Window, builder: &ViewportB
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies what `create_winit_window_builder` couldn't
|
||||
pub fn apply_viewport_builder_to_window(
|
||||
egui_ctx: &egui::Context,
|
||||
window: &Window,
|
||||
builder: &ViewportBuilder,
|
||||
) {
|
||||
if let Some(mouse_passthrough) = builder.mouse_passthrough {
|
||||
if let Err(err) = window.set_cursor_hittest(!mouse_passthrough) {
|
||||
log::warn!("set_cursor_hittest failed: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// In `create_winit_window_builder` we didn't know
|
||||
// on what monitor the window would appear, so we didn't know
|
||||
// how to translate egui ui point to native physical pixels.
|
||||
// Now we do know:
|
||||
|
||||
let pixels_per_point = pixels_per_point(egui_ctx, window);
|
||||
|
||||
if let Some(size) = builder.inner_size {
|
||||
window.set_inner_size(PhysicalSize::new(
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
));
|
||||
}
|
||||
if let Some(size) = builder.min_inner_size {
|
||||
window.set_min_inner_size(Some(PhysicalSize::new(
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
)));
|
||||
}
|
||||
if let Some(size) = builder.max_inner_size {
|
||||
window.set_max_inner_size(Some(PhysicalSize::new(
|
||||
pixels_per_point * size.x,
|
||||
pixels_per_point * size.y,
|
||||
)));
|
||||
}
|
||||
if let Some(pos) = builder.position {
|
||||
let pos = PhysicalPosition::new(pixels_per_point * pos.x, pixels_per_point * pos.y);
|
||||
window.set_outer_position(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Short and fast description of an event.
|
||||
|
||||
Reference in New Issue
Block a user