neovim-gtk/src/cursor.rs

383 lines
9.7 KiB
Rust
Raw Normal View History

2017-03-22 10:05:10 +00:00
use cairo;
use color;
use ui::UiMutex;
2017-07-08 20:45:55 +00:00
use mode;
use std::sync::{Arc, Weak};
2017-09-07 15:51:12 +00:00
use render;
use render::CellMetrics;
2017-03-22 10:05:10 +00:00
2017-03-22 15:37:34 +00:00
use glib;
struct Alpha(f64);
impl Alpha {
pub fn show(&mut self, step: f64) -> bool {
self.0 += step;
if self.0 > 1.0 {
self.0 = 1.0;
false
} else {
true
}
}
pub fn hide(&mut self, step: f64) -> bool {
self.0 -= step;
if self.0 < 0.0 {
self.0 = 0.0;
false
} else {
true
}
}
}
#[derive(PartialEq)]
enum AnimPhase {
2017-03-24 20:34:03 +00:00
Shown,
Hide,
2017-03-24 20:34:03 +00:00
Hidden,
Show,
NoFocus,
Busy,
}
2017-11-23 14:57:39 +00:00
struct State<CB: CursorRedrawCb> {
alpha: Alpha,
anim_phase: AnimPhase,
2017-11-23 14:57:39 +00:00
redraw_cb: Weak<UiMutex<CB>>,
2017-03-24 20:34:03 +00:00
timer: Option<glib::SourceId>,
}
2018-01-21 20:19:00 +00:00
impl<CB: CursorRedrawCb> State<CB> {
2017-11-23 14:57:39 +00:00
fn new(redraw_cb: Weak<UiMutex<CB>>) -> Self {
2017-03-24 19:23:22 +00:00
State {
alpha: Alpha(1.0),
2017-03-24 20:34:03 +00:00
anim_phase: AnimPhase::Shown,
2017-11-23 14:57:39 +00:00
redraw_cb,
2017-03-24 20:34:03 +00:00
timer: None,
}
}
fn reset_to(&mut self, phase: AnimPhase) {
self.alpha = Alpha(1.0);
self.anim_phase = phase;
2017-12-31 09:47:50 +00:00
if let Some(timer_id) = self.timer.take() {
glib::source_remove(timer_id);
}
}
}
2018-01-21 20:19:00 +00:00
pub trait Cursor {
fn draw(
&self,
ctx: &cairo::Context,
font_ctx: &render::Context,
line_y: f64,
double_width: bool,
color: &color::ColorModel,
2018-01-21 20:19:00 +00:00
);
}
pub struct EmptyCursor;
impl EmptyCursor {
pub fn new() -> Self {
EmptyCursor {}
2018-01-21 20:19:00 +00:00
}
}
impl Cursor for EmptyCursor {
fn draw(
&self,
_ctx: &cairo::Context,
_font_ctx: &render::Context,
_line_y: f64,
_double_width: bool,
_color: &color::ColorModel,
2018-01-21 20:19:00 +00:00
) {
}
}
pub struct BlinkCursor<CB: CursorRedrawCb> {
2017-11-23 14:57:39 +00:00
state: Arc<UiMutex<State<CB>>>,
mode_info: Option<mode::ModeInfo>,
2017-03-22 10:05:10 +00:00
}
2018-01-21 20:19:00 +00:00
impl<CB: CursorRedrawCb + 'static> BlinkCursor<CB> {
2017-11-23 14:57:39 +00:00
pub fn new(redraw_cb: Weak<UiMutex<CB>>) -> Self {
2018-01-21 20:19:00 +00:00
BlinkCursor {
state: Arc::new(UiMutex::new(State::new(redraw_cb))),
mode_info: None,
2018-01-21 20:19:00 +00:00
}
}
pub fn set_mode_info(&mut self, mode_info: Option<mode::ModeInfo>) {
self.mode_info = mode_info;
}
pub fn start(&mut self) {
let blinkwait = self.mode_info
.as_ref()
.and_then(|mi| mi.blinkwait)
.unwrap_or(500);
2017-03-24 20:34:03 +00:00
let state = self.state.clone();
2017-03-26 11:34:38 +00:00
let mut mut_state = self.state.borrow_mut();
mut_state.reset_to(AnimPhase::Shown);
mut_state.timer = Some(glib::timeout_add(
if blinkwait > 0 { blinkwait } else { 500 },
move || anim_step(&state),
));
}
pub fn reset_state(&mut self) {
if self.state.borrow().anim_phase != AnimPhase::Busy {
self.start();
}
2017-03-22 10:05:10 +00:00
}
pub fn enter_focus(&mut self) {
if self.state.borrow().anim_phase != AnimPhase::Busy {
self.start();
}
}
pub fn leave_focus(&mut self) {
if self.state.borrow().anim_phase != AnimPhase::Busy {
self.state.borrow_mut().reset_to(AnimPhase::NoFocus);
}
}
2017-03-22 10:05:10 +00:00
pub fn busy_on(&mut self) {
self.state.borrow_mut().reset_to(AnimPhase::Busy);
}
pub fn busy_off(&mut self) {
self.start();
}
2018-01-21 20:19:00 +00:00
}
2018-01-21 20:19:00 +00:00
impl<CB: CursorRedrawCb> Cursor for BlinkCursor<CB> {
fn draw(
2017-09-07 15:51:12 +00:00
&self,
ctx: &cairo::Context,
font_ctx: &render::Context,
line_y: f64,
double_width: bool,
color: &color::ColorModel,
2017-09-07 15:51:12 +00:00
) {
2017-03-26 11:34:38 +00:00
let state = self.state.borrow();
if state.anim_phase == AnimPhase::Busy {
return;
}
let current_point = ctx.get_current_point();
let bg = color.cursor_bg();
ctx.set_source_rgba(bg.0, bg.1, bg.2, state.alpha.0);
2017-03-22 10:05:10 +00:00
let (y, width, height) = cursor_rect(
self.mode_info.as_ref(),
font_ctx.cell_metrics(),
line_y,
double_width,
);
2017-07-09 09:40:31 +00:00
ctx.rectangle(current_point.0, y, width, height);
if state.anim_phase == AnimPhase::NoFocus {
ctx.stroke();
} else {
ctx.fill();
}
}
}
2017-09-07 15:51:12 +00:00
fn cursor_rect(
mode_info: Option<&mode::ModeInfo>,
2017-09-07 15:51:12 +00:00
cell_metrics: &CellMetrics,
line_y: f64,
double_width: bool,
) -> (f64, f64, f64) {
let &CellMetrics {
line_height,
char_width,
..
} = cell_metrics;
if let Some(mode_info) = mode_info {
2017-07-09 09:40:31 +00:00
match mode_info.cursor_shape() {
None | Some(&mode::CursorShape::Unknown) | Some(&mode::CursorShape::Block) => {
2017-07-09 09:40:31 +00:00
let cursor_width = if double_width {
char_width * 2.0
} else {
char_width
};
(line_y, cursor_width, line_height)
}
Some(&mode::CursorShape::Vertical) => {
2017-07-09 09:40:31 +00:00
let cell_percentage = mode_info.cell_percentage();
let cursor_width = if cell_percentage > 0 {
(char_width * cell_percentage as f64) / 100.0
} else {
char_width
};
(line_y, cursor_width, line_height)
}
Some(&mode::CursorShape::Horizontal) => {
2017-07-09 09:40:31 +00:00
let cell_percentage = mode_info.cell_percentage();
let cursor_width = if double_width {
char_width * 2.0
} else {
char_width
};
if cell_percentage > 0 {
let height = (line_height * cell_percentage as f64) / 100.0;
(line_y + line_height - height, cursor_width, height)
} else {
(line_y, cursor_width, line_height)
}
}
}
} else {
let cursor_width = if double_width {
2017-07-25 14:55:31 +00:00
char_width * 2.0
2017-03-22 10:05:10 +00:00
} else {
2017-07-25 14:55:31 +00:00
char_width
2017-03-22 10:05:10 +00:00
};
2017-07-09 09:40:31 +00:00
(line_y, cursor_width, line_height)
2017-03-22 10:05:10 +00:00
}
}
2017-11-23 14:57:39 +00:00
2018-01-21 20:19:00 +00:00
fn anim_step<CB: CursorRedrawCb + 'static>(state: &Arc<UiMutex<State<CB>>>) -> glib::Continue {
2017-03-26 11:34:38 +00:00
let mut mut_state = state.borrow_mut();
2017-03-24 20:34:03 +00:00
let next_event = match mut_state.anim_phase {
AnimPhase::Shown => {
mut_state.anim_phase = AnimPhase::Hide;
Some(60)
2017-03-24 20:34:03 +00:00
}
2018-01-21 20:19:00 +00:00
AnimPhase::Hide => if !mut_state.alpha.hide(0.3) {
mut_state.anim_phase = AnimPhase::Hidden;
2017-03-24 20:34:03 +00:00
2018-01-21 20:19:00 +00:00
Some(300)
} else {
None
},
2017-03-24 20:34:03 +00:00
AnimPhase::Hidden => {
mut_state.anim_phase = AnimPhase::Show;
Some(60)
2017-03-24 20:34:03 +00:00
}
2018-01-21 20:19:00 +00:00
AnimPhase::Show => if !mut_state.alpha.show(0.3) {
mut_state.anim_phase = AnimPhase::Shown;
2017-03-24 20:34:03 +00:00
2018-01-21 20:19:00 +00:00
Some(500)
} else {
None
},
AnimPhase::NoFocus => None,
AnimPhase::Busy => None,
2017-03-24 20:34:03 +00:00
};
2017-11-23 14:57:39 +00:00
let redraw_cb = mut_state.redraw_cb.upgrade().unwrap();
let mut redraw_cb = redraw_cb.borrow_mut();
redraw_cb.queue_redraw_cursor();
2017-03-24 20:34:03 +00:00
if let Some(timeout) = next_event {
let moved_state = state.clone();
mut_state.timer = Some(glib::timeout_add(timeout, move || anim_step(&moved_state)));
2017-03-24 20:34:03 +00:00
glib::Continue(false)
} else {
glib::Continue(true)
}
}
2018-01-21 20:19:00 +00:00
impl<CB: CursorRedrawCb> Drop for BlinkCursor<CB> {
fn drop(&mut self) {
2017-12-31 09:47:50 +00:00
if let Some(timer_id) = self.state.borrow_mut().timer.take() {
glib::source_remove(timer_id);
2017-03-22 15:37:34 +00:00
}
}
}
2017-07-09 09:40:31 +00:00
2017-11-23 14:57:39 +00:00
pub trait CursorRedrawCb {
fn queue_redraw_cursor(&mut self);
}
2017-07-09 09:40:31 +00:00
#[cfg(test)]
mod tests {
use super::*;
2018-04-01 14:41:17 +00:00
use std::collections::HashMap;
2017-07-09 09:40:31 +00:00
#[test]
fn test_cursor_rect_horizontal() {
2018-04-01 14:41:17 +00:00
let mut mode_data = HashMap::new();
mode_data.insert("cursor_shape".to_owned(), From::from("horizontal"));
mode_data.insert("cell_percentage".to_owned(), From::from(25));
let mode_info = mode::ModeInfo::new(&mode_data).ok();
2017-07-09 09:40:31 +00:00
let char_width = 50.0;
let line_height = 30.0;
let line_y = 0.0;
2017-09-11 15:00:51 +00:00
let (y, width, height) = cursor_rect(
2018-04-01 14:41:17 +00:00
mode_info.as_ref(),
2017-09-11 15:00:51 +00:00
&CellMetrics::new_hw(line_height, char_width),
line_y,
false,
);
2017-07-09 09:40:31 +00:00
assert_eq!(line_y + line_height - line_height / 4.0, y);
assert_eq!(char_width, width);
assert_eq!(line_height / 4.0, height);
}
#[test]
fn test_cursor_rect_horizontal_doublewidth() {
2018-04-01 14:41:17 +00:00
let mut mode_data = HashMap::new();
mode_data.insert("cursor_shape".to_owned(), From::from("horizontal"));
mode_data.insert("cell_percentage".to_owned(), From::from(25));
let mode_info = mode::ModeInfo::new(&mode_data).ok();
2017-07-09 09:40:31 +00:00
let char_width = 50.0;
let line_height = 30.0;
let line_y = 0.0;
2017-09-11 15:00:51 +00:00
let (y, width, height) = cursor_rect(
2018-04-01 14:41:17 +00:00
mode_info.as_ref(),
2017-09-11 15:00:51 +00:00
&CellMetrics::new_hw(line_height, char_width),
line_y,
true,
);
2017-07-09 09:40:31 +00:00
assert_eq!(line_y + line_height - line_height / 4.0, y);
assert_eq!(char_width * 2.0, width);
assert_eq!(line_height / 4.0, height);
}
#[test]
fn test_cursor_rect_vertical() {
2018-04-01 14:41:17 +00:00
let mut mode_data = HashMap::new();
mode_data.insert("cursor_shape".to_owned(), From::from("vertical"));
mode_data.insert("cell_percentage".to_owned(), From::from(25));
let mode_info = mode::ModeInfo::new(&mode_data).ok();
2017-07-09 09:40:31 +00:00
let char_width = 50.0;
let line_height = 30.0;
let line_y = 0.0;
2017-09-11 15:00:51 +00:00
let (y, width, height) = cursor_rect(
2018-04-01 14:41:17 +00:00
mode_info.as_ref(),
2017-09-11 15:00:51 +00:00
&CellMetrics::new_hw(line_height, char_width),
line_y,
false,
);
2017-07-09 09:40:31 +00:00
assert_eq!(line_y, y);
assert_eq!(char_width / 4.0, width);
assert_eq!(line_height, height);
}
}