Introduce global `zoom_factor` (#3608)

* Closes https://github.com/emilk/egui/issues/3602

You can now zoom any egui app by pressing Cmd+Plus, Cmd+Minus or Cmd+0,
just like in a browser. This will change the current `zoom_factor`
(default 1.0) which is persisted in the egui memory, and is the same for
all viewports.
You can turn off the keyboard shortcuts with `ctx.options_mut(|o|
o.zoom_with_keyboard = false);`

`zoom_factor` can also be explicitly read/written with
`ctx.zoom_factor()` and `ctx.set_zoom_factor()`.

This redefines `pixels_per_point` as `zoom_factor *
native_pixels_per_point`, where `native_pixels_per_point` is whatever is
the native scale factor for the monitor that the current viewport is in.

This adds some complexity to the interaction with winit, since we need
to know the current `zoom_factor` in a lot of places, because all egui
IO is done in ui points. I'm pretty sure this PR fixes a bunch of subtle
bugs though that used to be in this code.

`egui::gui_zoom::zoom_with_keyboard_shortcuts` is now gone, and is no
longer needed, as this is now the default behavior.

`Context::set_pixels_per_point` is still there, but it is recommended
you use `Context::set_zoom_factor` instead.
This commit is contained in:
Emil Ernerfeldt 2023-11-22 20:34:51 +01:00 committed by GitHub
parent ea53246c60
commit 63e48dc855
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 750 additions and 579 deletions

2
Cargo.lock generated
View File

@ -1317,6 +1317,8 @@ dependencies = [
"image",
"log",
"poll-promise",
"puffin",
"puffin_http",
"rfd",
"serde",
"wasm-bindgen",

View File

@ -60,6 +60,9 @@ disallowed-types = [
# "std::sync::Once", # enabled for now as the `log_once` macro uses it internally
"ring::digest::SHA1_FOR_LEGACY_USE_ONLY", # SHA1 is cryptographically broken
"winit::dpi::LogicalSize", # We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account
"winit::dpi::LogicalPosition", # We do our own pixels<->point conversion, taking `egui_ctx.zoom_factor` into account
]
# -----------------------------------------------------------------------------

View File

@ -12,6 +12,7 @@ use egui_winit::{EventResponse, WindowSettings};
use crate::{epi, Theme};
pub fn viewport_builder<E>(
egui_zoom_factor: f32,
event_loop: &EventLoopWindowTarget<E>,
native_options: &mut epi::NativeOptions,
window_settings: Option<WindowSettings>,
@ -26,8 +27,9 @@ pub fn viewport_builder<E>(
let inner_size_points = if let Some(mut window_settings) = window_settings {
// Restore pos/size from previous session
window_settings.clamp_size_to_sane_values(largest_monitor_point_size(event_loop));
window_settings.clamp_position_to_monitors(event_loop);
window_settings
.clamp_size_to_sane_values(largest_monitor_point_size(egui_zoom_factor, event_loop));
window_settings.clamp_position_to_monitors(egui_zoom_factor, event_loop);
viewport_builder = window_settings.initialize_viewport_builder(viewport_builder);
window_settings.inner_size_points()
@ -37,8 +39,8 @@ pub fn viewport_builder<E>(
}
if let Some(initial_window_size) = viewport_builder.inner_size {
let initial_window_size =
initial_window_size.at_most(largest_monitor_point_size(event_loop));
let initial_window_size = initial_window_size
.at_most(largest_monitor_point_size(egui_zoom_factor, event_loop));
viewport_builder = viewport_builder.with_inner_size(initial_window_size);
}
@ -49,9 +51,11 @@ pub fn viewport_builder<E>(
if native_options.centered {
crate::profile_scope!("center");
if let Some(monitor) = event_loop.available_monitors().next() {
let monitor_size = monitor.size().to_logical::<f32>(monitor.scale_factor());
let monitor_size = monitor
.size()
.to_logical::<f32>(egui_zoom_factor as f64 * monitor.scale_factor());
let inner_size = inner_size_points.unwrap_or(egui::Vec2 { x: 800.0, y: 600.0 });
if monitor_size.width > 0.0 && monitor_size.height > 0.0 {
if 0.0 < monitor_size.width && 0.0 < monitor_size.height {
let x = (monitor_size.width - inner_size.x) / 2.0;
let y = (monitor_size.height - inner_size.y) / 2.0;
viewport_builder = viewport_builder.with_position([x, y]);
@ -76,7 +80,10 @@ pub fn apply_window_settings(
}
}
fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui::Vec2 {
fn largest_monitor_point_size<E>(
egui_zoom_factor: f32,
event_loop: &EventLoopWindowTarget<E>,
) -> egui::Vec2 {
crate::profile_function!();
let mut max_size = egui::Vec2::ZERO;
@ -87,7 +94,9 @@ fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui:
};
for monitor in available_monitors {
let size = monitor.size().to_logical::<f32>(monitor.scale_factor());
let size = monitor
.size()
.to_logical::<f32>(egui_zoom_factor as f64 * monitor.scale_factor());
let size = egui::vec2(size.width, size.height);
max_size = max_size.max(size);
}
@ -137,21 +146,15 @@ pub struct EpiIntegration {
impl EpiIntegration {
#[allow(clippy::too_many_arguments)]
pub fn new(
egui_ctx: egui::Context,
window: &winit::window::Window,
system_theme: Option<Theme>,
app_name: &str,
native_options: &crate::NativeOptions,
storage: Option<Box<dyn epi::Storage>>,
is_desktop: bool,
#[cfg(feature = "glow")] gl: Option<std::rc::Rc<glow::Context>>,
#[cfg(feature = "wgpu")] wgpu_render_state: Option<egui_wgpu::RenderState>,
) -> Self {
let egui_ctx = egui::Context::default();
egui_ctx.set_embed_viewports(!is_desktop);
let memory = load_egui_memory(storage.as_deref()).unwrap_or_default();
egui_ctx.memory_mut(|mem| *mem = memory);
let frame = epi::Frame {
info: epi::IntegrationInfo {
system_theme,
@ -245,9 +248,6 @@ impl EpiIntegration {
state: ElementState::Pressed,
..
} => self.can_drag_window = true,
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
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);
self.frame.info.system_theme = Some(theme);
@ -336,7 +336,7 @@ impl EpiIntegration {
epi::set_value(
storage,
STORAGE_WINDOW_KEY,
&WindowSettings::from_display(window),
&WindowSettings::from_window(self.egui_ctx.zoom_factor(), window),
);
}
}

View File

@ -24,8 +24,8 @@ use egui_winit::{
};
use crate::{
native::epi_integration::EpiIntegration, App, AppCreator, CreationContext, NativeOptions,
Result, Storage,
native::{epi_integration::EpiIntegration, winit_integration::create_egui_context},
App, AppCreator, CreationContext, NativeOptions, Result, Storage,
};
use super::{
@ -86,6 +86,8 @@ struct GlowWinitRunning {
/// The setup is divided between the `new` fn and `on_resume` fn. we can just assume that `on_resume` is a continuation of
/// `new` fn on all platforms. only on android, do we get multiple resumed events because app can be suspended.
struct GlutinWindowContext {
egui_ctx: egui::Context,
swap_interval: glutin::surface::SwapInterval,
gl_config: glutin::config::Config,
@ -138,6 +140,7 @@ impl GlowWinitApp {
#[allow(unsafe_code)]
fn create_glutin_windowed_context(
egui_ctx: &egui::Context,
event_loop: &EventLoopWindowTarget<UserEvent>,
storage: Option<&dyn Storage>,
native_options: &mut NativeOptions,
@ -146,11 +149,16 @@ impl GlowWinitApp {
let window_settings = epi_integration::load_window_settings(storage);
let winit_window_builder =
epi_integration::viewport_builder(event_loop, native_options, window_settings);
let winit_window_builder = epi_integration::viewport_builder(
egui_ctx.zoom_factor(),
event_loop,
native_options,
window_settings,
);
let mut glutin_window_context =
unsafe { GlutinWindowContext::new(winit_window_builder, native_options, event_loop)? };
let mut glutin_window_context = unsafe {
GlutinWindowContext::new(egui_ctx, winit_window_builder, native_options, event_loop)?
};
// Creates the window - must come before we create our glow context
glutin_window_context.on_resume(event_loop)?;
@ -190,7 +198,10 @@ impl GlowWinitApp {
.unwrap_or(&self.app_name),
);
let egui_ctx = create_egui_context(storage.as_deref());
let (mut glutin, painter) = Self::create_glutin_windowed_context(
&egui_ctx,
event_loop,
storage.as_deref(),
&mut self.native_options,
@ -209,12 +220,12 @@ impl GlowWinitApp {
winit_integration::system_theme(&glutin.window(ViewportId::ROOT), &self.native_options);
let integration = EpiIntegration::new(
egui_ctx,
&glutin.window(ViewportId::ROOT),
system_theme,
&self.app_name,
&self.native_options,
storage,
winit_integration::IS_DESKTOP,
Some(gl.clone()),
#[cfg(feature = "wgpu")]
None,
@ -640,7 +651,7 @@ impl GlowWinitRunning {
std::thread::sleep(std::time::Duration::from_millis(10));
}
glutin.handle_viewport_output(viewport_output);
glutin.handle_viewport_output(&integration.egui_ctx, viewport_output);
if integration.should_close() {
EventResult::Exit
@ -761,6 +772,7 @@ impl GlowWinitRunning {
impl GlutinWindowContext {
#[allow(unsafe_code)]
unsafe fn new(
egui_ctx: &egui::Context,
viewport_builder: ViewportBuilder,
native_options: &NativeOptions,
event_loop: &EventLoopWindowTarget<UserEvent>,
@ -812,7 +824,11 @@ impl GlutinWindowContext {
let display_builder = 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(create_winit_window_builder(viewport_builder.clone())));
.with_window_builder(Some(create_winit_window_builder(
egui_ctx,
event_loop,
viewport_builder.clone(),
)));
let (window, gl_config) = {
crate::profile_scope!("DisplayBuilder::build");
@ -908,6 +924,7 @@ impl GlutinWindowContext {
// https://github.com/emilk/egui/pull/2541#issuecomment-1370767582
let mut slf = GlutinWindowContext {
egui_ctx: egui_ctx.clone(),
swap_interval,
gl_config,
current_gl_context: None,
@ -967,7 +984,7 @@ impl GlutinWindowContext {
log::trace!("Window doesn't exist yet. Creating one now with finalize_window");
let window = glutin_winit::finalize_window(
event_loop,
create_winit_window_builder(viewport.builder.clone()),
create_winit_window_builder(&self.egui_ctx, event_loop, viewport.builder.clone()),
&self.gl_config,
)?;
apply_viewport_builder_to_new_window(&window, &viewport.builder);
@ -1095,7 +1112,11 @@ impl GlutinWindowContext {
self.gl_config.display().get_proc_address(addr)
}
fn handle_viewport_output(&mut self, viewport_output: ViewportIdMap<ViewportOutput>) {
fn handle_viewport_output(
&mut self,
egui_ctx: &egui::Context,
viewport_output: ViewportIdMap<ViewportOutput>,
) {
crate::profile_function!();
let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect();
@ -1115,6 +1136,7 @@ impl GlutinWindowContext {
let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent);
let viewport = initialize_or_update_viewport(
egui_ctx,
&mut self.viewports,
ids,
class,
@ -1126,6 +1148,7 @@ impl GlutinWindowContext {
if let Some(window) = &viewport.window {
let is_viewport_focused = self.focused_viewport == Some(viewport_id);
egui_winit::process_viewport_commands(
egui_ctx,
&mut viewport.info,
commands,
window,
@ -1158,14 +1181,15 @@ impl Viewport {
}
}
fn initialize_or_update_viewport(
viewports: &mut ViewportIdMap<Viewport>,
fn initialize_or_update_viewport<'vp>(
egu_ctx: &'_ egui::Context,
viewports: &'vp mut ViewportIdMap<Viewport>,
ids: ViewportIdPair,
class: ViewportClass,
mut builder: ViewportBuilder,
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
focused_viewport: Option<ViewportId>,
) -> &mut Viewport {
) -> &'vp mut Viewport {
crate::profile_function!();
if builder.icon.is_none() {
@ -1213,6 +1237,7 @@ fn initialize_or_update_viewport(
} else if let Some(window) = &viewport.window {
let is_viewport_focused = focused_viewport == Some(ids.this);
process_viewport_commands(
egu_ctx,
&mut viewport.info,
delta_commands,
window,
@ -1248,6 +1273,7 @@ fn render_immediate_viewport(
let mut glutin = glutin.borrow_mut();
let viewport = initialize_or_update_viewport(
egui_ctx,
&mut glutin.viewports,
ids,
ViewportClass::Immediate,
@ -1363,7 +1389,7 @@ fn render_immediate_viewport(
winit_state.handle_platform_output(window, egui_ctx, platform_output);
glutin.handle_viewport_output(viewport_output);
glutin.handle_viewport_output(egui_ctx, viewport_output);
}
#[cfg(feature = "__screenshot")]

View File

@ -57,6 +57,7 @@ struct WgpuWinitRunning {
///
/// Wrapped in an `Rc<RefCell<…>>` so it can be re-entrantly shared via a weak-pointer.
pub struct SharedState {
egui_ctx: egui::Context,
viewports: Viewports,
painter: egui_wgpu::winit::Painter,
viewport_from_window: HashMap<WindowId, ViewportId>,
@ -123,7 +124,12 @@ impl WgpuWinitApp {
for viewport in viewports.values_mut() {
if viewport.window.is_none() {
viewport.init_window(viewport_from_window, painter, event_loop);
viewport.init_window(
&running.integration.egui_ctx,
viewport_from_window,
painter,
event_loop,
);
}
}
}
@ -140,6 +146,7 @@ impl WgpuWinitApp {
fn init_run_state(
&mut self,
egui_ctx: egui::Context,
event_loop: &EventLoopWindowTarget<UserEvent>,
storage: Option<Box<dyn Storage>>,
window: Window,
@ -163,12 +170,12 @@ impl WgpuWinitApp {
let system_theme = winit_integration::system_theme(&window, &self.native_options);
let integration = EpiIntegration::new(
egui_ctx.clone(),
&window,
system_theme,
&self.app_name,
&self.native_options,
storage,
winit_integration::IS_DESKTOP,
#[cfg(feature = "glow")]
None,
wgpu_render_state.clone(),
@ -177,22 +184,20 @@ impl WgpuWinitApp {
{
let event_loop_proxy = self.repaint_proxy.clone();
integration
.egui_ctx
.set_request_repaint_callback(move |info| {
log::trace!("request_repaint_callback: {info:?}");
let when = Instant::now() + info.delay;
let frame_nr = info.current_frame_nr;
egui_ctx.set_request_repaint_callback(move |info| {
log::trace!("request_repaint_callback: {info:?}");
let when = Instant::now() + info.delay;
let frame_nr = info.current_frame_nr;
event_loop_proxy
.lock()
.send_event(UserEvent::RequestRepaint {
when,
frame_nr,
viewport_id: info.viewport_id,
})
.ok();
});
event_loop_proxy
.lock()
.send_event(UserEvent::RequestRepaint {
when,
frame_nr,
viewport_id: info.viewport_id,
})
.ok();
});
}
let mut egui_winit = egui_winit::State::new(
@ -208,12 +213,12 @@ impl WgpuWinitApp {
integration.init_accesskit(&mut egui_winit, &window, event_loop_proxy);
}
let theme = system_theme.unwrap_or(self.native_options.default_theme);
integration.egui_ctx.set_visuals(theme.egui_visuals());
egui_ctx.set_visuals(theme.egui_visuals());
let app_creator = std::mem::take(&mut self.app_creator)
.expect("Single-use AppCreator has unexpectedly already been taken");
let cc = CreationContext {
egui_ctx: integration.egui_ctx.clone(),
egui_ctx: egui_ctx.clone(),
integration_info: integration.frame.info().clone(),
storage: integration.frame.storage(),
#[cfg(feature = "glow")]
@ -250,6 +255,7 @@ impl WgpuWinitApp {
);
let shared = Rc::new(RefCell::new(SharedState {
egui_ctx,
viewport_from_window,
viewports,
painter,
@ -263,20 +269,14 @@ impl WgpuWinitApp {
let event_loop: *const EventLoopWindowTarget<UserEvent> = event_loop;
egui::Context::set_immediate_viewport_renderer(move |egui_ctx, immediate_viewport| {
egui::Context::set_immediate_viewport_renderer(move |_egui_ctx, immediate_viewport| {
if let Some(shared) = shared.upgrade() {
// SAFETY: the event loop lives longer than
// the Rc:s we just upgraded above.
#[allow(unsafe_code)]
let event_loop = unsafe { event_loop.as_ref().unwrap() };
render_immediate_viewport(
event_loop,
egui_ctx,
beginning,
&shared,
immediate_viewport,
);
render_immediate_viewport(event_loop, beginning, &shared, immediate_viewport);
} else {
log::warn!("render_sync_callback called after window closed");
}
@ -374,9 +374,14 @@ impl WinitApp for WgpuWinitApp {
.as_ref()
.unwrap_or(&self.app_name),
);
let (window, builder) =
create_window(event_loop, storage.as_deref(), &mut self.native_options)?;
self.init_run_state(event_loop, storage, window, builder)?
let egui_ctx = winit_integration::create_egui_context(storage.as_deref());
let (window, builder) = create_window(
&egui_ctx,
event_loop,
storage.as_deref(),
&mut self.native_options,
)?;
self.init_run_state(egui_ctx, event_loop, storage, window, builder)?
};
EventResult::RepaintNow(
@ -549,6 +554,7 @@ impl WgpuWinitRunning {
let mut shared = shared.borrow_mut();
let SharedState {
egui_ctx,
viewports,
painter,
viewport_from_window,
@ -578,16 +584,16 @@ impl WgpuWinitRunning {
viewport_output,
} = full_output;
egui_winit.handle_platform_output(window, &integration.egui_ctx, platform_output);
egui_winit.handle_platform_output(window, egui_ctx, platform_output);
{
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
let clipped_primitives = egui_ctx.tessellate(shapes, pixels_per_point);
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),
app.clear_color(&egui_ctx.style().visuals),
&clipped_primitives,
&textures_delta,
screenshot_requested,
@ -607,7 +613,12 @@ impl WgpuWinitRunning {
let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect();
handle_viewport_output(viewport_output, viewports, *focused_viewport);
handle_viewport_output(
&integration.egui_ctx,
viewport_output,
viewports,
*focused_viewport,
);
// Prune dead viewports:
viewports.retain(|id, _| active_viewports_ids.contains(id));
@ -755,6 +766,7 @@ impl WgpuWinitRunning {
impl Viewport {
fn init_window(
&mut self,
egui_ctx: &egui::Context,
windows_id: &mut HashMap<WindowId, ViewportId>,
painter: &mut egui_wgpu::winit::Painter,
event_loop: &EventLoopWindowTarget<UserEvent>,
@ -763,7 +775,9 @@ impl Viewport {
let viewport_id = self.ids.this;
match create_winit_window_builder(self.builder.clone()).build(event_loop) {
match create_winit_window_builder(egui_ctx, event_loop, self.builder.clone())
.build(event_loop)
{
Ok(window) => {
apply_viewport_builder_to_new_window(&window, &self.builder);
@ -806,6 +820,7 @@ impl Viewport {
}
fn create_window(
egui_ctx: &egui::Context,
event_loop: &EventLoopWindowTarget<UserEvent>,
storage: Option<&dyn Storage>,
native_options: &mut NativeOptions,
@ -813,11 +828,16 @@ fn create_window(
crate::profile_function!();
let window_settings = epi_integration::load_window_settings(storage);
let viewport_builder =
epi_integration::viewport_builder(event_loop, native_options, window_settings);
let viewport_builder = epi_integration::viewport_builder(
egui_ctx.zoom_factor(),
event_loop,
native_options,
window_settings,
);
let window = {
crate::profile_scope!("WindowBuilder::build");
create_winit_window_builder(viewport_builder.clone()).build(event_loop)?
create_winit_window_builder(egui_ctx, event_loop, viewport_builder.clone())
.build(event_loop)?
};
apply_viewport_builder_to_new_window(&window, &viewport_builder);
epi_integration::apply_window_settings(&window, window_settings);
@ -826,7 +846,6 @@ fn create_window(
fn render_immediate_viewport(
event_loop: &EventLoopWindowTarget<UserEvent>,
egui_ctx: &egui::Context,
beginning: Instant,
shared: &RefCell<SharedState>,
immediate_viewport: ImmediateViewport<'_>,
@ -841,6 +860,7 @@ fn render_immediate_viewport(
let input = {
let SharedState {
egui_ctx,
viewports,
painter,
viewport_from_window,
@ -848,6 +868,7 @@ fn render_immediate_viewport(
} = &mut *shared.borrow_mut();
let viewport = initialize_or_update_viewport(
egui_ctx,
viewports,
ids,
ViewportClass::Immediate,
@ -856,7 +877,7 @@ fn render_immediate_viewport(
None,
);
if viewport.window.is_none() {
viewport.init_window(viewport_from_window, painter, event_loop);
viewport.init_window(egui_ctx, viewport_from_window, painter, event_loop);
}
viewport.update_viewport_info();
@ -873,6 +894,8 @@ fn render_immediate_viewport(
input
};
let egui_ctx = shared.borrow().egui_ctx.clone();
// ------------------------------------------
// Run the user code, which could re-entrantly call this function again (!).
@ -924,13 +947,14 @@ fn render_immediate_viewport(
false,
);
winit_state.handle_platform_output(window, egui_ctx, platform_output);
winit_state.handle_platform_output(window, &egui_ctx, platform_output);
handle_viewport_output(viewport_output, viewports, *focused_viewport);
handle_viewport_output(&egui_ctx, viewport_output, viewports, *focused_viewport);
}
/// Add new viewports, and update existing ones:
fn handle_viewport_output(
egui_ctx: &egui::Context,
viewport_output: ViewportIdMap<ViewportOutput>,
viewports: &mut ViewportIdMap<Viewport>,
focused_viewport: Option<ViewportId>,
@ -950,6 +974,7 @@ fn handle_viewport_output(
let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent);
let viewport = initialize_or_update_viewport(
egui_ctx,
viewports,
ids,
class,
@ -961,6 +986,7 @@ fn handle_viewport_output(
if let Some(window) = viewport.window.as_ref() {
let is_viewport_focused = focused_viewport == Some(viewport_id);
egui_winit::process_viewport_commands(
egui_ctx,
&mut viewport.info,
commands,
window,
@ -971,14 +997,15 @@ fn handle_viewport_output(
}
}
fn initialize_or_update_viewport(
viewports: &mut Viewports,
fn initialize_or_update_viewport<'vp>(
egui_ctx: &egui::Context,
viewports: &'vp mut Viewports,
ids: ViewportIdPair,
class: ViewportClass,
mut builder: ViewportBuilder,
viewport_ui_cb: Option<Arc<dyn Fn(&egui::Context) + Send + Sync>>,
focused_viewport: Option<ViewportId>,
) -> &mut Viewport {
) -> &'vp mut Viewport {
if builder.icon.is_none() {
// Inherit icon from parent
builder.icon = viewports
@ -1023,6 +1050,7 @@ fn initialize_or_update_viewport(
} else if let Some(window) = &viewport.window {
let is_viewport_focused = focused_viewport == Some(ids.this);
process_viewport_commands(
egui_ctx,
&mut viewport.info,
delta_commands,
window,

View File

@ -11,13 +11,27 @@ use egui_winit::accesskit_winit;
use super::epi_integration::EpiIntegration;
pub const IS_DESKTOP: bool = cfg!(any(
target_os = "freebsd",
target_os = "linux",
target_os = "macos",
target_os = "openbsd",
target_os = "windows",
));
/// Create an egui context, restoring it from storage if possible.
pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Context {
crate::profile_function!();
pub const IS_DESKTOP: bool = cfg!(any(
target_os = "freebsd",
target_os = "linux",
target_os = "macos",
target_os = "openbsd",
target_os = "windows",
));
let egui_ctx = egui::Context::default();
egui_ctx.set_embed_viewports(!IS_DESKTOP);
let memory = crate::native::epi_integration::load_egui_memory(storage).unwrap_or_default();
egui_ctx.memory_mut(|mem| *mem = memory);
egui_ctx
}
/// The custom even `eframe` uses with the [`winit`] event loop.
#[derive(Debug)]

View File

@ -58,6 +58,12 @@ impl AppRunner {
));
super::storage::load_memory(&egui_ctx);
egui_ctx.options_mut(|o| {
// On web, the browser controls the zoom factor:
o.zoom_with_keyboard = false;
o.zoom_factor = 1.0;
});
let theme = system_theme.unwrap_or(web_options.default_theme);
egui_ctx.set_visuals(theme.egui_visuals());
@ -112,7 +118,13 @@ 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());
runner
.input
.raw
.viewports
.entry(egui::ViewportId::ROOT)
.or_default()
.native_pixels_per_point = Some(super::native_pixels_per_point());
Ok(runner)
}

View File

@ -23,12 +23,17 @@ pub(crate) struct WebInput {
impl WebInput {
pub fn new_frame(&mut self, canvas_size: egui::Vec2) -> egui::RawInput {
egui::RawInput {
let mut raw_input = egui::RawInput {
screen_rect: Some(egui::Rect::from_min_size(Default::default(), canvas_size)),
pixels_per_point: Some(super::native_pixels_per_point()), // We ALWAYS use the native pixels-per-point
time: Some(super::now_sec()),
..self.raw.take()
}
};
raw_input
.viewports
.entry(egui::ViewportId::ROOT)
.or_default()
.native_pixels_per_point = Some(super::native_pixels_per_point());
raw_input
}
pub fn on_web_page_focus_change(&mut self, focused: bool) {

View File

@ -24,9 +24,14 @@ pub use window_settings::WindowSettings;
use raw_window_handle::HasRawDisplayHandle;
pub fn native_pixels_per_point(window: &Window) -> f32 {
window.scale_factor() as f32
}
#[allow(unused_imports)]
pub(crate) use profiling_scopes::*;
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
event_loop::EventLoopWindowTarget,
window::{CursorGrabMode, Window, WindowButtons, WindowLevel},
};
pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
let size = window.inner_size();
@ -111,7 +116,7 @@ impl State {
pointer_pos_in_points: None,
any_pointer_button_down: false,
current_cursor_icon: None,
current_pixels_per_point: 1.0,
current_pixels_per_point: native_pixels_per_point.unwrap_or(1.0),
clipboard: clipboard::Clipboard::new(display_target),
@ -125,9 +130,13 @@ impl State {
allow_ime: false,
};
if let Some(native_pixels_per_point) = native_pixels_per_point {
slf.set_pixels_per_point(native_pixels_per_point);
}
slf.egui_input
.viewports
.entry(ViewportId::ROOT)
.or_default()
.native_pixels_per_point = native_pixels_per_point;
if let Some(max_texture_side) = max_texture_side {
slf.set_max_texture_side(max_texture_side);
}
@ -155,19 +164,6 @@ impl State {
self.egui_input.max_texture_side = Some(max_texture_side);
}
/// Call this when a new native Window is created for rendering to initialize the `pixels_per_point`
/// for that window.
///
/// In particular, on Android it is necessary to call this after each `Resumed` lifecycle
/// event, each time a new native window is created.
///
/// Once this has been initialized for a new window then this state will be maintained by handling
/// [`winit::event::WindowEvent::ScaleFactorChanged`] events.
pub fn set_pixels_per_point(&mut self, pixels_per_point: f32) {
self.egui_input.pixels_per_point = Some(pixels_per_point);
self.current_pixels_per_point = pixels_per_point;
}
/// The number of physical pixels per logical point,
/// as configured on the current egui context (see [`egui::Context::pixels_per_point`]).
#[inline]
@ -193,7 +189,7 @@ impl State {
///
/// 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());
update_viewport_info(info, window, self.current_pixels_per_point);
}
/// Prepare for a new frame by extracting the accumulated input,
@ -206,8 +202,6 @@ impl State {
pub fn take_egui_input(&mut self, window: &Window) -> egui::RawInput {
crate::profile_function!();
let pixels_per_point = self.pixels_per_point();
self.egui_input.time = Some(self.start_time.elapsed().as_secs_f64());
// TODO remove this in winit 0.29
@ -220,7 +214,7 @@ impl State {
// See: https://github.com/rust-windowing/winit/issues/208
// This solves an issue where egui window positions would be changed when minimizing on Windows.
let screen_size_in_pixels = screen_size_in_pixels(window);
let screen_size_in_points = screen_size_in_pixels / pixels_per_point;
let screen_size_in_points = screen_size_in_pixels / self.current_pixels_per_point;
self.egui_input.screen_rect = (screen_size_in_points.x > 0.0
&& screen_size_in_points.y > 0.0)
@ -228,7 +222,13 @@ impl State {
// Tell egui which viewport is now active:
self.egui_input.viewport_id = self.viewport_id;
self.egui_input.native_pixels_per_point = Some(native_pixels_per_point(window));
self.egui_input
.viewports
.entry(self.viewport_id)
.or_default()
.native_pixels_per_point = Some(window.scale_factor() as f32);
self.egui_input.take()
}
@ -245,9 +245,14 @@ impl State {
use winit::event::WindowEvent;
match event {
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let pixels_per_point = *scale_factor as f32;
self.egui_input.pixels_per_point = Some(pixels_per_point);
self.current_pixels_per_point = pixels_per_point;
let native_pixels_per_point = *scale_factor as f32;
self.egui_input
.viewports
.entry(self.viewport_id)
.or_default()
.native_pixels_per_point = Some(native_pixels_per_point);
self.current_pixels_per_point = egui_ctx.zoom_factor() * native_pixels_per_point;
EventResponse {
repaint: true,
consumed: false,
@ -709,8 +714,7 @@ impl State {
accesskit_update,
} = platform_output;
self.current_pixels_per_point =
egui_ctx.input_for(self.viewport_id, |i| i.pixels_per_point); // someone can have changed it to scale the UI
self.current_pixels_per_point = egui_ctx.pixels_per_point(); // someone can have changed it to scale the UI
self.set_cursor_icon(window, cursor_icon);
@ -829,15 +833,15 @@ fn update_viewport_info(viewport_info: &mut ViewportInfo, window: &Window, pixel
}
};
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());
viewport_info.minimized = window.is_minimized().or(viewport_info.minimized);
viewport_info.fullscreen = Some(window.fullscreen().is_some());
viewport_info.inner_rect = inner_rect;
viewport_info.maximized = Some(window.is_maximized());
viewport_info.minimized = window.is_minimized().or(viewport_info.minimized);
viewport_info.monitor_size = monitor_size;
viewport_info.native_pixels_per_point = Some(window.scale_factor() as f32);
viewport_info.outer_rect = outer_rect;
viewport_info.title = Some(window.title());
}
fn open_url_in_browser(_url: &str) {
@ -1039,178 +1043,230 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::Curs
// ---------------------------------------------------------------------------
pub fn process_viewport_commands(
egui_ctx: &egui::Context,
info: &mut ViewportInfo,
commands: impl IntoIterator<Item = ViewportCommand>,
window: &Window,
is_viewport_focused: bool,
screenshot_requested: &mut bool,
) {
for command in commands {
process_viewport_command(
egui_ctx,
window,
command,
info,
is_viewport_focused,
screenshot_requested,
);
}
}
fn process_viewport_command(
egui_ctx: &egui::Context,
window: &Window,
command: ViewportCommand,
info: &mut ViewportInfo,
is_viewport_focused: bool,
screenshot_requested: &mut bool,
) {
crate::profile_function!();
use winit::window::ResizeDirection;
for command in commands {
match command {
ViewportCommand::Close => {
info.events.push(egui::ViewportEvent::Close);
}
ViewportCommand::StartDrag => {
// If `is_viewport_focused` is not checked on x11 the input will be permanently taken until the app is killed!
log::debug!("Processing ViewportCommand::{command:?}");
// 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}");
}
}
}
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));
}
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,
egui::viewport::ResizeDirection::West => ResizeDirection::West,
egui::viewport::ResizeDirection::NorthEast => ResizeDirection::NorthEast,
egui::viewport::ResizeDirection::SouthEast => ResizeDirection::SouthEast,
egui::viewport::ResizeDirection::NorthWest => ResizeDirection::NorthWest,
egui::viewport::ResizeDirection::SouthWest => ResizeDirection::SouthWest,
}) {
let egui_zoom_factor = egui_ctx.zoom_factor();
let pixels_per_point = egui_zoom_factor * window.scale_factor() as f32;
match command {
ViewportCommand::Close => {
info.events.push(egui::ViewportEvent::Close);
}
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}");
}
}
ViewportCommand::Title(title) => {
window.set_title(&title);
}
ViewportCommand::InnerSize(size) => {
let width_px = pixels_per_point * size.x.max(1.0);
let height_px = pixels_per_point * size.y.max(1.0);
window.set_inner_size(PhysicalSize::new(width_px, height_px));
}
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,
egui::viewport::ResizeDirection::West => ResizeDirection::West,
egui::viewport::ResizeDirection::NorthEast => ResizeDirection::NorthEast,
egui::viewport::ResizeDirection::SouthEast => ResizeDirection::SouthEast,
egui::viewport::ResizeDirection::NorthWest => ResizeDirection::NorthWest,
egui::viewport::ResizeDirection::SouthWest => ResizeDirection::SouthWest,
}) {
log::warn!("{command:?}: {err}");
}
ViewportCommand::Transparent(v) => window.set_transparent(v),
ViewportCommand::Visible(v) => window.set_visible(v),
ViewportCommand::OuterPosition(pos) => {
window.set_outer_position(LogicalPosition::new(pos.x, pos.y));
}
ViewportCommand::Title(title) => {
window.set_title(&title);
}
ViewportCommand::Transparent(v) => window.set_transparent(v),
ViewportCommand::Visible(v) => window.set_visible(v),
ViewportCommand::OuterPosition(pos) => {
window.set_outer_position(PhysicalPosition::new(
pixels_per_point * pos.x,
pixels_per_point * pos.y,
));
}
ViewportCommand::MinInnerSize(s) => {
window.set_min_inner_size((s.is_finite() && s != Vec2::ZERO).then_some(
PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
));
}
ViewportCommand::MaxInnerSize(s) => {
window.set_max_inner_size((s.is_finite() && s != Vec2::INFINITY).then_some(
PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y),
));
}
ViewportCommand::ResizeIncrements(s) => {
window.set_resize_increments(
s.map(|s| PhysicalSize::new(pixels_per_point * s.x, pixels_per_point * s.y)),
);
}
ViewportCommand::Resizable(v) => window.set_resizable(v),
ViewportCommand::EnableButtons {
close,
minimized,
maximize,
} => window.set_enabled_buttons(
if close {
WindowButtons::CLOSE
} else {
WindowButtons::empty()
} | if minimized {
WindowButtons::MINIMIZE
} else {
WindowButtons::empty()
} | if maximize {
WindowButtons::MAXIMIZE
} else {
WindowButtons::empty()
},
),
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)));
}
ViewportCommand::Decorations(v) => window.set_decorations(v),
ViewportCommand::WindowLevel(l) => window.set_window_level(match l {
egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
}),
ViewportCommand::Icon(icon) => {
window.set_window_icon(icon.map(|icon| {
winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height)
.expect("Invalid ICON data!")
}));
}
ViewportCommand::IMEPosition(pos) => {
window.set_ime_position(PhysicalPosition::new(
pixels_per_point * pos.x,
pixels_per_point * pos.y,
));
}
ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),
ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(match p {
egui::viewport::IMEPurpose::Password => winit::window::ImePurpose::Password,
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::MinInnerSize(s) => {
window.set_min_inner_size(
(s.is_finite() && s != Vec2::ZERO).then_some(LogicalSize::new(s.x, s.y)),
);
}
ViewportCommand::MaxInnerSize(s) => {
window.set_max_inner_size(
(s.is_finite() && s != Vec2::INFINITY).then_some(LogicalSize::new(s.x, s.y)),
);
}
ViewportCommand::ResizeIncrements(s) => {
window.set_resize_increments(s.map(|s| LogicalSize::new(s.x, s.y)));
}
ViewportCommand::Resizable(v) => window.set_resizable(v),
ViewportCommand::EnableButtons {
close,
minimized,
maximize,
} => window.set_enabled_buttons(
if close {
WindowButtons::CLOSE
} else {
WindowButtons::empty()
} | if minimized {
WindowButtons::MINIMIZE
} else {
WindowButtons::empty()
} | if maximize {
WindowButtons::MAXIMIZE
} else {
WindowButtons::empty()
},
),
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)));
}
ViewportCommand::Decorations(v) => window.set_decorations(v),
ViewportCommand::WindowLevel(l) => window.set_window_level(match l {
egui::viewport::WindowLevel::AlwaysOnBottom => WindowLevel::AlwaysOnBottom,
egui::viewport::WindowLevel::AlwaysOnTop => WindowLevel::AlwaysOnTop,
egui::viewport::WindowLevel::Normal => WindowLevel::Normal,
}),
ViewportCommand::Icon(icon) => {
window.set_window_icon(icon.map(|icon| {
winit::window::Icon::from_rgba(icon.rgba.clone(), icon.width, icon.height)
.expect("Invalid ICON data!")
}));
}
ViewportCommand::IMEPosition(pos) => {
window.set_ime_position(LogicalPosition::new(pos.x, pos.y));
}
ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),
ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(match p {
egui::viewport::IMEPurpose::Password => winit::window::ImePurpose::Password,
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(match a {
egui::UserAttentionType::Reset => None,
egui::UserAttentionType::Critical => {
Some(winit::window::UserAttentionType::Critical)
}
}
ViewportCommand::RequestUserAttention(a) => {
window.request_user_attention(match a {
egui::UserAttentionType::Reset => None,
egui::UserAttentionType::Critical => {
Some(winit::window::UserAttentionType::Critical)
}
egui::UserAttentionType::Informational => {
Some(winit::window::UserAttentionType::Informational)
}
});
}
ViewportCommand::SetTheme(t) => window.set_theme(match t {
egui::SystemTheme::Light => Some(winit::window::Theme::Light),
egui::SystemTheme::Dark => Some(winit::window::Theme::Dark),
egui::SystemTheme::SystemDefault => None,
}),
ViewportCommand::ContentProtected(v) => window.set_content_protected(v),
ViewportCommand::CursorPosition(pos) => {
if let Err(err) = window.set_cursor_position(LogicalPosition::new(pos.x, pos.y)) {
log::warn!("{command:?}: {err}");
egui::UserAttentionType::Informational => {
Some(winit::window::UserAttentionType::Informational)
}
});
}
ViewportCommand::SetTheme(t) => window.set_theme(match t {
egui::SystemTheme::Light => Some(winit::window::Theme::Light),
egui::SystemTheme::Dark => Some(winit::window::Theme::Dark),
egui::SystemTheme::SystemDefault => None,
}),
ViewportCommand::ContentProtected(v) => window.set_content_protected(v),
ViewportCommand::CursorPosition(pos) => {
if let Err(err) = window.set_cursor_position(PhysicalPosition::new(
pixels_per_point * pos.x,
pixels_per_point * pos.y,
)) {
log::warn!("{command:?}: {err}");
}
ViewportCommand::CursorGrab(o) => {
if let Err(err) = window.set_cursor_grab(match o {
egui::viewport::CursorGrab::None => CursorGrabMode::None,
egui::viewport::CursorGrab::Confined => CursorGrabMode::Confined,
egui::viewport::CursorGrab::Locked => CursorGrabMode::Locked,
}) {
log::warn!("{command:?}: {err}");
}
}
ViewportCommand::CursorGrab(o) => {
if let Err(err) = window.set_cursor_grab(match o {
egui::viewport::CursorGrab::None => CursorGrabMode::None,
egui::viewport::CursorGrab::Confined => CursorGrabMode::Confined,
egui::viewport::CursorGrab::Locked => CursorGrabMode::Locked,
}) {
log::warn!("{command:?}: {err}");
}
ViewportCommand::CursorVisible(v) => window.set_cursor_visible(v),
ViewportCommand::MousePassthrough(passthrough) => {
if let Err(err) = window.set_cursor_hittest(!passthrough) {
log::warn!("{command:?}: {err}");
}
}
ViewportCommand::Screenshot => {
*screenshot_requested = true;
}
ViewportCommand::CursorVisible(v) => window.set_cursor_visible(v),
ViewportCommand::MousePassthrough(passthrough) => {
if let Err(err) = window.set_cursor_hittest(!passthrough) {
log::warn!("{command:?}: {err}");
}
}
ViewportCommand::Screenshot => {
*screenshot_requested = true;
}
}
}
pub fn create_winit_window_builder(
pub fn create_winit_window_builder<T>(
egui_ctx: &egui::Context,
event_loop: &EventLoopWindowTarget<T>,
viewport_builder: ViewportBuilder,
) -> winit::window::WindowBuilder {
crate::profile_function!();
// 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.
let native_pixels_per_point = event_loop
.primary_monitor()
.or_else(|| event_loop.available_monitors().next())
.map_or_else(
|| {
log::debug!("Failed to find a monitor - assuming native_pixels_per_point of 1.0");
1.0
},
|m| m.scale_factor() as f32,
);
let zoom_factor = egui_ctx.zoom_factor();
let pixels_per_point = zoom_factor * native_pixels_per_point;
let ViewportBuilder {
title,
position,
@ -1271,27 +1327,31 @@ pub fn create_winit_window_builder(
.with_active(active.unwrap_or(true));
if let Some(inner_size) = inner_size {
window_builder = window_builder
.with_inner_size(winit::dpi::LogicalSize::new(inner_size.x, inner_size.y));
window_builder = window_builder.with_inner_size(PhysicalSize::new(
pixels_per_point * inner_size.x,
pixels_per_point * inner_size.y,
));
}
if let Some(min_inner_size) = min_inner_size {
window_builder = window_builder.with_min_inner_size(winit::dpi::LogicalSize::new(
min_inner_size.x,
min_inner_size.y,
window_builder = window_builder.with_min_inner_size(PhysicalSize::new(
pixels_per_point * min_inner_size.x,
pixels_per_point * min_inner_size.y,
));
}
if let Some(max_inner_size) = max_inner_size {
window_builder = window_builder.with_max_inner_size(winit::dpi::LogicalSize::new(
max_inner_size.x,
max_inner_size.y,
window_builder = window_builder.with_max_inner_size(PhysicalSize::new(
pixels_per_point * max_inner_size.x,
pixels_per_point * max_inner_size.y,
));
}
if let Some(position) = position {
window_builder =
window_builder.with_position(winit::dpi::LogicalPosition::new(position.x, position.y));
window_builder = window_builder.with_position(PhysicalPosition::new(
pixels_per_point * position.x,
pixels_per_point * position.y,
));
}
if let Some(icon) = icon {
@ -1430,10 +1490,3 @@ mod profiling_scopes {
}
pub(crate) use profile_scope;
}
#[allow(unused_imports)]
pub(crate) use profiling_scopes::*;
use winit::{
dpi::{LogicalPosition, LogicalSize},
window::{CursorGrabMode, Window, WindowButtons, WindowLevel},
};

View File

@ -18,8 +18,10 @@ pub struct WindowSettings {
}
impl WindowSettings {
pub fn from_display(window: &winit::window::Window) -> Self {
let inner_size_points = window.inner_size().to_logical::<f32>(window.scale_factor());
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()
@ -100,6 +102,7 @@ impl WindowSettings {
pub fn clamp_position_to_monitors<E>(
&mut self,
egui_zoom_factor: f32,
event_loop: &winit::event_loop::EventLoopWindowTarget<E>,
) {
// If the app last ran on two monitors and only one is now connected, then
@ -116,15 +119,16 @@ impl WindowSettings {
};
if let Some(pos_px) = &mut self.inner_position_pixels {
clamp_pos_to_monitors(event_loop, inner_size_points, pos_px);
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(event_loop, inner_size_points, pos_px);
clamp_pos_to_monitors(egui_zoom_factor, event_loop, inner_size_points, pos_px);
}
}
}
fn clamp_pos_to_monitors<E>(
egui_zoom_factor: f32,
event_loop: &winit::event_loop::EventLoopWindowTarget<E>,
window_size_pts: egui::Vec2,
position_px: &mut egui::Pos2,
@ -142,7 +146,7 @@ fn clamp_pos_to_monitors<E>(
};
for monitor in monitors {
let window_size_px = window_size_pts * (monitor.scale_factor() as f32);
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)
@ -155,10 +159,14 @@ fn clamp_pos_to_monitors<E>(
}
}
let mut window_size_px = window_size_pts * (active_monitor.scale_factor() as f32);
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 * active_monitor.scale_factor() as f32);
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,

View File

@ -200,6 +200,9 @@ struct ContextImpl {
animation_manager: AnimationManager,
tex_manager: WrappedTextureManager,
/// Set during the frame, becomes active at the start of the next frame.
new_zoom_factor: Option<f32>,
os: OperatingSystem,
/// How deeply nested are we?
@ -234,6 +237,8 @@ impl ContextImpl {
.and_then(|v| v.parent)
.unwrap_or_default();
let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent_id);
let is_outermost_viewport = self.viewport_stack.is_empty(); // not necessarily root, just outermost immediate viewport
self.viewport_stack.push(ids);
let viewport = self.viewports.entry(viewport_id).or_default();
@ -252,19 +257,26 @@ impl ContextImpl {
}
}
if let Some(new_pixels_per_point) = self.memory.override_pixels_per_point {
if viewport.input.pixels_per_point != new_pixels_per_point {
new_raw_input.pixels_per_point = Some(new_pixels_per_point);
if is_outermost_viewport {
if let Some(new_zoom_factor) = self.new_zoom_factor.take() {
let ratio = self.memory.options.zoom_factor / new_zoom_factor;
self.memory.options.zoom_factor = new_zoom_factor;
let input = &viewport.input;
// This is a bit hacky, but is required to avoid jitter:
let ratio = input.pixels_per_point / new_pixels_per_point;
let mut rect = input.screen_rect;
rect.min = (ratio * rect.min.to_vec2()).to_pos2();
rect.max = (ratio * rect.max.to_vec2()).to_pos2();
new_raw_input.screen_rect = Some(rect);
// We should really scale everything else in the input too,
// but the `screen_rect` is the most important part.
}
}
let pixels_per_point = self.memory.options.zoom_factor
* new_raw_input
.viewport()
.native_pixels_per_point
.unwrap_or(1.0);
viewport.layer_rects_prev_frame = std::mem::take(&mut viewport.layer_rects_this_frame);
@ -275,8 +287,11 @@ impl ContextImpl {
self.memory
.begin_frame(&viewport.input, &new_raw_input, &all_viewport_ids);
viewport.input = std::mem::take(&mut viewport.input)
.begin_frame(new_raw_input, viewport.repaint.requested_last_frame);
viewport.input = std::mem::take(&mut viewport.input).begin_frame(
new_raw_input,
viewport.repaint.requested_last_frame,
pixels_per_point,
);
viewport.frame_state.begin_frame(&viewport.input);
@ -469,13 +484,11 @@ impl std::cmp::PartialEq for Context {
impl Default for Context {
fn default() -> Self {
let s = Self(Arc::new(RwLock::new(ContextImpl::default())));
s.write(|ctx| {
ctx.embed_viewports = true;
});
s
let ctx = ContextImpl {
embed_viewports: true,
..Default::default()
};
Self(Arc::new(RwLock::new(ctx)))
}
}
@ -1338,44 +1351,85 @@ impl Context {
}
/// The number of physical pixels for each logical point.
///
/// This is calculated as [`Self::zoom_factor`] * [`Self::native_pixels_per_point`]
#[inline(always)]
pub fn pixels_per_point(&self) -> f32 {
self.input(|i| i.pixels_per_point())
self.input(|i| i.pixels_per_point)
}
/// Set the number of physical pixels for each logical point.
/// Will become active at the start of the next frame.
///
/// Note that this may be overwritten by input from the integration via [`RawInput::pixels_per_point`].
/// For instance, when using `eframe` on web, the browsers native zoom level will always be used.
/// This will actually translate to a call to [`Self::set_zoom_factor`].
pub fn set_pixels_per_point(&self, pixels_per_point: f32) {
if pixels_per_point != self.pixels_per_point() {
self.write(|ctx| {
ctx.memory.override_pixels_per_point = Some(pixels_per_point);
for id in ctx.all_viewport_ids() {
ctx.request_repaint(id);
}
});
self.set_zoom_factor(pixels_per_point / self.native_pixels_per_point().unwrap_or(1.0));
}
}
/// The number of physical pixels for each logical point on this monitor.
///
/// This is given as input to egui via [`ViewportInfo::native_pixels_per_point`]
/// and cannot be changed.
#[inline(always)]
pub fn native_pixels_per_point(&self) -> Option<f32> {
self.input(|i| i.viewport().native_pixels_per_point)
}
/// Global zoom factor of the UI.
///
/// This is used to calculate the `pixels_per_point`
/// for the UI as `pixels_per_point = zoom_fator * native_pixels_per_point`.
///
/// The default is 1.0.
/// Make larger to make everything larger.
#[inline(always)]
pub fn zoom_factor(&self) -> f32 {
self.options(|o| o.zoom_factor)
}
/// Sets zoom factor of the UI.
/// Will become active at the start of the next frame.
///
/// This is used to calculate the `pixels_per_point`
/// for the UI as `pixels_per_point = zoom_fator * native_pixels_per_point`.
///
/// The default is 1.0.
/// Make larger to make everything larger.
#[inline(always)]
pub fn set_zoom_factor(&self, zoom_factor: f32) {
self.write(|ctx| {
if ctx.memory.options.zoom_factor != zoom_factor {
ctx.new_zoom_factor = Some(zoom_factor);
for id in ctx.all_viewport_ids() {
ctx.request_repaint(id);
}
}
});
}
/// Useful for pixel-perfect rendering
#[inline]
pub(crate) fn round_to_pixel(&self, point: f32) -> f32 {
let pixels_per_point = self.pixels_per_point();
(point * pixels_per_point).round() / pixels_per_point
}
/// Useful for pixel-perfect rendering
#[inline]
pub(crate) fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
}
/// Useful for pixel-perfect rendering
#[inline]
pub(crate) fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
}
/// Useful for pixel-perfect rendering
#[inline]
pub(crate) fn round_rect_to_pixels(&self, rect: Rect) -> Rect {
Rect {
min: self.round_pos_to_pixels(rect.min),
@ -1496,6 +1550,11 @@ impl Context {
#[must_use]
pub fn end_frame(&self) -> FullOutput {
crate::profile_function!();
if self.options(|o| o.zoom_with_keyboard) {
crate::gui_zoom::zoom_with_keyboard(self);
}
self.write(|ctx| ctx.end_frame())
}
}

View File

@ -11,7 +11,10 @@ use crate::{emath::*, ViewportId, ViewportIdMap};
/// You can check if `egui` is using the inputs using
/// [`crate::Context::wants_pointer_input`] and [`crate::Context::wants_keyboard_input`].
///
/// All coordinates are in points (logical pixels) with origin (0, 0) in the top left corner.
/// All coordinates are in points (logical pixels) with origin (0, 0) in the top left .corner.
///
/// Ii "points" can be calculated from native physical pixels
/// using `pixels_per_point` = [`crate::Context::zoom_factor`] * `native_pixels_per_point`;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RawInput {
@ -31,20 +34,6 @@ pub struct RawInput {
/// `None` will be treated as "same as last frame", with the default being a very big area.
pub screen_rect: Option<Rect>,
/// 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<f32>,
/// 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<f32>,
/// Maximum size of one side of the font texture.
///
/// Ask your graphics drivers about this. This corresponds to `GL_MAX_TEXTURE_SIZE`.
@ -89,11 +78,9 @@ pub struct RawInput {
impl Default for RawInput {
fn default() -> Self {
Self {
viewport_id: Default::default(),
viewports: Default::default(),
viewport_id: ViewportId::ROOT,
viewports: std::iter::once((ViewportId::ROOT, Default::default())).collect(),
screen_rect: None,
pixels_per_point: None,
native_pixels_per_point: None,
max_texture_side: None,
time: None,
predicted_dt: 1.0 / 60.0,
@ -122,8 +109,6 @@ impl RawInput {
viewport_id: self.viewport_id,
viewports: self.viewports.clone(),
screen_rect: self.screen_rect.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,
@ -141,8 +126,6 @@ impl RawInput {
viewport_id: viewport_ids,
viewports,
screen_rect,
pixels_per_point,
native_pixels_per_point,
max_texture_side,
time,
predicted_dt,
@ -156,8 +139,6 @@ impl RawInput {
self.viewport_id = 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
@ -181,10 +162,12 @@ pub enum ViewportEvent {
Close,
}
/// Information about the current viewport,
/// given as input each frame.
/// Information about the current viewport, given as input each frame.
///
/// `None` means "unknown".
///
/// All units are in ui "points", which can be calculated from native physical pixels
/// using `pixels_per_point` = [`crate::Context::zoom_factor`] * `[Self::native_pixels_per_point`];
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct ViewportInfo {
@ -196,8 +179,13 @@ pub struct ViewportInfo {
pub events: Vec<ViewportEvent>,
/// Number of physical pixels per ui point.
pub pixels_per_point: f32,
/// 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<f32>,
/// Current monitor size in egui points.
pub monitor_size: Option<Vec2>,
@ -239,7 +227,7 @@ impl ViewportInfo {
parent,
title,
events,
pixels_per_point,
native_pixels_per_point,
monitor_size,
inner_rect,
outer_rect,
@ -262,8 +250,8 @@ impl ViewportInfo {
ui.label(format!("{events:?}"));
ui.end_row();
ui.label("Pixels per point:");
ui.label(pixels_per_point.to_string());
ui.label("Native pixels-per-point:");
ui.label(opt_as_str(native_pixels_per_point));
ui.end_row();
ui.label("Monitor size:");
@ -1115,8 +1103,6 @@ impl RawInput {
viewport_id,
viewports,
screen_rect,
pixels_per_point,
native_pixels_per_point,
max_texture_side,
time,
predicted_dt,
@ -1137,16 +1123,7 @@ impl RawInput {
});
}
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"));

View File

@ -12,20 +12,14 @@ pub mod kb_shortcuts {
pub const ZOOM_RESET: KeyboardShortcut = KeyboardShortcut::new(Modifiers::COMMAND, Key::Num0);
}
/// Let the user scale the GUI (change `Context::pixels_per_point`) by pressing
/// Let the user scale the GUI (change [`Context::zoom_factor`]) by pressing
/// Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser.
///
/// ```
/// # let ctx = &egui::Context::default();
/// // On web, the browser controls the gui zoom.
/// #[cfg(not(target_arch = "wasm32"))]
/// egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx);
/// ```
pub fn zoom_with_keyboard_shortcuts(ctx: &Context) {
/// By default, [`crate::Context`] calls this function at the end of each frame,
/// controllable by [`crate::Options::zoom_with_keyboard`].
pub(crate) fn zoom_with_keyboard(ctx: &Context) {
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_RESET)) {
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);
}
ctx.set_zoom_factor(1.0);
} else {
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_IN)) {
zoom_in(ctx);
@ -36,47 +30,34 @@ pub fn zoom_with_keyboard_shortcuts(ctx: &Context) {
}
}
const MIN_PIXELS_PER_POINT: f32 = 0.2;
const MAX_PIXELS_PER_POINT: f32 = 4.0;
const MIN_ZOOM_FACTOR: f32 = 0.2;
const MAX_ZOOM_FACTOR: f32 = 5.0;
/// Make everything larger.
/// Make everything larger by increasing [`Context::zoom_factor`].
pub fn zoom_in(ctx: &Context) {
let mut pixels_per_point = ctx.pixels_per_point();
pixels_per_point += 0.1;
pixels_per_point = pixels_per_point.clamp(MIN_PIXELS_PER_POINT, MAX_PIXELS_PER_POINT);
pixels_per_point = (pixels_per_point * 10.).round() / 10.;
ctx.set_pixels_per_point(pixels_per_point);
let mut zoom_factor = ctx.zoom_factor();
zoom_factor += 0.1;
zoom_factor = zoom_factor.clamp(MIN_ZOOM_FACTOR, MAX_ZOOM_FACTOR);
zoom_factor = (zoom_factor * 10.).round() / 10.;
ctx.set_zoom_factor(zoom_factor);
}
/// Make everything smaller.
/// Make everything smaller by decreasing [`Context::zoom_factor`].
pub fn zoom_out(ctx: &Context) {
let mut pixels_per_point = ctx.pixels_per_point();
pixels_per_point -= 0.1;
pixels_per_point = pixels_per_point.clamp(MIN_PIXELS_PER_POINT, MAX_PIXELS_PER_POINT);
pixels_per_point = (pixels_per_point * 10.).round() / 10.;
ctx.set_pixels_per_point(pixels_per_point);
let mut zoom_factor = ctx.zoom_factor();
zoom_factor -= 0.1;
zoom_factor = zoom_factor.clamp(MIN_ZOOM_FACTOR, MAX_ZOOM_FACTOR);
zoom_factor = (zoom_factor * 10.).round() / 10.;
ctx.set_zoom_factor(zoom_factor);
}
/// Show buttons for zooming the ui.
///
/// This is meant to be called from within a menu (See [`Ui::menu_button`]).
///
/// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as:
/// ```ignore
/// // On web, the browser controls the gui zoom.
/// if !frame.is_web() {
/// ui.menu_button("View", |ui| {
/// egui::gui_zoom::zoom_menu_buttons(
/// ui,
/// frame.info().native_pixels_per_point,
/// );
/// });
/// }
/// ```
pub fn zoom_menu_buttons(ui: &mut Ui, native_pixels_per_point: Option<f32>) {
pub fn zoom_menu_buttons(ui: &mut Ui) {
if ui
.add_enabled(
ui.ctx().pixels_per_point() < MAX_PIXELS_PER_POINT,
ui.ctx().zoom_factor() < MAX_ZOOM_FACTOR,
Button::new("Zoom In").shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_IN)),
)
.clicked()
@ -87,7 +68,7 @@ pub fn zoom_menu_buttons(ui: &mut Ui, native_pixels_per_point: Option<f32>) {
if ui
.add_enabled(
ui.ctx().pixels_per_point() > MIN_PIXELS_PER_POINT,
ui.ctx().zoom_factor() > MIN_ZOOM_FACTOR,
Button::new("Zoom Out")
.shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_OUT)),
)
@ -97,17 +78,15 @@ pub fn zoom_menu_buttons(ui: &mut Ui, native_pixels_per_point: Option<f32>) {
ui.close_menu();
}
if let Some(native_pixels_per_point) = native_pixels_per_point {
if ui
.add_enabled(
ui.ctx().pixels_per_point() != native_pixels_per_point,
Button::new("Reset Zoom")
.shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_RESET)),
)
.clicked()
{
ui.ctx().set_pixels_per_point(native_pixels_per_point);
ui.close_menu();
}
if ui
.add_enabled(
ui.ctx().zoom_factor() != 1.0,
Button::new("Reset Zoom")
.shortcut_text(ui.ctx().format_shortcut(&kb_shortcuts::ZOOM_RESET)),
)
.clicked()
{
ui.ctx().set_zoom_factor(1.0);
ui.close_menu();
}
}

View File

@ -148,6 +148,7 @@ impl InputState {
mut self,
mut new: RawInput,
requested_repaint_last_frame: bool,
pixels_per_point: f32,
) -> InputState {
crate::profile_function!();
@ -217,7 +218,7 @@ impl InputState {
scroll_delta,
zoom_factor_delta,
screen_rect,
pixels_per_point: new.pixels_per_point.unwrap_or(self.pixels_per_point),
pixels_per_point,
max_texture_side: new.max_texture_side.unwrap_or(self.max_texture_side),
time,
unstable_dt,

View File

@ -71,10 +71,6 @@ pub struct Memory {
pub caches: crate::util::cache::CacheStorage,
// ------------------------------------------
/// new scale that will be applied at the start of the next frame
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) override_pixels_per_point: Option<f32>,
/// new fonts that will be applied at the start of the next frame
#[cfg_attr(feature = "persistence", serde(skip))]
pub(crate) new_font_definitions: Option<epaint::text::FontDefinitions>,
@ -111,7 +107,6 @@ impl Default for Memory {
options: Default::default(),
data: Default::default(),
caches: Default::default(),
override_pixels_per_point: Default::default(),
new_font_definitions: Default::default(),
interactions: Default::default(),
viewport_id: Default::default(),
@ -176,6 +171,21 @@ pub struct Options {
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) style: std::sync::Arc<Style>,
/// Global zoom factor of the UI.
///
/// This is used to calculate the `pixels_per_point`
/// for the UI as `pixels_per_point = zoom_fator * native_pixels_per_point`.
///
/// The default is 1.0.
/// Make larger to make everything larger.
pub zoom_factor: f32,
/// If `true`, egui will change the scale of the ui ([`crate::Context::zoom_factor`]) when the user
/// presses Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser.
///
/// This is `true` by default.
pub zoom_with_keyboard: bool,
/// Controls the tessellator.
pub tessellation_options: epaint::TessellationOptions,
@ -208,6 +218,8 @@ impl Default for Options {
fn default() -> Self {
Self {
style: Default::default(),
zoom_factor: 1.0,
zoom_with_keyboard: true,
tessellation_options: Default::default(),
screen_reader: false,
preload_font_glyphs: true,

View File

@ -24,9 +24,10 @@ web_app = ["http", "persistence", "web_screen_reader"]
http = ["ehttp", "image", "poll-promise", "egui_extras/image"]
image_viewer = ["image", "egui_extras/all_loaders", "rfd"]
persistence = ["eframe/persistence", "egui/persistence", "serde"]
web_screen_reader = ["eframe/web_screen_reader"] # experimental
puffin = ["eframe/puffin", "dep:puffin", "dep:puffin_http"]
serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
syntect = ["egui_demo_lib/syntect"]
web_screen_reader = ["eframe/web_screen_reader"] # experimental
glow = ["eframe/glow"]
wgpu = ["eframe/wgpu", "bytemuck"]
@ -45,14 +46,17 @@ egui = { version = "0.23.0", path = "../egui", features = [
egui_demo_lib = { version = "0.23.0", path = "../egui_demo_lib", features = [
"chrono",
] }
egui_extras = { version = "0.23.0", path = "../egui_extras", features = [
"image",
] }
log = { version = "0.4", features = ["std"] }
# Optional dependencies:
bytemuck = { version = "1.7.1", optional = true }
egui_extras = { version = "0.23.0", path = "../egui_extras", features = [
"image",
] }
puffin = { version = "0.18", optional = true }
puffin_http = { version = "0.15", optional = true }
# feature "http":
ehttp = { version = "0.3.1", optional = true }

View File

@ -51,10 +51,6 @@ pub struct BackendPanel {
// go back to [`RunMode::Reactive`] mode each time we start
run_mode: RunMode,
/// current slider value for current gui scale
#[cfg_attr(feature = "serde", serde(skip))]
pixels_per_point: Option<f32>,
#[cfg_attr(feature = "serde", serde(skip))]
frame_history: crate::frame_history::FrameHistory,
@ -82,7 +78,7 @@ impl BackendPanel {
}
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
self.integration_ui(ui, frame);
integration_ui(ui, frame);
ui.separator();
@ -131,118 +127,6 @@ impl BackendPanel {
}
}
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("egui running inside ");
ui.hyperlink_to(
"eframe",
"https://github.com/emilk/egui/tree/master/crates/eframe",
);
ui.label(".");
});
#[cfg(target_arch = "wasm32")]
ui.collapsing("Web info (location)", |ui| {
ui.style_mut().wrap = Some(false);
ui.monospace(format!("{:#?}", frame.info().web_info.location));
});
// 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);
}
#[cfg(not(target_arch = "wasm32"))]
{
ui.horizontal(|ui| {
{
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()
{
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(fullscreen));
}
}
if ui
.button("📱 Phone Size")
.on_hover_text("Resize the window to be small like a phone.")
.clicked()
{
// 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();
}
});
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()
{
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
}
}
}
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());
let mut reset = false;
ui.horizontal(|ui| {
ui.spacing_mut().slider_width = 90.0;
let response = ui
.add(
egui::Slider::new(pixels_per_point, 0.5..=5.0)
.logarithmic(true)
.clamp_to_range(true)
.text("Scale"),
)
.on_hover_text("Physical pixels per point.");
if response.drag_released() {
// We wait until mouse release to activate:
ui.ctx().set_pixels_per_point(*pixels_per_point);
reset = true;
} else if !response.is_pointer_button_down_on() {
// When not dragging, show the current pixels_per_point so others can change it.
reset = true;
}
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"))
.on_hover_text(format!(
"Reset scale to native value ({native_pixels_per_point:.1})"
))
.clicked()
{
ui.ctx().set_pixels_per_point(native_pixels_per_point);
}
}
});
if reset {
self.pixels_per_point = None;
}
}
fn run_mode_ui(&mut self, ui: &mut egui::Ui) {
ui.horizontal(|ui| {
let run_mode = &mut self.run_mode;
@ -286,6 +170,65 @@ impl BackendPanel {
}
}
fn integration_ui(ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("egui running inside ");
ui.hyperlink_to(
"eframe",
"https://github.com/emilk/egui/tree/master/crates/eframe",
);
ui.label(".");
});
#[cfg(target_arch = "wasm32")]
ui.collapsing("Web info (location)", |ui| {
ui.style_mut().wrap = Some(false);
ui.monospace(format!("{:#?}", _frame.info().web_info.location));
});
#[cfg(not(target_arch = "wasm32"))]
{
ui.horizontal(|ui| {
{
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()
{
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(fullscreen));
}
}
if ui
.button("📱 Phone Size")
.on_hover_text("Resize the window to be small like a phone.")
.clicked()
{
// 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();
}
});
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()
{
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
}
}
}
// ----------------------------------------------------------------------------
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]

View File

@ -4,6 +4,22 @@
// When compiling natively:
fn main() -> Result<(), eframe::Error> {
for arg in std::env::args().skip(1) {
match arg.as_str() {
"--profile" => {
#[cfg(feature = "puffin")]
start_puffin_server();
#[cfg(not(feature = "puffin"))]
panic!("Unknown argument: {arg} - you need to enable the 'puffin' feature to use this.");
}
_ => {
panic!("Unknown argument: {arg}");
}
}
}
{
// Silence wgpu log spam (https://github.com/gfx-rs/wgpu/issues/3206)
let mut rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned());
@ -33,3 +49,28 @@ fn main() -> Result<(), eframe::Error> {
Box::new(|cc| Box::new(egui_demo_app::WrapApp::new(cc))),
)
}
#[cfg(feature = "puffin")]
fn start_puffin_server() {
puffin::set_scopes_on(true); // tell puffin to collect data
match puffin_http::Server::new("127.0.0.1:8585") {
Ok(puffin_server) => {
eprintln!("Run: cargo install puffin_viewer && puffin_viewer --url 127.0.0.1:8585");
std::process::Command::new("puffin_viewer")
.arg("--url")
.arg("127.0.0.1:8585")
.spawn()
.ok();
// We can store the server if we want, but in this case we just want
// it to keep running. Dropping it closes the server, so let's not drop it!
#[allow(clippy::mem_forget)]
std::mem::forget(puffin_server);
}
Err(err) => {
eprintln!("Failed to start puffin server: {err}");
}
};
}

View File

@ -283,11 +283,6 @@ impl eframe::App for WrapApp {
self.ui_file_drag_and_drop(ctx);
// On web, the browser controls `pixels_per_point`.
if !frame.is_web() {
egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx);
}
self.run_cmd(ctx, cmd);
}

View File

@ -329,7 +329,12 @@ fn file_menu_button(ui: &mut Ui) {
// On the web the browser controls the zoom
#[cfg(not(target_arch = "wasm32"))]
{
egui::gui_zoom::zoom_menu_buttons(ui, None);
egui::gui_zoom::zoom_menu_buttons(ui);
ui.weak(format!(
"Current zoom: {:.0}%",
100.0 * ui.ctx().zoom_factor()
))
.on_hover_text("The UI zoom level, on top of the operating system's default value");
ui.separator();
}

View File

@ -75,6 +75,7 @@ impl EguiGlow {
for (_, ViewportOutput { commands, .. }) in viewport_output {
let mut screenshot_requested = false;
egui_winit::process_viewport_commands(
&self.egui_ctx,
&mut self.viewport_info,
commands,
window,

View File

@ -8,6 +8,10 @@ rust-version = "1.72"
publish = false
[features]
wgpu = ["eframe/wgpu"]
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"puffin",

View File

@ -52,7 +52,7 @@ impl eframe::App for MyApp {
fn start_puffin_server() {
puffin::set_scopes_on(true); // tell puffin to collect data
match puffin_http::Server::new("0.0.0.0:8585") {
match puffin_http::Server::new("127.0.0.1:8585") {
Ok(puffin_server) => {
eprintln!("Run: cargo install puffin_viewer && puffin_viewer --url 127.0.0.1:8585");

View File

@ -259,7 +259,6 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc<RwLock<ViewportState>>]) {
data.insert_temp(container_id.with("pixels_per_point"), tmp_pixels_per_point);
});
}
egui::gui_zoom::zoom_with_keyboard_shortcuts(&ctx);
if ctx.viewport_id() != ctx.parent_viewport_id() {
let parent = ctx.parent_viewport_id();

View File

@ -20,41 +20,41 @@ export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454
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 doc --quiet --lib --no-deps --all-features
cargo doc --quiet --document-private-items --no-deps --all-features
cargo cranky --all-targets --all-features -- -D warnings
cargo cranky --quiet --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 test --all-targets --all-features
cargo test --doc # slow - checks all doc-tests
cargo check --quiet --all-targets
cargo check --quiet --all-targets --all-features
cargo check --quiet -p egui_demo_app --lib --target wasm32-unknown-unknown
cargo check --quiet -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features
cargo test --quiet --all-targets --all-features
cargo test --quiet --doc # slow - checks all doc-tests
(cd crates/eframe && cargo check --no-default-features --features "glow")
(cd crates/eframe && cargo check --no-default-features --features "wgpu")
(cd crates/egui && cargo check --no-default-features --features "serde")
(cd crates/egui_demo_app && cargo check --no-default-features --features "glow")
(cd crates/egui_demo_app && cargo check --no-default-features --features "wgpu")
(cd crates/egui_demo_lib && cargo check --no-default-features)
(cd crates/egui_extras && cargo check --no-default-features)
(cd crates/egui_glow && cargo check --no-default-features)
(cd crates/egui-winit && cargo check --no-default-features --features "wayland")
(cd crates/egui-winit && cargo check --no-default-features --features "x11")
(cd crates/emath && cargo check --no-default-features)
(cd crates/epaint && cargo check --no-default-features --release)
(cd crates/epaint && cargo check --no-default-features)
(cd crates/eframe && cargo check --quiet --no-default-features --features "glow")
(cd crates/eframe && cargo check --quiet --no-default-features --features "wgpu")
(cd crates/egui && cargo check --quiet --no-default-features --features "serde")
(cd crates/egui_demo_app && cargo check --quiet --no-default-features --features "glow")
(cd crates/egui_demo_app && cargo check --quiet --no-default-features --features "wgpu")
(cd crates/egui_demo_lib && cargo check --quiet --no-default-features)
(cd crates/egui_extras && cargo check --quiet --no-default-features)
(cd crates/egui_glow && cargo check --quiet --no-default-features)
(cd crates/egui-winit && cargo check --quiet --no-default-features --features "wayland")
(cd crates/egui-winit && cargo check --quiet --no-default-features --features "x11")
(cd crates/emath && cargo check --quiet --no-default-features)
(cd crates/epaint && cargo check --quiet --no-default-features --release)
(cd crates/epaint && cargo check --quiet --no-default-features)
(cd crates/eframe && cargo check --all-features)
(cd crates/egui && cargo check --all-features)
(cd crates/egui_demo_app && cargo check --all-features)
(cd crates/egui_extras && cargo check --all-features)
(cd crates/egui_glow && cargo check --all-features)
(cd crates/egui-winit && cargo check --all-features)
(cd crates/emath && cargo check --all-features)
(cd crates/epaint && cargo check --all-features)
(cd crates/eframe && cargo check --quiet --all-features)
(cd crates/egui && cargo check --quiet --all-features)
(cd crates/egui_demo_app && cargo check --quiet --all-features)
(cd crates/egui_extras && cargo check --quiet --all-features)
(cd crates/egui_glow && cargo check --quiet --all-features)
(cd crates/egui-winit && cargo check --quiet --all-features)
(cd crates/emath && cargo check --quiet --all-features)
(cd crates/epaint && cargo check --quiet --all-features)
./scripts/wasm_bindgen_check.sh

View File

@ -10,4 +10,4 @@ set -x
# Use scripts/clippy_wasm/clippy.toml
export CLIPPY_CONF_DIR="scripts/clippy_wasm"
cargo cranky --all-features --target wasm32-unknown-unknown --target-dir target_wasm -p egui_demo_app --lib -- --deny warnings
cargo cranky --quiet --all-features --target wasm32-unknown-unknown --target-dir target_wasm -p egui_demo_app --lib -- --deny warnings