From 69b995b2718395987772a8c345c147c325aa8d86 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Fri, 3 May 2024 07:11:45 -0700 Subject: [PATCH] Wire up scale factor (#245) Generally prefer use of LogicalPosition to PhysicalPosition for layout, events, etc. Use scale factor to convert to logical position. Apply scale factor as final transform before Vello rendering. This PR also makes physical_position available in PointerState. There is a TODO for handling WindowEvent::Rescale, which remains and is not addressed. It should be at some future point. --- crates/masonry/src/contexts.rs | 6 ++--- crates/masonry/src/event.rs | 12 ++++++---- crates/masonry/src/event_loop_runner.rs | 32 +++++++++++++++---------- crates/masonry/src/render_root.rs | 8 +++---- crates/masonry/src/testing/harness.rs | 11 +++++---- crates/masonry/src/widget/split.rs | 4 ++-- crates/masonry/src/widget/widget_pod.rs | 6 ++--- src/app_main.rs | 6 ++--- src/widget/raw_event.rs | 2 +- 9 files changed, 49 insertions(+), 38 deletions(-) diff --git a/crates/masonry/src/contexts.rs b/crates/masonry/src/contexts.rs index d419d38e..bb2bf329 100644 --- a/crates/masonry/src/contexts.rs +++ b/crates/masonry/src/contexts.rs @@ -8,7 +8,7 @@ use std::time::Duration; use parley::FontContext; use tracing::{trace, warn}; -use winit::dpi::PhysicalPosition; +use winit::dpi::LogicalPosition; use winit::window::CursorIcon; use crate::action::Action; @@ -630,9 +630,7 @@ impl LayoutCtx<'_> { self.widget_state.local_paint_rect = self.widget_state.local_paint_rect.union(child.paint_rect()); - let mouse_pos = self - .mouse_pos - .map(|pos| PhysicalPosition::new(pos.x, pos.y)); + let mouse_pos = self.mouse_pos.map(|pos| LogicalPosition::new(pos.x, pos.y)); // if the widget has moved, it may have moved under the mouse, in which // case we need to handle that. if WidgetPod::update_hot_state( diff --git a/crates/masonry/src/event.rs b/crates/masonry/src/event.rs index 9fb606d9..7e03e71b 100644 --- a/crates/masonry/src/event.rs +++ b/crates/masonry/src/event.rs @@ -9,7 +9,7 @@ use crate::WidgetId; use std::{collections::HashSet, path::PathBuf}; -use winit::dpi::{PhysicalPosition, PhysicalSize}; +use winit::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use winit::event::{Ime, KeyEvent, Modifiers, MouseButton}; use winit::keyboard::ModifiersState; @@ -39,7 +39,7 @@ pub enum PointerEvent { PointerMove(PointerState), PointerEnter(PointerState), PointerLeave(PointerState), - MouseWheel(PhysicalPosition, PointerState), + MouseWheel(LogicalPosition, PointerState), HoverFile(PathBuf, PointerState), DropFile(PathBuf, PointerState), HoverFileCancel(PointerState), @@ -60,7 +60,8 @@ pub enum TextEvent { pub struct PointerState { // TODO // pub device_id: DeviceId, - pub position: PhysicalPosition, + pub physical_position: PhysicalPosition, + pub position: LogicalPosition, pub buttons: HashSet, pub mods: Modifiers, pub count: u8, @@ -172,7 +173,7 @@ pub enum InternalLifeCycle { /// The parents widget origin in window coordinate space has changed. ParentWindowOrigin { - mouse_pos: Option>, + mouse_pos: Option>, }, } @@ -255,7 +256,8 @@ impl PointerState { let device_id = unsafe { DeviceId::dummy() }; PointerState { - position: PhysicalPosition::new(0.0, 0.0), + physical_position: PhysicalPosition::new(0.0, 0.0), + position: LogicalPosition::new(0.0, 0.0), buttons: Default::default(), mods: Default::default(), count: 0, diff --git a/crates/masonry/src/event_loop_runner.rs b/crates/masonry/src/event_loop_runner.rs index d5c48495..c1976be6 100644 --- a/crates/masonry/src/event_loop_runner.rs +++ b/crates/masonry/src/event_loop_runner.rs @@ -5,11 +5,12 @@ use std::num::NonZeroUsize; use std::sync::Arc; use tracing::warn; +use vello::kurbo::Affine; use vello::util::{RenderContext, RenderSurface}; use vello::{peniko::Color, AaSupport, RenderParams, Renderer, RendererOptions, Scene}; use wgpu::PresentMode; use winit::application::ApplicationHandler; -use winit::dpi::PhysicalPosition; +use winit::dpi::LogicalPosition; use winit::event::WindowEvent as WinitWindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::window::{Window, WindowId}; @@ -45,11 +46,12 @@ pub fn run( PresentMode::AutoVsync, )) .unwrap(); + let scale_factor = window.scale_factor(); let mut main_state = MainState { window, render_cx, surface, - render_root: RenderRoot::new(root_widget, WindowSizePolicy::User), + render_root: RenderRoot::new(root_widget, WindowSizePolicy::User, scale_factor), renderer: None, pointer_state: PointerState::empty(), app_driver: Box::new(app_driver), @@ -78,7 +80,8 @@ impl ApplicationHandler for MainState<'_> { .handle_text_event(TextEvent::ModifierChange(modifiers.state())); } WinitWindowEvent::CursorMoved { position, .. } => { - self.pointer_state.position = position; + self.pointer_state.physical_position = position; + self.pointer_state.position = position.to_logical(self.window.scale_factor()); self.render_root .handle_pointer_event(PointerEvent::PointerMove(self.pointer_state.clone())); } @@ -104,10 +107,13 @@ impl ApplicationHandler for MainState<'_> { }, WinitWindowEvent::MouseWheel { delta, .. } => { let delta = match delta { - winit::event::MouseScrollDelta::LineDelta(x, y) => (x as f64, y as f64), - winit::event::MouseScrollDelta::PixelDelta(delta) => (delta.x, delta.y), + winit::event::MouseScrollDelta::LineDelta(x, y) => { + LogicalPosition::new(x as f64, y as f64) + } + winit::event::MouseScrollDelta::PixelDelta(delta) => { + delta.to_logical(self.window.scale_factor()) + } }; - let delta = PhysicalPosition::new(delta.0, delta.1); self.render_root .handle_pointer_event(PointerEvent::MouseWheel( delta, @@ -175,7 +181,7 @@ impl ApplicationHandler for MainState<'_> { impl MainState<'_> { fn render(&mut self, scene: Scene) { - //let scale = self.window.scale_factor(); + let scale = self.window.scale_factor(); let size = self.window.inner_size(); let width = size.width; let height = size.height; @@ -185,12 +191,14 @@ impl MainState<'_> { .resize_surface(&mut self.surface, width, height); } - #[cfg(FALSE)] - let transform = if scale != 1.0 { - Some(Affine::scale(scale)) - } else { + let transformed_scene = if scale == 1.0 { None + } else { + let mut new_scene = Scene::new(); + new_scene.append(&scene, Some(Affine::scale(scale))); + Some(new_scene) }; + let scene_ref = transformed_scene.as_ref().unwrap_or(&scene); let Ok(surface_texture) = self.surface.surface.get_current_texture() else { warn!("failed to acquire next swapchain texture"); @@ -217,7 +225,7 @@ impl MainState<'_> { }; self.renderer .get_or_insert_with(|| Renderer::new(device, renderer_options).unwrap()) - .render_to_surface(device, queue, &scene, &surface_texture, &render_params) + .render_to_surface(device, queue, scene_ref, &surface_texture, &render_params) .expect("failed to render to surface"); surface_texture.present(); device.poll(wgpu::Maintain::Wait); diff --git a/crates/masonry/src/render_root.rs b/crates/masonry/src/render_root.rs index 1c770bb9..03bf7ec3 100644 --- a/crates/masonry/src/render_root.rs +++ b/crates/masonry/src/render_root.rs @@ -10,7 +10,7 @@ use parley::FontContext; use tracing::{info_span, warn}; use vello::peniko::{Color, Fill}; use vello::Scene; -use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize}; +use winit::dpi::{LogicalPosition, LogicalSize, PhysicalSize}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::CursorIcon; @@ -33,7 +33,7 @@ pub struct RenderRoot { pub(crate) scale_factor: f64, /// Is `Some` if the most recently displayed frame was an animation frame. pub(crate) last_anim: Option, - pub(crate) last_mouse_pos: Option>, + pub(crate) last_mouse_pos: Option>, pub(crate) cursor_icon: CursorIcon, pub(crate) state: RenderRootState, } @@ -80,12 +80,12 @@ pub enum RenderRootSignal { } impl RenderRoot { - pub fn new(root_widget: impl Widget, size_policy: WindowSizePolicy) -> Self { + pub fn new(root_widget: impl Widget, size_policy: WindowSizePolicy, scale_factor: f64) -> Self { let mut root = RenderRoot { root: WidgetPod::new(root_widget).boxed(), size_policy, size: PhysicalSize::new(0, 0), - scale_factor: 1.0, + scale_factor, last_anim: None, last_mouse_pos: None, cursor_icon: CursorIcon::Default, diff --git a/crates/masonry/src/testing/harness.rs b/crates/masonry/src/testing/harness.rs index 2b3e3621..614ed28e 100644 --- a/crates/masonry/src/testing/harness.rs +++ b/crates/masonry/src/testing/harness.rs @@ -13,7 +13,7 @@ use wgpu::{ BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Extent3d, ImageCopyBuffer, TextureDescriptor, TextureFormat, TextureUsages, }; -use winit::dpi::{PhysicalPosition, PhysicalSize}; +use winit::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use winit::event::{Ime, MouseButton}; use super::screenshots::get_image_diff; @@ -174,7 +174,7 @@ impl TestHarness { let window_size = PhysicalSize::new(window_size.width as _, window_size.height as _); let mut harness = TestHarness { - render_root: RenderRoot::new(root_widget, WindowSizePolicy::User), + render_root: RenderRoot::new(root_widget, WindowSizePolicy::User, 1.0), mouse_state, window_size, background_color, @@ -332,7 +332,10 @@ impl TestHarness { // FIXME - Account for scaling let pos = pos.into(); let pos = PhysicalPosition::new(pos.x, pos.y); - self.mouse_state.position = dbg!(pos); + self.mouse_state.physical_position = dbg!(pos); + // TODO: may want to support testing with non-unity scale factors. + let scale_factor = 1.0; + self.mouse_state.position = pos.to_logical(scale_factor); self.process_pointer_event(PointerEvent::PointerMove(self.mouse_state.clone())); } @@ -351,7 +354,7 @@ impl TestHarness { /// Send a Wheel event to the window pub fn mouse_wheel(&mut self, wheel_delta: Vec2) { - let pixel_delta = PhysicalPosition::new(wheel_delta.x, wheel_delta.y); + let pixel_delta = LogicalPosition::new(wheel_delta.x, wheel_delta.y); self.process_pointer_event(PointerEvent::MouseWheel( pixel_delta, self.mouse_state.clone(), diff --git a/crates/masonry/src/widget/split.rs b/crates/masonry/src/widget/split.rs index 790c2cb9..1bcf50bc 100644 --- a/crates/masonry/src/widget/split.rs +++ b/crates/masonry/src/widget/split.rs @@ -6,7 +6,7 @@ use smallvec::{smallvec, SmallVec}; use tracing::{trace, trace_span, warn, Span}; use vello::Scene; -use winit::dpi::PhysicalPosition; +use winit::dpi::LogicalPosition; use winit::event::MouseButton; use winit::window::CursorIcon; @@ -195,7 +195,7 @@ impl Split { } /// Returns true if the provided mouse position is inside the splitter bar area. - fn bar_hit_test(&self, size: Size, mouse_pos: PhysicalPosition) -> bool { + fn bar_hit_test(&self, size: Size, mouse_pos: LogicalPosition) -> bool { let (edge1, edge2) = self.bar_edges(size); match self.split_axis { Axis::Horizontal => mouse_pos.x >= edge1 && mouse_pos.x <= edge2, diff --git a/crates/masonry/src/widget/widget_pod.rs b/crates/masonry/src/widget/widget_pod.rs index edf0e755..fae2ea6d 100644 --- a/crates/masonry/src/widget/widget_pod.rs +++ b/crates/masonry/src/widget/widget_pod.rs @@ -3,7 +3,7 @@ use tracing::{info_span, trace, warn}; use vello::Scene; -use winit::dpi::PhysicalPosition; +use winit::dpi::LogicalPosition; use crate::event::{PointerEvent, TextEvent}; use crate::kurbo::{Affine, Insets, Point, Rect, Shape, Size}; @@ -231,7 +231,7 @@ impl WidgetPod { inner: &mut W, inner_state: &mut WidgetState, global_state: &mut RenderRootState, - mouse_pos: Option>, + mouse_pos: Option>, ) -> bool { let rect = inner_state.layout_rect() + inner_state.parent_window_origin.to_vec2(); let had_hot = inner_state.is_hot; @@ -607,7 +607,7 @@ impl WidgetPod { InternalLifeCycle::ParentWindowOrigin { mouse_pos } => { self.state.parent_window_origin = parent_ctx.widget_state.window_origin(); self.state.needs_window_origin = false; - let mouse_pos = mouse_pos.map(|pos| PhysicalPosition::new(pos.x, pos.y)); + let mouse_pos = mouse_pos.map(|pos| LogicalPosition::new(pos.x, pos.y)); WidgetPod::update_hot_state( &mut self.inner, &mut self.state, diff --git a/src/app_main.rs b/src/app_main.rs index 20bc4ee5..c5f3017d 100644 --- a/src/app_main.rs +++ b/src/app_main.rs @@ -12,7 +12,6 @@ use vello::{ use wgpu::PresentMode; use winit::{ application::ApplicationHandler, - dpi::PhysicalPosition, event::{ElementState, Modifiers, MouseButton, MouseScrollDelta, WindowEvent}, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, window::{Window, WindowId}, @@ -170,8 +169,9 @@ impl<'a, T: Send + 'static, V: View + 'static> MainState<'a, T, V> { MouseScrollDelta::LineDelta(x, y) => { ScrollDelta::Lines(x.trunc() as isize, y.trunc() as isize) } - MouseScrollDelta::PixelDelta(PhysicalPosition { x, y }) => { - ScrollDelta::Precise(Vec2::new(x, y) * (1.0 / self.window.scale_factor())) + MouseScrollDelta::PixelDelta(position) => { + let logical_pos = position.to_logical(self.window.scale_factor()); + ScrollDelta::Precise(Vec2::new(logical_pos.x, logical_pos.y)) } }))); self.window.request_redraw(); diff --git a/src/widget/raw_event.rs b/src/widget/raw_event.rs index d14af31f..668f17c4 100644 --- a/src/widget/raw_event.rs +++ b/src/widget/raw_event.rs @@ -38,7 +38,7 @@ pub struct MouseEvent { /// > Positive values indicate that the content that is being scrolled should move /// > right and down (revealing more content left and up). /// -/// The choice to follow this has not been reasoned, but is based on expediance +/// The choice to follow this has not been reasoned, but is based on expedience. pub enum ScrollDelta { Precise(Vec2), Lines(isize, isize),