From a4e19d7207f9aadacbc6cd1a133d98dbe861fa4b Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 17 Oct 2020 23:54:46 +0200 Subject: [PATCH] [app] Add demo app slider to change scale of all of Egui --- CHANGELOG.md | 1 + egui/src/app.rs | 10 +++++- egui/src/demos/app.rs | 70 ++++++++++++++++++++++++++++++--------- egui/src/input.rs | 2 +- egui_glium/src/backend.rs | 19 +++++++++-- egui_glium/src/lib.rs | 43 ++++++++---------------- egui_glium/src/painter.rs | 33 ++++++++++-------- egui_web/src/backend.rs | 32 +++++++++++++----- egui_web/src/lib.rs | 24 +++++++------- 9 files changed, 149 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2721668..8209dcda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * `ui.horizontal(...)` etc returns `Response` * Add ability to override text color with `visuals.override_text_color` * Refactored the interface for `egui::app::App` +* Demo App: Add slider to scale all of Egui ## 0.2.0 - 2020-10-10 diff --git a/egui/src/app.rs b/egui/src/app.rs index 53ffb9bf..29fe3d25 100644 --- a/egui/src/app.rs +++ b/egui/src/app.rs @@ -24,12 +24,14 @@ pub trait App { fn on_exit(&mut self, _storage: &mut dyn Storage) {} } +#[derive(Clone, Debug)] pub struct WebInfo { /// e.g. "#fragment" part of "www.example.com/index.html#fragment" pub web_location_hash: String, } /// Information about the backend passed to the use app each frame. +#[derive(Clone, Debug)] pub struct BackendInfo { /// If the app is running in a Web context, this returns information about the environment. pub web_info: Option, @@ -41,14 +43,20 @@ pub struct BackendInfo { /// Local time. Used for the clock in the demo app. /// Set to `None` if you don't know. pub seconds_since_midnight: Option, + + /// The OS native pixels-per-point + pub native_pixels_per_point: Option, } /// Action that can be taken by the user app. -#[derive(Clone, Copy, Debug, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct AppOutput { /// Set to `true` to stop the app. /// This does nothing for web apps. pub quit: bool, + + /// If the app sets this, change the `pixels_per_point` of Egui to this next frame. + pub pixels_per_point: Option, } pub trait TextureAllocator { diff --git a/egui/src/demos/app.rs b/egui/src/demos/app.rs index 253c0e9e..f7a671ac 100644 --- a/egui/src/demos/app.rs +++ b/egui/src/demos/app.rs @@ -188,6 +188,9 @@ pub struct DemoApp { demo_window: DemoWindow, fractal_clock: FractalClock, + /// current slider value for current gui scale (backend demo only) + pixels_per_point: Option, + #[cfg_attr(feature = "serde", serde(skip))] frame_history: FrameHistory, @@ -320,8 +323,6 @@ impl DemoApp { let is_web = info.web_info.is_some(); - let mut options = app::AppOutput::default(); - if is_web { ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."); ui.label( @@ -332,23 +333,17 @@ impl DemoApp { ui.label("Project home page:"); ui.hyperlink("https://github.com/emilk/egui"); }); - } else { - ui.heading("Egui"); - options.quit |= ui.button("Quit").clicked; + ui.separator(); } - ui.separator(); - self.run_mode_ui(ui); - if self.run_mode == RunMode::Continuous { - ui.label(format!( - "Repainting the UI each frame. FPS: {:.1}", - self.frame_history.fps() - )); - } else { - ui.label("Only running UI code when there are animations or input"); - } + ui.separator(); + + let mut output = app::AppOutput::default(); + output.pixels_per_point = self.pixels_per_point_ui(ui, info); + + ui.separator(); ui.separator(); self.frame_history.ui(ui); @@ -359,7 +354,41 @@ impl DemoApp { "Show color blend test (debug backend painter)", ); - options + if !is_web { + output.quit |= ui.button("Quit").clicked; + } + + output + } + + fn pixels_per_point_ui(&mut self, ui: &mut Ui, info: &app::BackendInfo) -> Option { + self.pixels_per_point = self + .pixels_per_point + .or(info.native_pixels_per_point) + .or_else(|| Some(ui.ctx().pixels_per_point())); + if let Some(pixels_per_point) = &mut self.pixels_per_point { + ui.add( + Slider::f32(pixels_per_point, 0.5..=5.0) + .logarithmic(true) + .text("Scale (physical pixels per point)"), + ); + if let Some(native_pixels_per_point) = info.native_pixels_per_point { + if ui + .button(format!( + "Reset scale to native value ({:.1})", + native_pixels_per_point + )) + .clicked + { + *pixels_per_point = native_pixels_per_point; + } + } + if !ui.ctx().is_using_mouse() { + // We wait until mouse release to activate: + return Some(*pixels_per_point); + } + } + None } fn run_mode_ui(&mut self, ui: &mut Ui) { @@ -371,6 +400,15 @@ impl DemoApp { ui.radio_value(run_mode, RunMode::Reactive, "Reactive") .on_hover_text("Repaint when there are animations or input (e.g. mouse movement)"); }); + + if self.run_mode == RunMode::Continuous { + ui.label(format!( + "Repainting the UI each frame. FPS: {:.1}", + self.frame_history.fps() + )); + } else { + ui.label("Only running UI code when there are animations or input"); + } } } diff --git a/egui/src/input.rs b/egui/src/input.rs index b4ddadaa..64002a1e 100644 --- a/egui/src/input.rs +++ b/egui/src/input.rs @@ -388,7 +388,7 @@ impl InputState { ui.label(format!("scroll_delta: {:?} points", scroll_delta)); ui.label(format!("screen_size: {:?} points", screen_size)); ui.label(format!( - "{:?} points for each physical pixel (HDPI factor)", + "{:?} physical pixels for each logical point", pixels_per_point )); ui.label(format!("time: {:.3} s", time)); diff --git a/egui_glium/src/backend.rs b/egui_glium/src/backend.rs index b7b0a55c..c9c42a57 100644 --- a/egui_glium/src/backend.rs +++ b/egui_glium/src/backend.rs @@ -51,7 +51,10 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) - let mut ctx = egui::Context::new(); *ctx.memory() = egui::app::get_value(&storage, EGUI_MEMORY_KEY).unwrap_or_default(); - let mut raw_input = make_raw_input(&display); + let mut raw_input = egui::RawInput { + pixels_per_point: Some(native_pixels_per_point(&display)), + ..Default::default() + }; let start_time = Instant::now(); let mut previous_frame_time = None; @@ -62,11 +65,14 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) - let mut redraw = || { let egui_start = Instant::now(); raw_input.time = start_time.elapsed().as_nanos() as f64 * 1e-9; + raw_input.screen_size = + screen_size_in_pixels(&display) / raw_input.pixels_per_point.unwrap(); let backend_info = egui::app::BackendInfo { web_info: None, cpu_usage: previous_frame_time, seconds_since_midnight: Some(seconds_since_midnight()), + native_pixels_per_point: Some(native_pixels_per_point(&display)), }; let mut ui = ctx.begin_frame(raw_input.take()); @@ -75,10 +81,17 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) - let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32; previous_frame_time = Some(frame_time); - painter.paint_jobs(&display, paint_jobs, &ctx.texture()); + painter.paint_jobs(&display, ctx.pixels_per_point(), paint_jobs, &ctx.texture()); { - let egui::app::AppOutput { quit } = app_output; + let egui::app::AppOutput { + quit, + pixels_per_point, + } = app_output; + if let Some(pixels_per_point) = pixels_per_point { + // User changed GUI scale + raw_input.pixels_per_point = Some(pixels_per_point); + } *control_flow = if quit { glutin::event_loop::ControlFlow::Exit diff --git a/egui_glium/src/lib.rs b/egui_glium/src/lib.rs index 30668aa3..e4971ae7 100644 --- a/egui_glium/src/lib.rs +++ b/egui_glium/src/lib.rs @@ -27,30 +27,16 @@ pub fn input_to_egui( use glutin::event::WindowEvent::*; match event { CloseRequested | Destroyed => *control_flow = ControlFlow::Exit, - - Resized(physical_size) => { - raw_input.screen_size = - egui::vec2(physical_size.width as f32, physical_size.height as f32) - / raw_input.pixels_per_point.unwrap(); - } - - ScaleFactorChanged { - scale_factor, - new_inner_size, - } => { - raw_input.pixels_per_point = Some(scale_factor as f32); - raw_input.screen_size = - egui::vec2(new_inner_size.width as f32, new_inner_size.height as f32) - / (scale_factor as f32); - } - MouseInput { state, .. } => { raw_input.mouse_down = state == glutin::event::ElementState::Pressed; } - CursorMoved { position, .. } => { + CursorMoved { + position: pos_in_pixels, + .. + } => { raw_input.mouse_pos = Some(pos2( - position.x as f32 / raw_input.pixels_per_point.unwrap(), - position.y as f32 / raw_input.pixels_per_point.unwrap(), + pos_in_pixels.x as f32 / raw_input.pixels_per_point.unwrap(), + pos_in_pixels.y as f32 / raw_input.pixels_per_point.unwrap(), )); } CursorLeft { .. } => { @@ -213,14 +199,11 @@ pub fn seconds_since_midnight() -> f64 { time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64) } -pub fn make_raw_input(display: &glium::Display) -> egui::RawInput { - let pixels_per_point = display.gl_window().window().scale_factor() as f32; - egui::RawInput { - screen_size: { - let (width, height) = display.get_framebuffer_dimensions(); - vec2(width as f32, height as f32) / pixels_per_point - }, - pixels_per_point: Some(pixels_per_point), - ..Default::default() - } +pub fn screen_size_in_pixels(display: &glium::Display) -> Vec2 { + let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions(); + vec2(width_in_pixels as f32, height_in_pixels as f32) +} + +pub fn native_pixels_per_point(display: &glium::Display) -> f32 { + display.gl_window().window().scale_factor() as f32 } diff --git a/egui_glium/src/painter.rs b/egui_glium/src/painter.rs index 59382295..ff05e397 100644 --- a/egui_glium/src/painter.rs +++ b/egui_glium/src/painter.rs @@ -152,16 +152,23 @@ impl Painter { pub fn paint_jobs( &mut self, display: &glium::Display, + pixels_per_point: f32, jobs: PaintJobs, - texture: &egui::Texture, + egui_texture: &egui::Texture, ) { - self.upload_egui_texture(display, texture); + self.upload_egui_texture(display, egui_texture); self.upload_pending_user_textures(display); let mut target = display.draw(); target.clear_color(0.0, 0.0, 0.0, 0.0); for (clip_rect, triangles) in jobs { - self.paint_job(&mut target, display, clip_rect, &triangles) + self.paint_job( + &mut target, + display, + pixels_per_point, + clip_rect, + &triangles, + ) } target.finish().unwrap(); } @@ -183,6 +190,7 @@ impl Painter { &mut self, target: &mut Frame, display: &glium::Display, + pixels_per_point: f32, clip_rect: Rect, triangles: &Triangles, ) { @@ -217,15 +225,14 @@ impl Painter { let index_buffer = glium::IndexBuffer::new(display, PrimitiveType::TrianglesList, &indices).unwrap(); - let pixels_per_point = display.gl_window().window().scale_factor() as f32; - let (width_pixels, height_pixels) = display.get_framebuffer_dimensions(); - let width_points = width_pixels as f32 / pixels_per_point; - let height_points = height_pixels as f32 / pixels_per_point; + let (width_in_pixels, height_in_pixels) = display.get_framebuffer_dimensions(); + let width_in_points = width_in_pixels as f32 / pixels_per_point; + let height_in_points = height_in_pixels as f32 / pixels_per_point; let texture = self.get_texture(triangles.texture_id); let uniforms = uniform! { - u_screen_size: [width_points, height_points], + u_screen_size: [width_in_points, height_in_points], u_sampler: texture.sampled().wrap_function(SamplerWrapFunction::Clamp), }; @@ -255,10 +262,10 @@ impl Painter { let clip_max_y = pixels_per_point * clip_rect.max.y; // Make sure clip rect can fit withing an `u32`: - let clip_min_x = clamp(clip_min_x, 0.0..=width_pixels as f32); - let clip_min_y = clamp(clip_min_y, 0.0..=height_pixels as f32); - let clip_max_x = clamp(clip_max_x, clip_min_x..=width_pixels as f32); - let clip_max_y = clamp(clip_max_y, clip_min_y..=height_pixels as f32); + let clip_min_x = clamp(clip_min_x, 0.0..=width_in_pixels as f32); + let clip_min_y = clamp(clip_min_y, 0.0..=height_in_pixels as f32); + let clip_max_x = clamp(clip_max_x, clip_min_x..=width_in_pixels as f32); + let clip_max_y = clamp(clip_max_y, clip_min_y..=height_in_pixels as f32); let clip_min_x = clip_min_x.round() as u32; let clip_min_y = clip_min_y.round() as u32; @@ -269,7 +276,7 @@ impl Painter { blend, scissor: Some(glium::Rect { left: clip_min_x, - bottom: height_pixels - clip_max_y, + bottom: height_in_pixels - clip_max_y, width: clip_max_x - clip_min_x, height: clip_max_y - clip_min_y, }), diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index a0ae9db4..4884c81b 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -2,7 +2,7 @@ use crate::*; pub use egui::{ app::{App, WebInfo}, - Srgba, + pos2, Srgba, }; // ---------------------------------------------------------------------------- @@ -105,13 +105,17 @@ pub struct WebInput { } impl WebInput { - pub fn new_frame(&mut self) -> egui::RawInput { + pub fn new_frame(&mut self, pixels_per_point: f32) -> egui::RawInput { + // Compensate for potential different scale of Egui compared to native. + let scale = native_pixels_per_point() / pixels_per_point; + let scroll_delta = std::mem::take(&mut self.scroll_delta) * scale; + let mouse_pos = self.mouse_pos.map(|mp| pos2(mp.x * scale, mp.y * scale)); egui::RawInput { mouse_down: self.mouse_down, - mouse_pos: self.mouse_pos, - scroll_delta: std::mem::take(&mut self.scroll_delta), - screen_size: screen_size().unwrap(), - pixels_per_point: Some(pixels_per_point()), + mouse_pos, + scroll_delta, + screen_size: screen_size_in_native_points().unwrap() * scale, + pixels_per_point: Some(pixels_per_point), time: now_sec(), events: std::mem::take(&mut self.events), } @@ -121,6 +125,7 @@ impl WebInput { // ---------------------------------------------------------------------------- pub struct AppRunner { + pixels_per_point: f32, pub web_backend: WebBackend, pub web_input: WebInput, pub app: Box, @@ -130,6 +135,7 @@ pub struct AppRunner { impl AppRunner { pub fn new(web_backend: WebBackend, app: Box) -> Result { Ok(Self { + pixels_per_point: native_pixels_per_point(), web_backend, web_input: Default::default(), app, @@ -142,9 +148,9 @@ impl AppRunner { } pub fn logic(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> { - resize_to_screen_size(self.web_backend.canvas_id()); + resize_canvas_to_screen_size(self.web_backend.canvas_id()); - let raw_input = self.web_input.new_frame(); + let raw_input = self.web_input.new_frame(self.pixels_per_point); let backend_info = egui::app::BackendInfo { web_info: Some(WebInfo { @@ -152,6 +158,7 @@ impl AppRunner { }), cpu_usage: self.web_backend.previous_frame_time, seconds_since_midnight: Some(seconds_since_midnight()), + native_pixels_per_point: Some(native_pixels_per_point()), }; let mut ui = self.web_backend.begin_frame(raw_input); @@ -162,7 +169,14 @@ impl AppRunner { handle_output(&egui_output); { - let egui::app::AppOutput { quit: _ } = app_output; + let egui::app::AppOutput { + quit: _, + pixels_per_point, + } = app_output; + + if let Some(pixels_per_point) = pixels_per_point { + self.pixels_per_point = pixels_per_point; + } } Ok((egui_output, paint_jobs)) diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 6644f2c7..e16f34f1 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -17,14 +17,6 @@ pub fn console_log(s: String) { web_sys::console::log_1(&s.into()); } -pub fn screen_size() -> Option { - let window = web_sys::window()?; - Some(egui::Vec2::new( - window.inner_width().ok()?.as_f64()? as f32, - window.inner_height().ok()?.as_f64()? as f32, - )) -} - pub fn now_sec() -> f64 { web_sys::window() .expect("should have a Window") @@ -40,7 +32,15 @@ pub fn seconds_since_midnight() -> f64 { seconds as f64 + 1e-3 * (d.get_milliseconds() as f64) } -pub fn pixels_per_point() -> f32 { +pub fn screen_size_in_native_points() -> Option { + let window = web_sys::window()?; + Some(egui::Vec2::new( + window.inner_width().ok()?.as_f64()? as f32, + window.inner_height().ok()?.as_f64()? as f32, + )) +} + +pub fn native_pixels_per_point() -> f32 { let pixels_per_point = web_sys::window().unwrap().device_pixel_ratio() as f32; if pixels_per_point > 0.0 && pixels_per_point.is_finite() { pixels_per_point @@ -78,11 +78,11 @@ pub fn pos_from_touch_event(event: &web_sys::TouchEvent) -> egui::Pos2 { } } -pub fn resize_to_screen_size(canvas_id: &str) -> Option<()> { +pub fn resize_canvas_to_screen_size(canvas_id: &str) -> Option<()> { let canvas = canvas_element(canvas_id)?; - let screen_size = screen_size()?; - let pixels_per_point = pixels_per_point(); + let screen_size = screen_size_in_native_points()?; + let pixels_per_point = native_pixels_per_point(); canvas .style() .set_property("width", &format!("{}px", screen_size.x))