Work on framerate-independent cursor animation

Has several different easing methods, but defaults to linear for now.
ease_out_quad was also pretty good, but which specific method we choose
will be more a personal preference than an objective thing
macos-click-through
Jon Valdés 5 years ago
parent cbe0eab3c7
commit 945ef975f6

@ -0,0 +1,41 @@
use skulpin::skia_safe::Point;
#[allow(dead_code)]
pub fn ease_linear(t: f32) -> f32 {
t
}
#[allow(dead_code)]
pub fn ease_in_quad(t: f32) -> f32 {
t * t
}
#[allow(dead_code)]
pub fn ease_out_quad(t: f32) -> f32 {
-t * (t - 2.0)
}
#[allow(dead_code)]
pub fn ease_in_out_quad(t: f32) -> f32 {
if t < 0.5 {
2.0 * t * t
} else {
let n = t * 2.0 - 1.0;
-0.5 * (n * (n - 2.0) - 1.0)
}
}
pub fn lerp(start: f32, end: f32, t: f32) -> f32 {
start + (end - start) * t
}
pub fn ease(ease_func: fn(f32) -> f32, start: f32, end: f32, t: f32) -> f32 {
lerp(start, end, ease_func(t))
}
pub fn ease_point(ease_func: fn(f32) -> f32, start: Point, end: Point, t: f32) -> Point {
Point {
x: ease(ease_func, start.x, end.x, t),
y: ease(ease_func, start.y, end.y, t),
}
}

