diff options
author | Ville Hakulinen <ville.hakulinen@gmail.com> | 2020-07-22 21:19:00 +0300 |
---|---|---|
committer | Ville Hakulinen <ville.hakulinen@gmail.com> | 2020-07-22 23:32:15 +0300 |
commit | 356c8849630e66885f5182696d6683da4384b61f (patch) | |
tree | 0a39165079cc07830406571cb112077a98293d8d | |
parent | 0ac2cef5f1eaafe64dcd2dae788e970ba9bc806d (diff) |
First iteration of animated cursor
-rw-r--r-- | src/ui/grid/context.rs | 91 | ||||
-rw-r--r-- | src/ui/grid/cursor.rs | 87 | ||||
-rw-r--r-- | src/ui/grid/grid.rs | 97 | ||||
-rw-r--r-- | src/ui/grid/mod.rs | 1 | ||||
-rw-r--r-- | src/ui/state.rs | 2 | ||||
-rw-r--r-- | src/ui/ui.rs | 13 |
6 files changed, 176 insertions, 115 deletions
diff --git a/src/ui/grid/context.rs b/src/ui/grid/context.rs index c4ecc04..3c563d6 100644 --- a/src/ui/grid/context.rs +++ b/src/ui/grid/context.rs @@ -3,8 +3,9 @@ use gtk::prelude::*; use gtk::DrawingArea; use pango; -use crate::ui::color::{Color, Highlight, HlDefs}; +use crate::ui::color::HlDefs; use crate::ui::font::Font; +use crate::ui::grid::cursor::Cursor; use crate::ui::grid::render; use crate::ui::grid::row::Row; @@ -20,24 +21,14 @@ pub struct Context { /// Internal grid. pub rows: Vec<Row>, - /// Cursor, (row, col): - pub cursor: (u64, u64), - /// Cursor alpha color. Used to make the cursor blink. - pub cursor_alpha: f64, - /// The duration of the cursor blink - pub cursor_blink_on: u64, - /// Width of the cursor. - pub cursor_cell_percentage: f64, - /// Color of the cursor. - pub cursor_color: Color, + pub cursor: Cursor, + /// Cairo context for cursor. + pub cursor_context: cairo::Context, + /// If the current status is busy or not. When busy, the cursor is not /// drawn (like when in terminal mode in inserting text). pub busy: bool, - /// Cairo context for cursor. - pub cursor_context: cairo::Context, - /// Current highlight. - pub current_hl: Highlight, /// If the grid that this context belongs to is active or not. pub active: bool, @@ -104,15 +95,10 @@ impl Context { cell_metrics_update: None, rows: vec![], - cursor: (0, 0), - cursor_alpha: 1.0, - cursor_blink_on: 0, - cursor_cell_percentage: 1.0, - cursor_color: Color::from_u64(0), - busy: false, + cursor: Cursor::default(), cursor_context, - current_hl: Highlight::default(), + busy: false, active: false, queue_draw_area: vec![], @@ -223,10 +209,10 @@ impl Context { pub fn get_cursor_rect(&self) -> (i32, i32, i32, i32) { let double_width = self .rows - .get(self.cursor.0 as usize) + .get(self.cursor.pos.0 as usize) .and_then(|row| { Some( - row.cell_at(self.cursor.1 as usize) + row.cell_at(self.cursor.pos.1 as usize) .map(|c| c.double_width) .unwrap_or(false), ) @@ -237,8 +223,8 @@ impl Context { let (x, y) = render::get_coords( cm.height, cm.width, - self.cursor.0 as f64, - self.cursor.1 as f64, + self.cursor.pos.0, + self.cursor.pos.1, ); ( x.floor() as i32, @@ -251,6 +237,59 @@ impl Context { cm.height.ceil() as i32, ) } + + pub fn cursor_goto(&mut self, row: u64, col: u64, clock: &gdk::FrameClock) { + // Clear old cursor position. + let (x, y, w, h) = self.get_cursor_rect(); + self.queue_draw_area.push(( + f64::from(x), + f64::from(y), + f64::from(w), + f64::from(h), + )); + self.cursor.goto(row as f64, col as f64, clock); + + // Mark the new cursor position to be drawn. + let (x, y, w, h) = self.get_cursor_rect(); + self.queue_draw_area.push(( + f64::from(x), + f64::from(y), + f64::from(w), + f64::from(h), + )); + } + + pub fn tick(&mut self, da: &DrawingArea, clock: &gdk::FrameClock) { + let (x, y, w, h) = self.get_cursor_rect(); + da.queue_draw_area(x, y, w, h); + + self.cursor.tick(clock); + + let (x, y, w, h) = self.get_cursor_rect(); + + let mut alpha = self.cursor.alpha; + if alpha > 1.0 { + alpha = 2.0 - alpha; + } + + let cr = &self.cursor_context; + cr.save(); + cr.rectangle(0.0, 0.0, 100.0, 100.0); + cr.set_operator(cairo::Operator::Source); + cr.set_source_rgba( + self.cursor.color.r, + self.cursor.color.g, + self.cursor.color.b, + alpha, + ); + cr.fill(); + cr.restore(); + + // Don't use the queue_draw_area, because those draws will only + // happen once nvim sends 'flush' event. This draw needs to happen + // on each tick so the cursor blinks. + da.queue_draw_area(x, y, w, h); + } } /// Cell metrics tells the size (and other metrics) of the cells in a grid. diff --git a/src/ui/grid/cursor.rs b/src/ui/grid/cursor.rs new file mode 100644 index 0000000..9290159 --- /dev/null +++ b/src/ui/grid/cursor.rs @@ -0,0 +1,87 @@ +use gdk; + +use crate::ui::color::Color; + +#[derive(Default)] +struct Animation { + start: (f64, f64), + end: (f64, f64), + start_time: i64, + end_time: i64, +} + +#[derive(Default)] +pub struct Cursor { + /// Position, (row, col). + pub pos: (f64, f64), + got_first_goto: bool, + animation: Animation, + + /// Alpha color. Used to make the cursor blink. + pub alpha: f64, + /// The duration of the blink. + pub blink_on: u64, + /// Width of the cursor. + pub cell_percentage: f64, + /// Color of the cursor. + pub color: Color, +} + +impl Cursor { + pub fn goto(&mut self, row: f64, col: f64, clock: &gdk::FrameClock) { + if !self.got_first_goto { + self.pos = (row, col); + self.got_first_goto = true; + } + + let now = clock.get_frame_time(); + let duration = 200; + self.animation = Animation { + start: self.pos, + end: (row, col), + start_time: now, + end_time: now + 1000 * duration, + }; + } + + pub fn tick(&mut self, clock: &gdk::FrameClock) { + self.blink(); + self.animate_position(clock); + } + + fn blink(&mut self) { + // If we dont need to blink, return. + if self.blink_on == 0 { + return; + } + + // Assuming a 60hz framerate + self.alpha += 100.0 / (6.0 * self.blink_on as f64); + + if self.alpha > 2.0 { + self.alpha = 0.0; + } + } + + fn animate_position(&mut self, clock: &gdk::FrameClock) { + let now = clock.get_frame_time(); + if now < self.animation.end_time && self.pos != self.animation.end { + let mut t = (now - self.animation.start_time) as f64 + / (self.animation.end_time - self.animation.start_time) as f64; + t = ease_out_cubic(t); + self.pos.0 = self.animation.start.0 + + t * (self.animation.end.0 - self.animation.start.0); + self.pos.1 = self.animation.start.1 + + t * (self.animation.end.1 - self.animation.start.1); + } else if self.pos != self.animation.end { + self.pos = self.animation.end; + } + } +} + +/// From clutter-easing.c, based on Robert Penner's +/// infamous easing equations, MIT license. +fn ease_out_cubic(t: f64) -> f64 { + let p = t - 1f64; + return p * p * p + 1f64; +} diff --git a/src/ui/grid/grid.rs b/src/ui/grid/grid.rs index c4dbc3b..412eff5 100644 --- a/src/ui/grid/grid.rs +++ b/src/ui/grid/grid.rs @@ -105,10 +105,16 @@ impl Grid { eb.add_events(EventMask::SCROLL_MASK); eb.add(&da); + da.add_tick_callback(clone!(ctx => move |da, clock| { + let mut ctx = ctx.borrow_mut(); + ctx.tick(da, clock); + glib::Continue(true) + })); + Grid { id, - da: da, - eb: eb, + da, + eb, context: ctx, drag_position: Rc::new(RefCell::new((0, 0))), im_context: None, @@ -124,12 +130,12 @@ impl Grid { if let Some(Some(cell)) = ctx .rows - .get(ctx.cursor.0 as usize) - .map(|row| row.cell_at(ctx.cursor.1 as usize)) + .get(ctx.cursor.pos.0 as usize) + .map(|row| row.cell_at(ctx.cursor.pos.1 as usize)) { // If cursor isn't blinking, drawn the inverted cell into // the cursor's cairo context. - if ctx.cursor_blink_on == 0 { + if ctx.cursor.blink_on == 0 { render::cursor_cell( &ctx.cursor_context, &self.da.get_pango_context().unwrap(), @@ -141,7 +147,7 @@ impl Grid { // Update cursor color. let hl = hl_defs.get(&cell.hl_id).unwrap(); - ctx.cursor_color = hl.foreground.unwrap_or(hl_defs.default_fg); + ctx.cursor.color = hl.foreground.unwrap_or(hl_defs.default_fg); } while let Some(area) = ctx.queue_draw_area.pop() { @@ -334,34 +340,17 @@ impl Grid { } pub fn cursor_goto(&self, row: u64, col: u64) { + let clock = self.da.get_frame_clock().unwrap(); let mut ctx = self.context.borrow_mut(); + ctx.cursor_goto(row, col, &clock); - // Clear old cursor position. - let (x, y, w, h) = ctx.get_cursor_rect(); - ctx.queue_draw_area.push(( - f64::from(x), - f64::from(y), - f64::from(w), - f64::from(h), - )); - ctx.cursor.0 = row; - ctx.cursor.1 = col; - - // Mark the new cursor position to be drawn. - let (x, y, w, h) = ctx.get_cursor_rect(); - ctx.queue_draw_area.push(( - f64::from(x), - f64::from(y), - f64::from(w), - f64::from(h), - )); - + let (x, y, width, height) = ctx.get_cursor_rect(); if let Some(ref im_context) = self.im_context { let rect = gdk::Rectangle { - x: x, - y: y, - width: w, - height: h, + x, + y, + width, + height, }; im_context.set_cursor_location(&rect); } @@ -440,48 +429,6 @@ impl Grid { ctx.active = active; } - pub fn tick(&self) { - let mut ctx = self.context.borrow_mut(); - - // If we dont need to blink, return. Otherwise, handle the cursor - // blining. - if ctx.cursor_blink_on == 0 { - return; - } - - // Assuming a 60hz framerate - ctx.cursor_alpha += 100.0 / (6.0 * ctx.cursor_blink_on as f64); - - if ctx.cursor_alpha > 2.0 { - ctx.cursor_alpha = 0.0; - } - - let (x, y, w, h) = ctx.get_cursor_rect(); - - let mut alpha = ctx.cursor_alpha; - if alpha > 1.0 { - alpha = 2.0 - alpha; - } - - let cr = &ctx.cursor_context; - cr.save(); - cr.rectangle(0.0, 0.0, 100.0, 100.0); - cr.set_operator(cairo::Operator::Source); - cr.set_source_rgba( - ctx.cursor_color.r, - ctx.cursor_color.g, - ctx.cursor_color.b, - alpha, - ); - cr.fill(); - cr.restore(); - - // Don't use the ctx.queue_draw_area, because those draws will only - // happen once nvim sends 'flush' event. This draw needs to happen - // on each tick so the cursor blinks. - self.da.queue_draw_area(x, y, w, h); - } - /// Set a new font and line space. This will likely change the cell metrics. /// Use `calc_size` to receive the updated size (cols and rows) of the grid. pub fn update_cell_metrics( @@ -509,8 +456,8 @@ impl Grid { pub fn set_mode(&self, mode: &ModeInfo) { let mut ctx = self.context.borrow_mut(); - ctx.cursor_blink_on = mode.blink_on; - ctx.cursor_cell_percentage = mode.cell_percentage; + ctx.cursor.blink_on = mode.blink_on; + ctx.cursor.cell_percentage = mode.cell_percentage; } pub fn set_busy(&self, busy: bool) { @@ -539,7 +486,7 @@ fn drawingarea_draw(cr: &cairo::Context, ctx: &mut Context) { cr.rectangle( f64::from(x), f64::from(y), - f64::from(w) * ctx.cursor_cell_percentage, + f64::from(w) * ctx.cursor.cell_percentage, f64::from(h), ); let surface = ctx.cursor_context.get_target(); diff --git a/src/ui/grid/mod.rs b/src/ui/grid/mod.rs index f227206..0dc686e 100644 --- a/src/ui/grid/mod.rs +++ b/src/ui/grid/mod.rs @@ -1,4 +1,5 @@ mod context; +mod cursor; mod grid; mod render; mod row; diff --git a/src/ui/state.rs b/src/ui/state.rs index 390a803..dba0206 100644 --- a/src/ui/state.rs +++ b/src/ui/state.rs @@ -137,7 +137,7 @@ impl UIState { let grid = self.grids.get(&self.current_grid).unwrap(); grid.set_active(false); - grid.tick(); // Trick the grid to invalide the cursor's rect. + //grid.tick(); // Trick the grid to invalide the cursor's rect. self.current_grid = grid_id; diff --git a/src/ui/ui.rs b/src/ui/ui.rs index d1849df..bf79cbb 100644 --- a/src/ui/ui.rs +++ b/src/ui/ui.rs @@ -257,19 +257,6 @@ impl UI { nvim, } = self; - gtk::timeout_add( - 33, - clone!(state => move || { - let state = state.borrow(); - // Tick the current active grid. - let grid = - state.grids.get(&state.current_grid).unwrap(); - grid.tick(); - - glib::Continue(true) - }), - ); - rx.attach(None, move |message| { match message { // Handle a notify. |