From de74e9834f98021c81a4d2122c4d3a47e8101329 Mon Sep 17 00:00:00 2001 From: Aaron Muir Hamilton Date: Tue, 19 Aug 2025 16:23:20 -0400 Subject: [PATCH] dxf_viewer: Add middle/right click and double tap drag zooming. --- Cargo.lock | 1 + examples/dxf_viewer/Cargo.toml | 1 + examples/dxf_viewer/src/main.rs | 281 ++++++++++++++++++++++++-------- 3 files changed, 214 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d333e21..78ac8b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -496,6 +496,7 @@ name = "dxf_viewer" version = "0.1.0" dependencies = [ "anyhow", + "dpi", "joto_constants", "pollster", "static_aabb2d_index", diff --git a/examples/dxf_viewer/Cargo.toml b/examples/dxf_viewer/Cargo.toml index 9f64fc5..00be892 100644 --- a/examples/dxf_viewer/Cargo.toml +++ b/examples/dxf_viewer/Cargo.toml @@ -23,6 +23,7 @@ ui-events = "0.2.0" ui-events-winit = "0.2.0" vello = "0.6.0" winit = { version = "0.30.10", default-features = false } +dpi = "0.1.2" static_aabb2d_index = { version = "2.0.0", features = ["unsafe_optimizations"] } diff --git a/examples/dxf_viewer/src/main.rs b/examples/dxf_viewer/src/main.rs index d76bf33..1eed23b 100644 --- a/examples/dxf_viewer/src/main.rs +++ b/examples/dxf_viewer/src/main.rs @@ -34,6 +34,7 @@ #![windows_subsystem = "windows"] use anyhow::Result; +use dpi::PhysicalPosition; use joto_constants::u64::{INCH, MICROMETER}; use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; @@ -44,13 +45,13 @@ use tracing_subscriber::prelude::*; use ui_events::{ ScrollDelta, pointer::{ - PointerButton, PointerButtonEvent, PointerEvent, PointerId, PointerInfo, - PointerScrollEvent, PointerType, PointerUpdate, + PointerButton, PointerButtonEvent, PointerEvent, PointerGesture, PointerGestureEvent, + PointerId, PointerInfo, PointerScrollEvent, PointerState, PointerType, PointerUpdate, }, }; use ui_events_winit::{WindowEventReducer, WindowEventTranslation}; use vello::kurbo::{ - Affine, DEFAULT_ACCURACY, ParamCurveNearest, PathSeg, Point, Rect, Shape, Stroke, Vec2, + Affine, DEFAULT_ACCURACY, ParamCurveNearest, PathSeg, Point, Rect, Shape, Size, Stroke, Vec2, }; use vello::peniko::{Brush, Color, color::palette}; use vello::util::{RenderContext, RenderSurface}; @@ -86,12 +87,27 @@ enum RenderState<'s> { Suspended(Option>), } -#[derive(Default)] -struct GestureState { - /// Pointer currently panning. - pan: Option, - /// Cursor position. - cursor_pos: Point, +#[derive(Default, Debug, PartialEq)] +enum GestureState { + /// Hover highlighting. + #[default] + Hover, + /// Panning. + Pan { + /// Pointer currently panning. + pointer_id: Option, + /// Last pointer position. + pos: Point, + }, + /// Pointer zooming about point. + DragZoomAbout { + /// Pointer performing the drag zoom. + pointer_id: Option, + /// Zoom center. + about: Point, + /// Last y position. + y: f64, + }, } struct DrawingViewer { @@ -241,7 +257,7 @@ impl ApplicationHandler for TabulonDxfViewer<'_> { view_transform, text_cull_index, gestures: GestureState::default(), - defer_reprojection: false, + defer_reprojection: true, pick: None, }); } @@ -278,6 +294,21 @@ impl ApplicationHandler for TabulonDxfViewer<'_> { _ => return, }; + let size = { + let ws = window.inner_size(); + Size { + width: ws.width as f64, + height: ws.height as f64, + } + }; + + let scale_factor = window.scale_factor(); + + let center = Point { + x: size.width * 0.5, + y: size.height * 0.5, + }; + let mut reproject = false; // Set if reprojection is requested as a result of a deferral. let mut reproject_deferred = false; @@ -289,7 +320,7 @@ impl ApplicationHandler for TabulonDxfViewer<'_> { .. } ) { - if let Some(wet) = self.event_reducer.reduce(window.scale_factor(), &event) { + if let Some(wet) = self.event_reducer.reduce(scale_factor, &event) { match wet { WindowEventTranslation::Keyboard(k) => { use ui_events::keyboard::{Key, NamedKey}; @@ -312,7 +343,11 @@ impl ApplicationHandler for TabulonDxfViewer<'_> { .. }, button: Some(PointerButton::Primary), - state, + state: + PointerState { + position: PhysicalPosition { x, y }, + .. + }, } | PointerButtonEvent { pointer: @@ -321,82 +356,204 @@ impl ApplicationHandler for TabulonDxfViewer<'_> { pointer_type: PointerType::Touch, .. }, - state, + state: + PointerState { + position: PhysicalPosition { x, y }, + count: 1, + .. + }, .. }, ) => { - if viewer.gestures.pan.is_none() { - viewer.gestures.pan = pointer_id; - viewer.gestures.cursor_pos = Point { - x: state.position.x, - y: state.position.y, + if let GestureState::Hover = viewer.gestures { + viewer.gestures = GestureState::Pan { + pointer_id, + pos: Point { x, y }, + }; + } + } + PointerEvent::Down(PointerButtonEvent { + pointer: + PointerInfo { + pointer_id, + pointer_type: PointerType::Touch, + .. + }, + state: + PointerState { + position: PhysicalPosition { x, y }, + count: 2, + .. + }, + .. + }) => { + if let GestureState::Hover = viewer.gestures { + viewer.gestures = GestureState::DragZoomAbout { + pointer_id, + about: Point { x, y }, + y, + } + } + } + PointerEvent::Down(PointerButtonEvent { + pointer: + PointerInfo { + pointer_id, + pointer_type: PointerType::Mouse, + .. + }, + button: Some(PointerButton::Auxiliary), + state: + PointerState { + position: PhysicalPosition { x, y }, + .. + }, + }) => { + if let GestureState::Hover = viewer.gestures { + viewer.gestures = GestureState::DragZoomAbout { + pointer_id, + about: Point { x, y }, + y, + } + } + } + PointerEvent::Down(PointerButtonEvent { + pointer: + PointerInfo { + pointer_id, + pointer_type: PointerType::Mouse, + .. + }, + button: Some(PointerButton::Secondary), + state: + PointerState { + position: PhysicalPosition { y, .. }, + .. + }, + }) => { + if let GestureState::Hover = viewer.gestures { + viewer.gestures = GestureState::DragZoomAbout { + pointer_id, + about: center, + y, } } } PointerEvent::Move(PointerUpdate { pointer: PointerInfo { pointer_id, .. }, - current, + current: + PointerState { + position: PhysicalPosition { x, y }, + .. + }, .. }) => { - let p = Point { - x: current.position.x, - y: current.position.y, - }; - + let p = Point { x, y }; let dp = viewer.view_transform.inverse() * p; - if viewer.gestures.pan == pointer_id { - viewer.view_transform = viewer - .view_transform - .then_translate(-(viewer.gestures.cursor_pos - p)); - reproject = true; - } else if pointer_id == Some(PointerId::PRIMARY) { - let pick_dist: f64 = window.scale_factor() * 1.414; - let pick_started = Instant::now(); - - let pick = viewer - .picking_index - .pick(dp, pick_dist * viewer.view_scale.recip()); - - if viewer.pick != pick { - if let Some(pick) = pick { - let pick_duration = Instant::now() - .saturating_duration_since(pick_started); - eprintln!( - "{:#?}", - viewer.td.info.get_entity(pick).specific - ); - eprintln!("Pick took {pick_duration:?}"); - } - viewer.pick = pick; + match &mut viewer.gestures { + GestureState::Pan { + pointer_id: g_pointer, + pos, + } if *g_pointer == pointer_id => { + viewer.view_transform = + viewer.view_transform.then_translate(-(*pos - p)); + reproject = true; + *pos = p; + } + GestureState::DragZoomAbout { + pointer_id: g_pointer, + about, + y, + } if *g_pointer == pointer_id => { + let sd = 1. + (p.y - *y) * (400.0 * scale_factor).recip(); + viewer.view_transform = + viewer.view_transform.then_scale_about(sd, *about); + viewer.view_scale *= sd; reproject = true; + *y = p.y; } + GestureState::Hover + if pointer_id == Some(PointerId::PRIMARY) => + { + let pick_dist: f64 = window.scale_factor() * 1.414; + let pick_started = Instant::now(); + + let pick = viewer + .picking_index + .pick(dp, pick_dist * viewer.view_scale.recip()); + + if viewer.pick != pick { + if let Some(pick) = pick { + let pick_duration = Instant::now() + .saturating_duration_since(pick_started); + eprintln!( + "{:#?}", + viewer.td.info.get_entity(pick).specific + ); + eprintln!("Pick took {pick_duration:?}"); + } + viewer.pick = pick; + reproject = true; + } + } + _ => {} } - - viewer.gestures.cursor_pos = p; } PointerEvent::Up(PointerButtonEvent { pointer: PointerInfo { pointer_id, .. }, .. }) | PointerEvent::Cancel(PointerInfo { pointer_id, .. }) => { - if viewer.gestures.pan == pointer_id { - viewer.gestures.pan = None; + match viewer.gestures { + GestureState::Pan { + pointer_id: g_pointer, + .. + } + | GestureState::DragZoomAbout { + pointer_id: g_pointer, + .. + } if g_pointer == pointer_id => { + viewer.gestures = GestureState::Hover; + } + _ => {} } } - PointerEvent::Scroll(PointerScrollEvent { delta, .. }) => { + PointerEvent::Scroll(PointerScrollEvent { + delta, + state: + PointerState { + position: PhysicalPosition { x, y }, + .. + }, + .. + }) => { let d = match delta { ScrollDelta::LineDelta(_, y) => y as f64 * 0.1, ScrollDelta::PixelDelta(pd) => pd.y * 0.05, _ => 0., }; - viewer.view_transform = viewer .view_transform - .then_scale_about(1. + d, viewer.gestures.cursor_pos); + .then_scale_about(1. + d, Point { x, y }); viewer.view_scale *= 1. + d; reproject = true; } + PointerEvent::Gesture(PointerGestureEvent { + gesture: PointerGesture::Pinch(d), + state: + PointerState { + position: PhysicalPosition { x, y }, + .. + }, + .. + }) => { + viewer.view_transform = viewer + .view_transform + .then_scale_about(1. + d as f64, Point { x, y }); + viewer.view_scale *= 1. + d as f64; + reproject = true; + } _ => {} } } @@ -472,20 +629,6 @@ impl ApplicationHandler for TabulonDxfViewer<'_> { reproject = true; } - WindowEvent::PinchGesture { delta: d, .. } => { - let Some(viewer) = &mut self.viewer else { - return; - }; - - viewer.view_transform = viewer - .view_transform - .then_translate(-viewer.gestures.cursor_pos.to_vec2()) - .then_scale(1. + d) - .then_translate(viewer.gestures.cursor_pos.to_vec2()); - viewer.view_scale *= 1. + d; - reproject = true; - } - WindowEvent::RedrawRequested => { let wgpu::SurfaceConfiguration { width, height, .. } = surface.config;