summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVille Hakulinen <ville.hakulinen@gmail.com>2020-07-22 21:19:00 +0300
committerVille Hakulinen <ville.hakulinen@gmail.com>2020-07-22 23:32:15 +0300
commit356c8849630e66885f5182696d6683da4384b61f (patch)
tree0a39165079cc07830406571cb112077a98293d8d
parent0ac2cef5f1eaafe64dcd2dae788e970ba9bc806d (diff)
First iteration of animated cursor
-rw-r--r--src/ui/grid/context.rs91
-rw-r--r--src/ui/grid/cursor.rs87
-rw-r--r--src/ui/grid/grid.rs97
-rw-r--r--src/ui/grid/mod.rs1
-rw-r--r--src/ui/state.rs2
-rw-r--r--src/ui/ui.rs13
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.