diff --git a/crates/project/src/configuration.rs b/crates/project/src/configuration.rs index 1e9fae991c..b4c93158dc 100644 --- a/crates/project/src/configuration.rs +++ b/crates/project/src/configuration.rs @@ -1,5 +1,5 @@ use std::{ - ops::{Add, Div, Mul, Sub}, + ops::{Add, Div, Mul, Sub, SubAssign}, path::Path, }; @@ -148,6 +148,16 @@ impl> Div> for XY { } } +impl SubAssign for XY +where + T: SubAssign + Copy, +{ + fn sub_assign(&mut self, rhs: Self) { + self.x -= rhs.x; + self.y -= rhs.y; + } +} + impl Into> for XY { fn into(self) -> XY { XY { diff --git a/crates/rendering/src/coord.rs b/crates/rendering/src/coord.rs index 29cc5046f2..2f067f11be 100644 --- a/crates/rendering/src/coord.rs +++ b/crates/rendering/src/coord.rs @@ -1,28 +1,44 @@ -use std::ops::{Add, Deref, Mul, Sub}; +use std::ops::{Add, Deref, Mul, Sub, SubAssign}; use cap_project::{ProjectConfiguration, XY}; use crate::{ProjectUniforms, RenderOptions, zoom::InterpolatedZoom}; +/// Coordinate system for display frames +/// `(0, 0)` is the top left of the frame, +/// not the top left of the display #[derive(Default, Clone, Copy, Debug)] pub struct RawDisplaySpace; -// raw cursor data +/// Same as `RawDisplaySpace` except UV (0<->1) +/// Cursor positions are stored as this #[derive(Default, Clone, Copy, Debug)] pub struct RawDisplayUVSpace; +/// Coordinate system for display frames after they've been croppped. +/// `(0, 0)` is the top left of the crop. +/// The top left of the raw display would be negative #[derive(Default, Clone, Copy, Debug)] pub struct CroppedDisplaySpace; +/// Coordinate system for rendered frames. +/// `(0, 0)` is the top left of the final frame. +/// Going from CroppedDisplaySpace to FrameSpace will account for padding. +/// Cursor positions are defined in this coordinate space. +/// +/// Rendered frame size is calculated before inner layouts, +/// so this can be used during layout calculations. #[derive(Default, Clone, Copy, Debug)] pub struct FrameSpace; +/// Coordinate system for FrameSpace coordinates after zoom has been interpolated and applied. +/// (0, 0) is the top left of the frame itself, +/// so after a zoom is applied the original top left will likely be negative. +/// +/// Used to account for zoom in calculating cursor positions. #[derive(Default, Clone, Copy, Debug)] pub struct ZoomedFrameSpace; -#[derive(Default, Clone, Copy, Debug)] -pub struct TransformedDisplaySpace; - #[derive(Clone, Copy, Debug, Default)] pub struct Coord { pub coord: XY, @@ -160,3 +176,9 @@ impl Mul for Coord { } } } + +impl SubAssign for Coord { + fn sub_assign(&mut self, rhs: Self) { + self.coord -= rhs.coord; + } +} diff --git a/crates/rendering/src/layers/cursor.rs b/crates/rendering/src/layers/cursor.rs index fd6f912b96..94e9ae2a2b 100644 --- a/crates/rendering/src/layers/cursor.rs +++ b/crates/rendering/src/layers/cursor.rs @@ -8,8 +8,8 @@ use tracing::error; use wgpu::{BindGroup, FilterMode, include_wgsl, util::DeviceExt}; use crate::{ - DecodedSegmentFrames, ProjectUniforms, RenderVideoConstants, STANDARD_CURSOR_HEIGHT, - zoom::InterpolatedZoom, + Coord, DecodedSegmentFrames, FrameSpace, ProjectUniforms, RenderVideoConstants, + STANDARD_CURSOR_HEIGHT, zoom::InterpolatedZoom, }; const CURSOR_CLICK_DURATION: f64 = 0.25; @@ -288,7 +288,7 @@ impl CursorLayer { return; }; - let cursor_base_size_px = { + let size = { let cursor_texture_size = cursor_texture.texture.size(); let cursor_texture_size_aspect = cursor_texture_size.width as f32 / cursor_texture_size.height as f32; @@ -301,47 +301,54 @@ impl CursorLayer { let factor = STANDARD_CURSOR_HEIGHT / constants.options.screen_size.y as f32 * uniforms.output_size.1 as f32; - let cursor_size_constant = - factor * cursor_size_percentage * zoom.display_amount() as f32; + let cursor_size_constant = factor * cursor_size_percentage; - if cursor_texture_size_aspect > 1.0 { + Coord::::new(if cursor_texture_size_aspect > 1.0 { // Wide cursor: base sizing on width to prevent excessive width let width = cursor_size_constant; let height = cursor_size_constant / cursor_texture_size_aspect; - XY::new(width, height) + XY::new(width, height).into() } else { // Tall or square cursor: base sizing on height (current behavior) XY::new( cursor_size_constant * cursor_texture_size_aspect, cursor_size_constant, ) - } + .into() + }) }; - let click_scale_factor = get_click_t(&cursor.clicks, (time_s as f64) * 1000.0) - * (1.0 - CLICK_SHRINK_SIZE) - + CLICK_SHRINK_SIZE; + let hotspot = Coord::::new(size.coord * cursor_texture.hotspot); - let cursor_size_px: XY = (cursor_base_size_px * click_scale_factor).into(); + let position = interpolated_cursor.position.to_frame_space( + &constants.options, + &uniforms.project, + resolution_base, + ) - hotspot; - let hotspot_px = cursor_texture.hotspot * cursor_size_px; + let zoomed_position = position.to_zoomed_frame_space( + &constants.options, + &uniforms.project, + resolution_base, + zoom, + ); - let position = { - let mut frame_position = interpolated_cursor.position.to_frame_space( - &constants.options, - &uniforms.project, - resolution_base, - ); + let zoomed_size = (position + size).to_zoomed_frame_space( + &constants.options, + &uniforms.project, + resolution_base, + zoom, + ) - zoomed_position; - frame_position.coord = frame_position.coord - hotspot_px.map(|v| v as f64); + let click_scale_factor = get_click_t(&cursor.clicks, (time_s as f64) * 1000.0) + // lerp shrink size + * (1.0 - CLICK_SHRINK_SIZE) + + CLICK_SHRINK_SIZE; - frame_position - .to_zoomed_frame_space(&constants.options, &uniforms.project, resolution_base, zoom) - .coord - }; + let cursor_size_px = zoomed_size.coord * click_scale_factor as f64; let uniforms = CursorUniforms { - position: [position.x as f32, position.y as f32], + position: [zoomed_position.x as f32, zoomed_position.y as f32], size: [cursor_size_px.x as f32, cursor_size_px.y as f32], output_size: [uniforms.output_size.0 as f32, uniforms.output_size.1 as f32], screen_bounds: uniforms.display.target_bounds, @@ -526,22 +533,15 @@ impl CursorTexture { // Calculate scale to fit the SVG into the target size while maintaining aspect ratio let scale_x = width as f32 / rtree.size().width(); let scale_y = SVG_CURSOR_RASTERIZED_HEIGHT as f32 / rtree.size().height(); - let scale = scale_x.min(scale_y) * 1.5; - let transform = tiny_skia::Transform::from_row( - scale, - 0.0, - 0.0, - scale, - (SVG_CURSOR_RASTERIZED_HEIGHT / 4) as f32 * -1.0, - (SVG_CURSOR_RASTERIZED_HEIGHT / 4) as f32 * -1.0, - ); + let scale = scale_x.min(scale_y); + let transform = tiny_skia::Transform::from_scale(scale, scale); resvg::render(&rtree, transform, &mut pixmap.as_mut()); let rgba: Vec = pixmap .pixels() .iter() - .flat_map(|p| [p.red(), p.green(), p.red(), p.alpha()]) + .flat_map(|p| [p.red(), p.green(), p.blue(), p.alpha()]) .collect(); Ok(Self::prepare(