Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion crates/project/src/configuration.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::{
ops::{Add, Div, Mul, Sub},
ops::{Add, Div, Mul, Sub, SubAssign},
path::Path,
};

Expand Down Expand Up @@ -148,6 +148,16 @@ impl<T: Div<Output = T>> Div<XY<T>> for XY<T> {
}
}

impl<T> SubAssign for XY<T>
where
T: SubAssign + Copy,
{
fn sub_assign(&mut self, rhs: Self) {
self.x -= rhs.x;
self.y -= rhs.y;
}
}

impl Into<XY<f64>> for XY<f32> {
fn into(self) -> XY<f64> {
XY {
Expand Down
32 changes: 27 additions & 5 deletions crates/rendering/src/coord.rs
Original file line number Diff line number Diff line change
@@ -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<TSpace> {
pub coord: XY<f64>,
Expand Down Expand Up @@ -160,3 +176,9 @@ impl<T> Mul<f64> for Coord<T> {
}
}
}

impl<T> SubAssign for Coord<T> {
fn sub_assign(&mut self, rhs: Self) {
self.coord -= rhs.coord;
}
}
70 changes: 35 additions & 35 deletions crates/rendering/src/layers/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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::<FrameSpace>::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::<FrameSpace>::new(size.coord * cursor_texture.hotspot);

let cursor_size_px: XY<f64> = (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,
Expand Down Expand Up @@ -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<u8> = 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(
Expand Down
Loading