@ -2,12 +2,16 @@ use std::time::{Duration, Instant};
use skulpin::skia_safe::{Canvas, Paint, Path, Point}; use skulpin::skia_safe::{Canvas, Paint, Path, Point};
use crate::settings::SETTINGS;
use crate::renderer::animation_utils::*;
use crate::renderer::CachingShaper; use crate::renderer::CachingShaper;
use crate::editor::{EDITOR, Colors, Cursor, CursorShape}; use crate::editor::{EDITOR, Colors, Cursor, CursorShape};
use crate::redraw_scheduler::REDRAW_SCHEDULER; use crate::redraw_scheduler::REDRAW_SCHEDULER;
const AVERAGE_MOTION_PERCENTAGE: f32 = 0.7;
const MOTION_PERCENTAGE_SPREAD: f32 = 0.5;
const BASE_ANIMATION_LENGTH_SECONDS: f32 = 0.06;
const CURSOR_TRAIL_SIZE: f32 = 0.6;
const COMMAND_LINE_DELAY_FRAMES: u64 = 5; const COMMAND_LINE_DELAY_FRAMES: u64 = 5;
const DEFAULT_CELL_PERCENTAGE: f32 = 1.0 / 8.0; const DEFAULT_CELL_PERCENTAGE: f32 = 1.0 / 8.0;
@ -85,47 +89,67 @@ impl BlinkStatus {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Corner { pub struct Corner {
pub current_position: Point, start_position: Point,
pub relative_position: Point, current_position: Point,
relative_position: Point,
previous_destination: Point,
t: f32,
} }
impl Corner { impl Corner {
pub fn new(relative_position: Point) -> Corner { pub fn new() -> Corner {
Corner { Corner {
start_position: Point::new(0.0, 0.0),
current_position: Point::new(0.0, 0.0), current_position: Point::new(0.0, 0.0),
relative_position relative_position: Point::new(0.0, 0.0),
previous_destination: Point::new(-1000.0, -1000.0),
t: 0.0,
} }
} }
pub fn update(&mut self, font_dimensions: Point, destination: Point) -> bool { pub fn update(&mut self, font_dimensions: Point, destination: Point, dt: f32) -> bool {
let relative_scaled_position: Point = if destination != self.previous_destination {
(self.relative_position.x * font_dimensions.x, self.relative_position.y * font_dimensions.y).into(); self.t = 0.0;
self.start_position = self.current_position;
self.previous_destination = destination;
}
let finished_animating = self.t >= 1.0; // Check first if animation's over, so we still render one frame at t == 1.0
if self.t <= 1.0 {
// Calculate window-space destination for corner
let relative_scaled_position: Point = (
self.relative_position.x * font_dimensions.x,
self.relative_position.y * font_dimensions.y,
)
.into();
let corner_destination = destination + relative_scaled_position; let corner_destination = destination + relative_scaled_position;
let delta = corner_destination - self.current_position; // Calculate how much a corner will be lagging behind based on how much it's aligned
// with the direction of motion. Corners in front will move faster than corners in the
// back
let corner_direction = {
let mut d = destination - self.current_position;
d.normalize();
d
};
if delta.length() > 0.0 { let direction_alignment = corner_direction.dot(self.relative_position);
// Project relative_scaled_position (actual possition of the corner relative to the
// center of the cursor) onto the remaining distance vector. This gives us the relative
// distance to the destination along the delta vector which we can then use to scale the
// motion_percentage.
let motion_scale = delta.dot(relative_scaled_position) / delta.length() / font_dimensions.length();
// The motion_percentage is then equal to the motion_scale factor times the self.current_position = ease_point(
// MOTION_PERCENTAGE_SPREAD and added to the AVERAGE_MOTION_PERCENTAGE. This way all of ease_linear,
// the percentages are positive and spread out by the spread constant. self.start_position,
let motion_percentage = motion_scale * MOTION_PERCENTAGE_SPREAD + AVERAGE_MOTION_PERCENTAGE; corner_destination,
self.t);
// Then the current_position is animated by taking the delta vector, multiplying it by let corner_dt = dt * lerp(1.0, 1.0 - CURSOR_TRAIL_SIZE, direction_alignment);
// the motion_percentage and adding the resulting value to the current position causing
// the cursor to "jump" toward the target destination. Since further away corners jump self.t = (self.t + corner_dt / BASE_ANIMATION_LENGTH_SECONDS).min(1.0)
// slower, the cursor appears to smear toward the destination in a satisfying and
// visually trackable way.
let delta = corner_destination - self.current_position;
self.current_position += delta * motion_percentage;
} }
delta.length() > 0.001 !finished_animating
} }
} }
@ -139,7 +163,7 @@ pub struct CursorRenderer {
impl CursorRenderer { impl CursorRenderer {
pub fn new() -> CursorRenderer { pub fn new() -> CursorRenderer {
let mut renderer = CursorRenderer { let mut renderer = CursorRenderer {
corners: vec![Corner::new((0.0, 0.0).into()); 4], corners: vec![Corner::new(); 4],
previous_position: (0, 0), previous_position: (0, 0),
command_line_delay: 0, command_line_delay: 0,
blink_status: BlinkStatus::new() blink_status: BlinkStatus::new()
@ -221,10 +245,12 @@ impl CursorRenderer {
self.set_cursor_shape(&cursor.shape, cursor.cell_percentage.unwrap_or(DEFAULT_CELL_PERCENTAGE)); self.set_cursor_shape(&cursor.shape, cursor.cell_percentage.unwrap_or(DEFAULT_CELL_PERCENTAGE));
let dt = 1.0 / (SETTINGS.get("refresh_rate").read_u16() as f32);
let mut animating = false; let mut animating = false;
if !center_destination.is_zero() { if !center_destination.is_zero() {
for corner in self.corners.iter_mut() { for corner in self.corners.iter_mut() {
let corner_animating = corner.update(font_dimensions, center_destination); let corner_animating = corner.update(font_dimensions, center_destination, dt);
animating = animating || corner_animating; animating = animating || corner_animating;
} }
} }

@ -5,6 +5,7 @@ use skulpin::skia_safe::{Canvas, Paint, Surface, Budgeted, Rect, colors, dash_pa
use skulpin::skia_safe::gpu::SurfaceOrigin; use skulpin::skia_safe::gpu::SurfaceOrigin;
use log::trace; use log::trace;
mod animation_utils;
mod caching_shaper; mod caching_shaper;
mod cursor_renderer; mod cursor_renderer;

Loading…
Cancel
Save