From 6fcd50289afb0b2b41664cf868e19d73a742c818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20Vald=C3=A9s?= Date: Fri, 21 Feb 2020 13:29:55 +0100 Subject: [PATCH] Added particle trail effect --- src/renderer/cursor_renderer/cursor_vfx.rs | 111 ++++++++++++++++++++- src/renderer/cursor_renderer/mod.rs | 3 +- 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/renderer/cursor_renderer/cursor_vfx.rs b/src/renderer/cursor_renderer/cursor_vfx.rs index 03da3de..c87bde0 100644 --- a/src/renderer/cursor_renderer/cursor_vfx.rs +++ b/src/renderer/cursor_renderer/cursor_vfx.rs @@ -1,7 +1,7 @@ use skulpin::skia_safe::{paint::Style, BlendMode, Canvas, Color, Paint, Point, Rect}; -use crate::editor::{Colors, Cursor}; use super::animation_utils::*; +use crate::editor::{Colors, Cursor}; pub trait CursorVFX { fn update(&mut self, current_cursor_destination: Point, dt: f32) -> bool; @@ -35,7 +35,7 @@ impl PointHighlight { impl CursorVFX for PointHighlight { fn update(&mut self, _current_cursor_destination: Point, dt: f32) -> bool { self.t = (self.t + dt * 5.0).min(1.0); // TODO - speed config - return self.t < 1.0; + self.t < 1.0 } fn restart(&mut self, position: Point) { @@ -82,3 +82,110 @@ impl CursorVFX for PointHighlight { } } } + +#[derive(Clone)] +struct ParticleData { + pos: Point, + speed: Point, + lifetime: f32, +} + +pub struct ParticleTrail { + particles: Vec, + previous_cursor_dest: Point, +} + +impl ParticleTrail { + pub fn new() -> ParticleTrail { + ParticleTrail { + particles: vec![], + previous_cursor_dest: Point::new(0.0, 0.0), + } + } + + fn add_particle(&mut self, pos: Point, speed: Point, lifetime: f32) { + self.particles.push(ParticleData { + pos, + speed, + lifetime, + }); + } + + // Note this method doesn't keep particles in order + fn remove_particle(&mut self, idx: usize) { + self.particles[idx] = self.particles[self.particles.len() - 1].clone(); + self.particles.pop(); + } +} + +const PARTICLE_DENSITY: f32 = 0.05; // TODO - density should be based on font size too +const PARTICLE_LIFETIME: f32 = 1.0; + +impl CursorVFX for ParticleTrail { + fn update(&mut self, current_cursor_dest: Point, dt: f32) -> bool { + // Update lifetimes and remove dead particles + let mut i = 0; + while i < self.particles.len() { + let particle: &mut ParticleData = &mut self.particles[i]; + particle.lifetime -= dt; + if particle.lifetime <= 0.0 { + self.remove_particle(i); + } else { + i += 1; + } + } + + for i in 0..self.particles.len() { + let particle = &mut self.particles[i]; + particle.pos += particle.speed * dt; + } + + // Spawn new particles + if current_cursor_dest != self.previous_cursor_dest { + let travel = current_cursor_dest - self.previous_cursor_dest; + let travel_distance = travel.length(); + let particle_count = (travel_distance * PARTICLE_DENSITY) as usize; + + let prev_p = self.previous_cursor_dest; + for i in 0..particle_count { + let t = i as f32 / (particle_count as f32 - 1.0); + let rand_seed = t * std::f32::consts::E * 20.0; + let rand = Point::new(rand_seed.sin(), rand_seed.cos()); + + let pos = prev_p + travel * (t + 0.3 * rand.x / particle_count as f32); + + self.add_particle(pos, rand * 10.0, t * PARTICLE_LIFETIME); + } + + self.previous_cursor_dest = current_cursor_dest; + } + + // Keep animating as long as there are particles alive + !self.particles.is_empty() + } + + fn restart(&mut self, position: Point) {} + + fn render(&self, canvas: &mut Canvas, cursor: &Cursor, colors: &Colors, font_size: (f32, f32)) { + let mut paint = Paint::new(skulpin::skia_safe::colors::WHITE, None); + paint.set_style(Style::Stroke); + paint.set_stroke_width(font_size.1 * 0.2); + let base_color: Color = cursor.background(&colors).to_color(); + + paint.set_blend_mode(BlendMode::SrcOver); + + self.particles.iter().for_each(|particle| { + let l = particle.lifetime / PARTICLE_LIFETIME; + let alpha = (l * 255.0) as u8; + let color = Color::from_argb(alpha, base_color.r(), base_color.g(), base_color.b()); + paint.set_color(color); + + let radius = font_size.0 * 0.5 * l; + let hr = radius * 0.5; + + let rect = Rect::from_xywh(particle.pos.x - hr, particle.pos.y - hr, radius, radius); + canvas.draw_oval(&rect, &paint); + //canvas.draw_rect(&rect, &paint); + }); + } +} diff --git a/src/renderer/cursor_renderer/mod.rs b/src/renderer/cursor_renderer/mod.rs index 7561235..625108e 100644 --- a/src/renderer/cursor_renderer/mod.rs +++ b/src/renderer/cursor_renderer/mod.rs @@ -194,7 +194,8 @@ impl CursorRenderer { command_line_delay: 0, blink_status: BlinkStatus::new(), previous_cursor_shape: None, - cursor_vfx: Box::new(PointHighlight::new(Point{x:0.0, y:0.0}, HighlightMode::Ripple)), + //cursor_vfx: Box::new(PointHighlight::new(Point{x:0.0, y:0.0}, HighlightMode::Ripple)), + cursor_vfx: Box::new(ParticleTrail::new()), }; renderer.set_cursor_shape(&CursorShape::Block, DEFAULT_CELL_PERCENTAGE); renderer