1#![warn(missing_docs)]
7
8use crate::item_tree::ItemTreeRc;
9use crate::item_tree::{ItemRc, ItemWeak, VisitChildrenResult};
10pub use crate::items::PointerEventButton;
11pub use crate::items::{FocusReason, KeyEvent, KeyboardModifiers};
12use crate::items::{ItemRef, TextCursorDirection};
13use crate::lengths::{LogicalPoint, LogicalVector};
14use crate::timers::Timer;
15use crate::window::{WindowAdapter, WindowInner};
16use crate::{Coord, Property, SharedString};
17use alloc::rc::Rc;
18use alloc::vec::Vec;
19use const_field_offset::FieldOffsets;
20use core::cell::Cell;
21use core::pin::Pin;
22use core::time::Duration;
23
24#[repr(C)]
29#[derive(Debug, Clone, Copy, PartialEq)]
30#[allow(missing_docs)]
31pub enum MouseEvent {
32 Pressed { position: LogicalPoint, button: PointerEventButton, click_count: u8 },
37 Released { position: LogicalPoint, button: PointerEventButton, click_count: u8 },
42 Moved { position: LogicalPoint },
44 Wheel { position: LogicalPoint, delta_x: Coord, delta_y: Coord },
49 Exit,
51}
52
53impl MouseEvent {
54 pub fn position(&self) -> Option<LogicalPoint> {
56 match self {
57 MouseEvent::Pressed { position, .. } => Some(*position),
58 MouseEvent::Released { position, .. } => Some(*position),
59 MouseEvent::Moved { position } => Some(*position),
60 MouseEvent::Wheel { position, .. } => Some(*position),
61 MouseEvent::Exit => None,
62 }
63 }
64
65 pub fn translate(&mut self, vec: LogicalVector) {
67 let pos = match self {
68 MouseEvent::Pressed { position, .. } => Some(position),
69 MouseEvent::Released { position, .. } => Some(position),
70 MouseEvent::Moved { position } => Some(position),
71 MouseEvent::Wheel { position, .. } => Some(position),
72 MouseEvent::Exit => None,
73 };
74 if let Some(pos) = pos {
75 *pos += vec;
76 }
77 }
78
79 fn set_click_count(&mut self, count: u8) {
81 match self {
82 MouseEvent::Pressed { click_count, .. } | MouseEvent::Released { click_count, .. } => {
83 *click_count = count
84 }
85 _ => (),
86 }
87 }
88}
89
90#[repr(u8)]
95#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
96pub enum InputEventResult {
97 EventAccepted,
100 #[default]
102 EventIgnored,
103 GrabMouse,
105}
106
107#[repr(C)]
111#[derive(Debug, Copy, Clone, PartialEq, Default)]
112pub enum InputEventFilterResult {
113 #[default]
116 ForwardEvent,
117 ForwardAndIgnore,
120 ForwardAndInterceptGrab,
123 Intercept,
126 DelayForwarding(u64),
133}
134
135#[allow(missing_docs, non_upper_case_globals)]
137pub mod key_codes {
138 macro_rules! declare_consts_for_special_keys {
139 ($($char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|*;)*) => {
140 $(pub const $name : char = $char;)*
141
142 #[allow(missing_docs)]
143 #[derive(Debug, Copy, Clone, PartialEq)]
144 #[non_exhaustive]
145 pub enum Key {
160 $($name,)*
161 }
162
163 impl From<Key> for char {
164 fn from(k: Key) -> Self {
165 match k {
166 $(Key::$name => $name,)*
167 }
168 }
169 }
170
171 impl From<Key> for crate::SharedString {
172 fn from(k: Key) -> Self {
173 char::from(k).into()
174 }
175 }
176 };
177 }
178
179 i_slint_common::for_each_special_keys!(declare_consts_for_special_keys);
180}
181
182#[derive(Clone, Copy, Default, Debug)]
185pub(crate) struct InternalKeyboardModifierState {
186 left_alt: bool,
187 right_alt: bool,
188 altgr: bool,
189 left_control: bool,
190 right_control: bool,
191 left_meta: bool,
192 right_meta: bool,
193 left_shift: bool,
194 right_shift: bool,
195}
196
197impl InternalKeyboardModifierState {
198 pub(crate) fn state_update(mut self, pressed: bool, text: &SharedString) -> Option<Self> {
201 if let Some(key_code) = text.chars().next() {
202 match key_code {
203 key_codes::Alt => self.left_alt = pressed,
204 key_codes::AltGr => self.altgr = pressed,
205 key_codes::Control => self.left_control = pressed,
206 key_codes::ControlR => self.right_control = pressed,
207 key_codes::Shift => self.left_shift = pressed,
208 key_codes::ShiftR => self.right_shift = pressed,
209 key_codes::Meta => self.left_meta = pressed,
210 key_codes::MetaR => self.right_meta = pressed,
211 _ => return None,
212 };
213
214 debug_assert_eq!(key_code.len_utf8(), text.len());
218 }
219
220 #[cfg(target_os = "windows")]
222 {
223 if self.altgr {
224 self.left_control = false;
226 self.right_control = false;
227 } else if self.control() && self.alt() {
228 self.left_control = false;
230 self.right_control = false;
231 self.left_alt = false;
232 self.right_alt = false;
233 }
234 }
235
236 Some(self)
237 }
238
239 pub fn shift(&self) -> bool {
240 self.right_shift || self.left_shift
241 }
242 pub fn alt(&self) -> bool {
243 self.right_alt || self.left_alt
244 }
245 pub fn meta(&self) -> bool {
246 self.right_meta || self.left_meta
247 }
248 pub fn control(&self) -> bool {
249 self.right_control || self.left_control
250 }
251}
252
253impl From<InternalKeyboardModifierState> for KeyboardModifiers {
254 fn from(internal_state: InternalKeyboardModifierState) -> Self {
255 Self {
256 alt: internal_state.alt(),
257 control: internal_state.control(),
258 meta: internal_state.meta(),
259 shift: internal_state.shift(),
260 }
261 }
262}
263
264#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
266#[repr(u8)]
267pub enum KeyEventType {
268 #[default]
270 KeyPressed = 0,
271 KeyReleased = 1,
273 UpdateComposition = 2,
276 CommitComposition = 3,
278}
279
280impl KeyEvent {
281 pub fn shortcut(&self) -> Option<StandardShortcut> {
284 if self.modifiers.control && !self.modifiers.shift {
285 match self.text.as_str() {
286 #[cfg(not(target_arch = "wasm32"))]
287 "c" => Some(StandardShortcut::Copy),
288 #[cfg(not(target_arch = "wasm32"))]
289 "x" => Some(StandardShortcut::Cut),
290 #[cfg(not(target_arch = "wasm32"))]
291 "v" => Some(StandardShortcut::Paste),
292 "a" => Some(StandardShortcut::SelectAll),
293 "f" => Some(StandardShortcut::Find),
294 "s" => Some(StandardShortcut::Save),
295 "p" => Some(StandardShortcut::Print),
296 "z" => Some(StandardShortcut::Undo),
297 #[cfg(target_os = "windows")]
298 "y" => Some(StandardShortcut::Redo),
299 "r" => Some(StandardShortcut::Refresh),
300 _ => None,
301 }
302 } else if self.modifiers.control && self.modifiers.shift {
303 match self.text.as_str() {
304 #[cfg(not(target_os = "windows"))]
305 "z" => Some(StandardShortcut::Redo),
306 _ => None,
307 }
308 } else {
309 None
310 }
311 }
312
313 pub fn text_shortcut(&self) -> Option<TextShortcut> {
316 let keycode = self.text.chars().next()?;
317
318 let move_mod = if cfg!(target_os = "macos") {
319 self.modifiers.alt && !self.modifiers.control && !self.modifiers.meta
320 } else {
321 self.modifiers.control && !self.modifiers.alt && !self.modifiers.meta
322 };
323
324 if move_mod {
325 match keycode {
326 key_codes::LeftArrow => {
327 return Some(TextShortcut::Move(TextCursorDirection::BackwardByWord))
328 }
329 key_codes::RightArrow => {
330 return Some(TextShortcut::Move(TextCursorDirection::ForwardByWord))
331 }
332 key_codes::UpArrow => {
333 return Some(TextShortcut::Move(TextCursorDirection::StartOfParagraph))
334 }
335 key_codes::DownArrow => {
336 return Some(TextShortcut::Move(TextCursorDirection::EndOfParagraph))
337 }
338 key_codes::Backspace => {
339 return Some(TextShortcut::DeleteWordBackward);
340 }
341 key_codes::Delete => {
342 return Some(TextShortcut::DeleteWordForward);
343 }
344 _ => (),
345 };
346 }
347
348 #[cfg(not(target_os = "macos"))]
349 {
350 if self.modifiers.control && !self.modifiers.alt && !self.modifiers.meta {
351 match keycode {
352 key_codes::Home => {
353 return Some(TextShortcut::Move(TextCursorDirection::StartOfText))
354 }
355 key_codes::End => {
356 return Some(TextShortcut::Move(TextCursorDirection::EndOfText))
357 }
358 _ => (),
359 };
360 }
361 }
362
363 #[cfg(target_os = "macos")]
364 {
365 if self.modifiers.control {
366 match keycode {
367 key_codes::LeftArrow => {
368 return Some(TextShortcut::Move(TextCursorDirection::StartOfLine))
369 }
370 key_codes::RightArrow => {
371 return Some(TextShortcut::Move(TextCursorDirection::EndOfLine))
372 }
373 key_codes::UpArrow => {
374 return Some(TextShortcut::Move(TextCursorDirection::StartOfText))
375 }
376 key_codes::DownArrow => {
377 return Some(TextShortcut::Move(TextCursorDirection::EndOfText))
378 }
379 _ => (),
380 };
381 }
382 }
383
384 if let Ok(direction) = TextCursorDirection::try_from(keycode) {
385 Some(TextShortcut::Move(direction))
386 } else {
387 match keycode {
388 key_codes::Backspace => Some(TextShortcut::DeleteBackward),
389 key_codes::Delete => Some(TextShortcut::DeleteForward),
390 _ => None,
391 }
392 }
393 }
394}
395
396pub enum StandardShortcut {
398 Copy,
400 Cut,
402 Paste,
404 SelectAll,
406 Find,
408 Save,
410 Print,
412 Undo,
414 Redo,
416 Refresh,
418}
419
420pub enum TextShortcut {
422 Move(TextCursorDirection),
424 DeleteForward,
426 DeleteBackward,
428 DeleteWordForward,
430 DeleteWordBackward,
432}
433
434#[repr(u8)]
437#[derive(Debug, Clone, Copy, PartialEq, Default)]
438pub enum KeyEventResult {
439 EventAccepted,
441 #[default]
443 EventIgnored,
444}
445
446#[repr(u8)]
449#[derive(Debug, Clone, Copy, PartialEq, Default)]
450pub enum FocusEventResult {
451 FocusAccepted,
453 #[default]
455 FocusIgnored,
456}
457
458#[derive(Debug, Clone, Copy, PartialEq)]
461#[repr(u8)]
462pub enum FocusEvent {
463 FocusIn(FocusReason),
465 FocusOut(FocusReason),
467}
468
469#[derive(Default)]
471pub struct ClickState {
472 click_count_time_stamp: Cell<Option<crate::animations::Instant>>,
473 click_count: Cell<u8>,
474 click_position: Cell<LogicalPoint>,
475 click_button: Cell<PointerEventButton>,
476}
477
478impl ClickState {
479 fn restart(&self, position: LogicalPoint, button: PointerEventButton) {
481 self.click_count.set(0);
482 self.click_count_time_stamp.set(Some(crate::animations::Instant::now()));
483 self.click_position.set(position);
484 self.click_button.set(button);
485 }
486
487 pub fn reset(&self) {
489 self.click_count.set(0);
490 self.click_count_time_stamp.replace(None);
491 }
492
493 pub fn check_repeat(&self, mouse_event: MouseEvent, click_interval: Duration) -> MouseEvent {
495 match mouse_event {
496 MouseEvent::Pressed { position, button, .. } => {
497 let instant_now = crate::animations::Instant::now();
498
499 if let Some(click_count_time_stamp) = self.click_count_time_stamp.get() {
500 if instant_now - click_count_time_stamp < click_interval
501 && button == self.click_button.get()
502 && (position - self.click_position.get()).square_length() < 100 as _
503 {
504 self.click_count.set(self.click_count.get().wrapping_add(1));
505 self.click_count_time_stamp.set(Some(instant_now));
506 } else {
507 self.restart(position, button);
508 }
509 } else {
510 self.restart(position, button);
511 }
512
513 return MouseEvent::Pressed {
514 position,
515 button,
516 click_count: self.click_count.get(),
517 };
518 }
519 MouseEvent::Released { position, button, .. } => {
520 return MouseEvent::Released {
521 position,
522 button,
523 click_count: self.click_count.get(),
524 }
525 }
526 _ => {}
527 };
528
529 mouse_event
530 }
531}
532
533#[derive(Default)]
535pub struct MouseInputState {
536 item_stack: Vec<(ItemWeak, InputEventFilterResult)>,
539 pub(crate) offset: LogicalPoint,
541 grabbed: bool,
543 delayed: Option<(crate::timers::Timer, MouseEvent)>,
544 delayed_exit_items: Vec<ItemWeak>,
545}
546
547impl MouseInputState {
548 fn top_item(&self) -> Option<ItemRc> {
550 self.item_stack.last().and_then(|x| x.0.upgrade())
551 }
552
553 pub fn top_item_including_delayed(&self) -> Option<ItemRc> {
555 self.delayed_exit_items.last().and_then(|x| x.upgrade()).or_else(|| self.top_item())
556 }
557}
558
559pub(crate) fn handle_mouse_grab(
562 mouse_event: MouseEvent,
563 window_adapter: &Rc<dyn WindowAdapter>,
564 mouse_input_state: &mut MouseInputState,
565) -> Option<MouseEvent> {
566 if !mouse_input_state.grabbed || mouse_input_state.item_stack.is_empty() {
567 return Some(mouse_event);
568 };
569
570 let mut event = mouse_event;
571 let mut intercept = false;
572 let mut invalid = false;
573
574 event.translate(-mouse_input_state.offset.to_vector());
575
576 mouse_input_state.item_stack.retain(|it| {
577 if invalid {
578 return false;
579 }
580 let item = if let Some(item) = it.0.upgrade() {
581 item
582 } else {
583 invalid = true;
584 return false;
585 };
586 if intercept {
587 item.borrow().as_ref().input_event(MouseEvent::Exit, window_adapter, &item);
588 return false;
589 }
590 let g = item.geometry();
591 event.translate(-g.origin.to_vector());
592
593 let interested = matches!(
594 it.1,
595 InputEventFilterResult::ForwardAndInterceptGrab
596 | InputEventFilterResult::DelayForwarding(_)
597 );
598
599 if interested
600 && item.borrow().as_ref().input_event_filter_before_children(
601 event,
602 window_adapter,
603 &item,
604 ) == InputEventFilterResult::Intercept
605 {
606 intercept = true;
607 }
608 true
609 });
610 if invalid {
611 return Some(mouse_event);
612 }
613
614 let grabber = mouse_input_state.top_item().unwrap();
615 let input_result = grabber.borrow().as_ref().input_event(event, window_adapter, &grabber);
616 if input_result != InputEventResult::GrabMouse {
617 mouse_input_state.grabbed = false;
618 Some(
620 mouse_event
621 .position()
622 .map_or(MouseEvent::Exit, |position| MouseEvent::Moved { position }),
623 )
624 } else {
625 None
626 }
627}
628
629pub(crate) fn send_exit_events(
630 old_input_state: &MouseInputState,
631 new_input_state: &mut MouseInputState,
632 mut pos: Option<LogicalPoint>,
633 window_adapter: &Rc<dyn WindowAdapter>,
634) {
635 for it in core::mem::take(&mut new_input_state.delayed_exit_items) {
636 let Some(item) = it.upgrade() else { continue };
637 item.borrow().as_ref().input_event(MouseEvent::Exit, window_adapter, &item);
638 }
639
640 let mut clipped = false;
641 for (idx, it) in old_input_state.item_stack.iter().enumerate() {
642 let Some(item) = it.0.upgrade() else { break };
643 let g = item.geometry();
644 let contains = pos.is_some_and(|p| g.contains(p));
645 if let Some(p) = pos.as_mut() {
646 *p -= g.origin.to_vector();
647 }
648 if !contains || clipped {
649 if item.borrow().as_ref().clips_children() {
650 clipped = true;
651 }
652 item.borrow().as_ref().input_event(MouseEvent::Exit, window_adapter, &item);
653 } else if new_input_state.item_stack.get(idx).map_or(true, |(x, _)| *x != it.0) {
654 if new_input_state.delayed.is_some() {
656 new_input_state.delayed_exit_items.push(it.0.clone());
657 } else {
658 item.borrow().as_ref().input_event(MouseEvent::Exit, window_adapter, &item);
659 }
660 }
661 }
662}
663
664pub fn process_mouse_input(
668 root: ItemRc,
669 mouse_event: MouseEvent,
670 window_adapter: &Rc<dyn WindowAdapter>,
671 mouse_input_state: MouseInputState,
672) -> MouseInputState {
673 let mut result = MouseInputState::default();
674 let r = send_mouse_event_to_item(
675 mouse_event,
676 root.clone(),
677 window_adapter,
678 &mut result,
679 mouse_input_state.top_item().as_ref(),
680 false,
681 );
682 if mouse_input_state.delayed.is_some()
683 && (!r.has_aborted()
684 || Option::zip(result.item_stack.last(), mouse_input_state.item_stack.last())
685 .map_or(true, |(a, b)| a.0 != b.0))
686 {
687 return mouse_input_state;
689 }
690 send_exit_events(&mouse_input_state, &mut result, mouse_event.position(), window_adapter);
691
692 if let MouseEvent::Wheel { position, .. } = mouse_event {
693 if r.has_aborted() {
694 return process_mouse_input(
696 root,
697 MouseEvent::Moved { position },
698 window_adapter,
699 result,
700 );
701 }
702 }
703
704 result
705}
706
707pub(crate) fn process_delayed_event(
708 window_adapter: &Rc<dyn WindowAdapter>,
709 mut mouse_input_state: MouseInputState,
710) -> MouseInputState {
711 let event = match mouse_input_state.delayed.take() {
713 Some(e) => e.1,
714 None => return mouse_input_state,
715 };
716
717 let top_item = match mouse_input_state.top_item() {
718 Some(i) => i,
719 None => return MouseInputState::default(),
720 };
721
722 let mut actual_visitor =
723 |component: &ItemTreeRc, index: u32, _: Pin<ItemRef>| -> VisitChildrenResult {
724 send_mouse_event_to_item(
725 event,
726 ItemRc::new(component.clone(), index),
727 window_adapter,
728 &mut mouse_input_state,
729 Some(&top_item),
730 true,
731 )
732 };
733 vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
734 vtable::VRc::borrow_pin(top_item.item_tree()).as_ref().visit_children_item(
735 top_item.index() as isize,
736 crate::item_tree::TraversalOrder::FrontToBack,
737 actual_visitor,
738 );
739 mouse_input_state
740}
741
742fn send_mouse_event_to_item(
743 mouse_event: MouseEvent,
744 item_rc: ItemRc,
745 window_adapter: &Rc<dyn WindowAdapter>,
746 result: &mut MouseInputState,
747 last_top_item: Option<&ItemRc>,
748 ignore_delays: bool,
749) -> VisitChildrenResult {
750 let item = item_rc.borrow();
751 let geom = item_rc.geometry();
752 let mut event_for_children = mouse_event;
754 event_for_children.translate(-geom.origin.to_vector());
755
756 let filter_result = if mouse_event.position().is_some_and(|p| geom.contains(p))
757 || item.as_ref().clips_children()
758 {
759 item.as_ref().input_event_filter_before_children(
760 event_for_children,
761 window_adapter,
762 &item_rc,
763 )
764 } else {
765 InputEventFilterResult::ForwardAndIgnore
766 };
767
768 let (forward_to_children, ignore) = match filter_result {
769 InputEventFilterResult::ForwardEvent => (true, false),
770 InputEventFilterResult::ForwardAndIgnore => (true, true),
771 InputEventFilterResult::ForwardAndInterceptGrab => (true, false),
772 InputEventFilterResult::Intercept => (false, false),
773 InputEventFilterResult::DelayForwarding(_) if ignore_delays => (true, false),
774 InputEventFilterResult::DelayForwarding(duration) => {
775 let timer = Timer::default();
776 let w = Rc::downgrade(window_adapter);
777 timer.start(
778 crate::timers::TimerMode::SingleShot,
779 Duration::from_millis(duration),
780 move || {
781 if let Some(w) = w.upgrade() {
782 WindowInner::from_pub(w.window()).process_delayed_event();
783 }
784 },
785 );
786 result.delayed = Some((timer, event_for_children));
787 result
788 .item_stack
789 .push((item_rc.downgrade(), InputEventFilterResult::DelayForwarding(duration)));
790 return VisitChildrenResult::abort(item_rc.index(), 0);
791 }
792 };
793
794 result.item_stack.push((item_rc.downgrade(), filter_result));
795 if forward_to_children {
796 let mut actual_visitor =
797 |component: &ItemTreeRc, index: u32, _: Pin<ItemRef>| -> VisitChildrenResult {
798 send_mouse_event_to_item(
799 event_for_children,
800 ItemRc::new(component.clone(), index),
801 window_adapter,
802 result,
803 last_top_item,
804 ignore_delays,
805 )
806 };
807 vtable::new_vref!(let mut actual_visitor : VRefMut<crate::item_tree::ItemVisitorVTable> for crate::item_tree::ItemVisitor = &mut actual_visitor);
808 let r = vtable::VRc::borrow_pin(item_rc.item_tree()).as_ref().visit_children_item(
809 item_rc.index() as isize,
810 crate::item_tree::TraversalOrder::FrontToBack,
811 actual_visitor,
812 );
813 if r.has_aborted() {
814 return r;
815 }
816 };
817
818 let r = if ignore {
819 InputEventResult::EventIgnored
820 } else {
821 let mut event = mouse_event;
822 event.translate(-geom.origin.to_vector());
823 if last_top_item.map_or(true, |x| *x != item_rc) {
824 event.set_click_count(0);
825 }
826 item.as_ref().input_event(event, window_adapter, &item_rc)
827 };
828 match r {
829 InputEventResult::EventAccepted => VisitChildrenResult::abort(item_rc.index(), 0),
830 InputEventResult::EventIgnored => {
831 let _pop = result.item_stack.pop();
832 debug_assert_eq!(
833 _pop.map(|x| (x.0.upgrade().unwrap().index(), x.1)).unwrap(),
834 (item_rc.index(), filter_result)
835 );
836 VisitChildrenResult::CONTINUE
837 }
838 InputEventResult::GrabMouse => {
839 result.item_stack.last_mut().unwrap().1 =
840 InputEventFilterResult::ForwardAndInterceptGrab;
841 result.grabbed = true;
842 VisitChildrenResult::abort(item_rc.index(), 0)
843 }
844 }
845}
846
847#[derive(FieldOffsets)]
854#[repr(C)]
855#[pin]
856pub(crate) struct TextCursorBlinker {
857 cursor_visible: Property<bool>,
858 cursor_blink_timer: crate::timers::Timer,
859}
860
861impl TextCursorBlinker {
862 pub fn new() -> Pin<Rc<Self>> {
865 Rc::pin(Self {
866 cursor_visible: Property::new(true),
867 cursor_blink_timer: Default::default(),
868 })
869 }
870
871 pub fn set_binding(instance: Pin<Rc<TextCursorBlinker>>, prop: &Property<bool>) {
874 instance.as_ref().cursor_visible.set(true);
875 Self::start(&instance);
877 prop.set_binding(move || {
878 TextCursorBlinker::FIELD_OFFSETS.cursor_visible.apply_pin(instance.as_ref()).get()
879 });
880 }
881
882 pub fn start(self: &Pin<Rc<Self>>) {
885 if self.cursor_blink_timer.running() {
886 self.cursor_blink_timer.restart();
887 } else {
888 let toggle_cursor = {
889 let weak_blinker = pin_weak::rc::PinWeak::downgrade(self.clone());
890 move || {
891 if let Some(blinker) = weak_blinker.upgrade() {
892 let visible = TextCursorBlinker::FIELD_OFFSETS
893 .cursor_visible
894 .apply_pin(blinker.as_ref())
895 .get();
896 blinker.cursor_visible.set(!visible);
897 }
898 }
899 };
900 self.cursor_blink_timer.start(
901 crate::timers::TimerMode::Repeated,
902 Duration::from_millis(500),
903 toggle_cursor,
904 );
905 }
906 }
907
908 pub fn stop(&self) {
911 self.cursor_blink_timer.stop()
912 }
913}