nvim_gtk/
cursor.rs

1use cairo;
2use crate::mode;
3use crate::render;
4use crate::render::CellMetrics;
5use crate::highlight::HighlightMap;
6use std::sync::{Arc, Weak};
7use crate::ui::UiMutex;
8
9use glib;
10
11struct Alpha(f64);
12
13impl Alpha {
14    pub fn show(&mut self, step: f64) -> bool {
15        self.0 += step;
16        if self.0 > 1.0 {
17            self.0 = 1.0;
18            false
19        } else {
20            true
21        }
22    }
23    pub fn hide(&mut self, step: f64) -> bool {
24        self.0 -= step;
25        if self.0 < 0.0 {
26            self.0 = 0.0;
27            false
28        } else {
29            true
30        }
31    }
32}
33
34#[derive(PartialEq)]
35enum AnimPhase {
36    Shown,
37    Hide,
38    Hidden,
39    Show,
40    NoFocus,
41    Busy,
42}
43
44struct BlinkCount {
45    count: u32,
46    max: u32,
47}
48
49impl BlinkCount {
50    fn new(max: u32) -> Self {
51        Self { count: 0, max }
52    }
53}
54
55struct State<CB: CursorRedrawCb> {
56    alpha: Alpha,
57    anim_phase: AnimPhase,
58    redraw_cb: Weak<UiMutex<CB>>,
59
60    timer: Option<glib::SourceId>,
61    counter: Option<BlinkCount>,
62}
63
64impl<CB: CursorRedrawCb> State<CB> {
65    fn new(redraw_cb: Weak<UiMutex<CB>>) -> Self {
66        State {
67            alpha: Alpha(1.0),
68            anim_phase: AnimPhase::Shown,
69            redraw_cb,
70            timer: None,
71            counter: None,
72        }
73    }
74
75    fn reset_to(&mut self, phase: AnimPhase) {
76        self.alpha = Alpha(1.0);
77        self.anim_phase = phase;
78        if let Some(timer_id) = self.timer.take() {
79            glib::source_remove(timer_id);
80        }
81    }
82}
83
84pub trait Cursor {
85    /// return cursor current alpha value
86    fn draw(
87        &self,
88        ctx: &cairo::Context,
89        font_ctx: &render::Context,
90        line_y: f64,
91        double_width: bool,
92        hl: &HighlightMap,
93    ) -> f64;
94
95    fn is_visible(&self) -> bool;
96
97    fn mode_info(&self) -> Option<&mode::ModeInfo>;
98}
99
100pub struct EmptyCursor;
101
102impl EmptyCursor {
103    pub fn new() -> Self {
104        EmptyCursor {}
105    }
106}
107
108impl Cursor for EmptyCursor {
109    fn draw(
110        &self,
111        _ctx: &cairo::Context,
112        _font_ctx: &render::Context,
113        _line_y: f64,
114        _double_width: bool,
115        _color: &HighlightMap,
116    ) -> f64 {
117        0.0
118    }
119
120    fn is_visible(&self) -> bool {
121        false
122    }
123
124    fn mode_info(&self) -> Option<&mode::ModeInfo> {
125        None
126    }
127}
128
129pub struct BlinkCursor<CB: CursorRedrawCb> {
130    state: Arc<UiMutex<State<CB>>>,
131    mode_info: Option<mode::ModeInfo>,
132}
133
134impl<CB: CursorRedrawCb + 'static> BlinkCursor<CB> {
135    pub fn new(redraw_cb: Weak<UiMutex<CB>>) -> Self {
136        BlinkCursor {
137            state: Arc::new(UiMutex::new(State::new(redraw_cb))),
138            mode_info: None,
139        }
140    }
141
142    pub fn set_mode_info(&mut self, mode_info: Option<mode::ModeInfo>) {
143        self.mode_info = mode_info;
144    }
145
146    pub fn set_cursor_blink(&mut self, val: i32) {
147        let mut mut_state = self.state.borrow_mut();
148        mut_state.counter = if val < 0 {
149            None
150        } else {
151            Some(BlinkCount::new(val as u32))
152        }
153    }
154
155    pub fn start(&mut self) {
156        let blinkwait = self
157            .mode_info
158            .as_ref()
159            .and_then(|mi| mi.blinkwait)
160            .unwrap_or(500);
161
162        let state = self.state.clone();
163        let mut mut_state = self.state.borrow_mut();
164
165        mut_state.reset_to(AnimPhase::Shown);
166
167        if let Some(counter) = &mut mut_state.counter {
168            counter.count = 0;
169        }
170
171        mut_state.timer = Some(glib::timeout_add(
172            if blinkwait > 0 { blinkwait } else { 500 },
173            move || anim_step(&state),
174        ));
175    }
176
177    pub fn reset_state(&mut self) {
178        if self.state.borrow().anim_phase != AnimPhase::Busy {
179            self.start();
180        }
181    }
182
183    pub fn enter_focus(&mut self) {
184        if self.state.borrow().anim_phase != AnimPhase::Busy {
185            self.start();
186        }
187    }
188
189    pub fn leave_focus(&mut self) {
190        if self.state.borrow().anim_phase != AnimPhase::Busy {
191            self.state.borrow_mut().reset_to(AnimPhase::NoFocus);
192        }
193    }
194
195    pub fn busy_on(&mut self) {
196        self.state.borrow_mut().reset_to(AnimPhase::Busy);
197    }
198
199    pub fn busy_off(&mut self) {
200        self.start();
201    }
202}
203
204impl<CB: CursorRedrawCb> Cursor for BlinkCursor<CB> {
205    fn draw(
206        &self,
207        ctx: &cairo::Context,
208        font_ctx: &render::Context,
209        line_y: f64,
210        double_width: bool,
211        hl: &HighlightMap,
212    ) -> f64 {
213        let state = self.state.borrow();
214
215        let current_point = ctx.get_current_point();
216
217        let bg = hl.cursor_bg();
218        ctx.set_source_rgba(bg.0, bg.1, bg.2, state.alpha.0);
219
220        let (y, width, height) = cursor_rect(
221            self.mode_info(),
222            font_ctx.cell_metrics(),
223            line_y,
224            double_width,
225        );
226
227        ctx.rectangle(current_point.0, y, width, height);
228        if state.anim_phase == AnimPhase::NoFocus {
229            ctx.stroke();
230        } else {
231            ctx.fill();
232        }
233
234        state.alpha.0
235    }
236
237    fn is_visible(&self) -> bool {
238        let state = self.state.borrow();
239
240        if state.anim_phase == AnimPhase::Busy {
241            return false;
242        }
243
244        if state.alpha.0 < 0.000001 {
245            false
246        } else {
247            true
248        }
249    }
250
251    fn mode_info(&self) -> Option<&mode::ModeInfo> {
252        self.mode_info.as_ref()
253    }
254}
255
256pub fn cursor_rect(
257    mode_info: Option<&mode::ModeInfo>,
258    cell_metrics: &CellMetrics,
259    line_y: f64,
260    double_width: bool,
261) -> (f64, f64, f64) {
262    let &CellMetrics {
263        line_height,
264        char_width,
265        ..
266    } = cell_metrics;
267
268    if let Some(mode_info) = mode_info {
269        match mode_info.cursor_shape() {
270            None | Some(&mode::CursorShape::Unknown) | Some(&mode::CursorShape::Block) => {
271                let cursor_width = if double_width {
272                    char_width * 2.0
273                } else {
274                    char_width
275                };
276                (line_y, cursor_width, line_height)
277            }
278            Some(&mode::CursorShape::Vertical) => {
279                let cell_percentage = mode_info.cell_percentage();
280                let cursor_width = if cell_percentage > 0 {
281                    (char_width * cell_percentage as f64) / 100.0
282                } else {
283                    char_width
284                };
285                (line_y, cursor_width, line_height)
286            }
287            Some(&mode::CursorShape::Horizontal) => {
288                let cell_percentage = mode_info.cell_percentage();
289                let cursor_width = if double_width {
290                    char_width * 2.0
291                } else {
292                    char_width
293                };
294
295                if cell_percentage > 0 {
296                    let height = (line_height * cell_percentage as f64) / 100.0;
297                    (line_y + line_height - height, cursor_width, height)
298                } else {
299                    (line_y, cursor_width, line_height)
300                }
301            }
302        }
303    } else {
304        let cursor_width = if double_width {
305            char_width * 2.0
306        } else {
307            char_width
308        };
309
310        (line_y, cursor_width, line_height)
311    }
312}
313
314fn anim_step<CB: CursorRedrawCb + 'static>(state: &Arc<UiMutex<State<CB>>>) -> glib::Continue {
315    let mut mut_state = state.borrow_mut();
316
317    let next_event = match mut_state.anim_phase {
318        AnimPhase::Shown => {
319            if let Some(counter) = &mut mut_state.counter {
320                if counter.count < counter.max {
321                    counter.count += 1;
322                    mut_state.anim_phase = AnimPhase::Hide;
323                    Some(60)
324                } else {
325                    None
326                }
327            } else {
328                mut_state.anim_phase = AnimPhase::Hide;
329                Some(60)
330            }
331        }
332        AnimPhase::Hide => {
333            if !mut_state.alpha.hide(0.3) {
334                mut_state.anim_phase = AnimPhase::Hidden;
335
336                Some(300)
337            } else {
338                None
339            }
340        }
341        AnimPhase::Hidden => {
342            mut_state.anim_phase = AnimPhase::Show;
343
344            Some(60)
345        }
346        AnimPhase::Show => {
347            if !mut_state.alpha.show(0.3) {
348                mut_state.anim_phase = AnimPhase::Shown;
349
350                Some(500)
351            } else {
352                None
353            }
354        }
355        AnimPhase::NoFocus => None,
356        AnimPhase::Busy => None,
357    };
358
359    let redraw_cb = mut_state.redraw_cb.upgrade().unwrap();
360    let mut redraw_cb = redraw_cb.borrow_mut();
361    redraw_cb.queue_redraw_cursor();
362
363    if let Some(timeout) = next_event {
364        let moved_state = state.clone();
365        mut_state.timer = Some(glib::timeout_add(timeout, move || anim_step(&moved_state)));
366
367        glib::Continue(false)
368    } else {
369        glib::Continue(true)
370    }
371}
372
373impl<CB: CursorRedrawCb> Drop for BlinkCursor<CB> {
374    fn drop(&mut self) {
375        if let Some(timer_id) = self.state.borrow_mut().timer.take() {
376            glib::source_remove(timer_id);
377        }
378    }
379}
380
381pub trait CursorRedrawCb {
382    fn queue_redraw_cursor(&mut self);
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use std::collections::HashMap;
389
390    #[test]
391    fn test_cursor_rect_horizontal() {
392        let mut mode_data = HashMap::new();
393        mode_data.insert("cursor_shape".to_owned(), From::from("horizontal"));
394        mode_data.insert("cell_percentage".to_owned(), From::from(25));
395
396        let mode_info = mode::ModeInfo::new(&mode_data).ok();
397        let char_width = 50.0;
398        let line_height = 30.0;
399        let line_y = 0.0;
400
401        let (y, width, height) = cursor_rect(
402            mode_info.as_ref(),
403            &CellMetrics::new_hw(line_height, char_width),
404            line_y,
405            false,
406        );
407        assert_eq!(line_y + line_height - line_height / 4.0, y);
408        assert_eq!(char_width, width);
409        assert_eq!(line_height / 4.0, height);
410    }
411
412    #[test]
413    fn test_cursor_rect_horizontal_doublewidth() {
414        let mut mode_data = HashMap::new();
415        mode_data.insert("cursor_shape".to_owned(), From::from("horizontal"));
416        mode_data.insert("cell_percentage".to_owned(), From::from(25));
417
418        let mode_info = mode::ModeInfo::new(&mode_data).ok();
419        let char_width = 50.0;
420        let line_height = 30.0;
421        let line_y = 0.0;
422
423        let (y, width, height) = cursor_rect(
424            mode_info.as_ref(),
425            &CellMetrics::new_hw(line_height, char_width),
426            line_y,
427            true,
428        );
429        assert_eq!(line_y + line_height - line_height / 4.0, y);
430        assert_eq!(char_width * 2.0, width);
431        assert_eq!(line_height / 4.0, height);
432    }
433
434    #[test]
435    fn test_cursor_rect_vertical() {
436        let mut mode_data = HashMap::new();
437        mode_data.insert("cursor_shape".to_owned(), From::from("vertical"));
438        mode_data.insert("cell_percentage".to_owned(), From::from(25));
439
440        let mode_info = mode::ModeInfo::new(&mode_data).ok();
441        let char_width = 50.0;
442        let line_height = 30.0;
443        let line_y = 0.0;
444
445        let (y, width, height) = cursor_rect(
446            mode_info.as_ref(),
447            &CellMetrics::new_hw(line_height, char_width),
448            line_y,
449            false,
450        );
451        assert_eq!(line_y, y);
452        assert_eq!(char_width / 4.0, width);
453        assert_eq!(line_height, height);
454    }
455}