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 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}