1use super::*;
2use crate::config::constants::ui;
3use crate::core_tui::session::MouseDragTarget;
4use crate::core_tui::session::render::modal_render_styles;
5use crate::core_tui::session::{TranscriptLinkClickAction, inline_list, list_panel, modal};
6use std::time::Instant;
7
8impl Session {
9 #[cfg(test)]
10 pub(crate) fn process_key(&mut self, key: KeyEvent) -> Option<InlineEvent> {
11 events::process_key(self, key)
12 }
13
14 fn input_area_contains(&self, column: u16, row: u16) -> bool {
15 self.core.input_area().is_some_and(|area| {
16 row >= area.y
17 && row < area.y.saturating_add(area.height)
18 && column >= area.x
19 && column < area.x.saturating_add(area.width)
20 })
21 }
22
23 fn bottom_panel_contains(&self, column: u16, row: u16) -> bool {
24 self.core.bottom_panel_area().is_some_and(|area| {
25 row >= area.y
26 && row < area.y.saturating_add(area.height)
27 && column >= area.x
28 && column < area.x.saturating_add(area.width)
29 })
30 }
31
32 fn panel_row_index(
33 &self,
34 layout: &list_panel::ListPanelLayout,
35 column: u16,
36 row: u16,
37 ) -> Option<usize> {
38 let area = self.core.bottom_panel_area()?;
39 layout.row_index(area, column, row)
40 }
41
42 fn handle_modal_list_result(
43 &mut self,
44 result: modal::ModalListKeyResult,
45 events: &UnboundedSender<InlineEvent>,
46 callback: Option<&(dyn Fn(&InlineEvent) + Send + Sync + 'static)>,
47 ) -> bool {
48 match result {
49 modal::ModalListKeyResult::NotHandled => false,
50 modal::ModalListKeyResult::HandledNoRedraw => true,
51 modal::ModalListKeyResult::Redraw => {
52 self.mark_dirty();
53 true
54 }
55 modal::ModalListKeyResult::Emit(event) => {
56 self.mark_dirty();
57 let outbound: InlineEvent = event.into();
58 events::emit_inline_event(&outbound, events, callback);
59 true
60 }
61 modal::ModalListKeyResult::Submit(event) | modal::ModalListKeyResult::Cancel(event) => {
62 self.close_overlay();
63 self.mark_dirty();
64 let outbound: InlineEvent = event.into();
65 events::emit_inline_event(&outbound, events, callback);
66 true
67 }
68 }
69 }
70
71 fn handle_link_click_action(
72 &mut self,
73 action: TranscriptLinkClickAction,
74 clear_drag_target: bool,
75 events: &UnboundedSender<InlineEvent>,
76 callback: Option<&(dyn Fn(&InlineEvent) + Send + Sync + 'static)>,
77 ) -> bool {
78 match action {
79 TranscriptLinkClickAction::Open(outbound) => {
80 if clear_drag_target {
81 self.core.mouse_drag_target = MouseDragTarget::None;
82 }
83 self.mark_dirty();
84 let outbound: InlineEvent = outbound.into();
85 events::emit_inline_event(&outbound, events, callback);
86 self.core.mouse_selection.clear_click_history();
87 true
88 }
89 TranscriptLinkClickAction::Consume => {
90 if clear_drag_target {
91 self.core.mouse_drag_target = MouseDragTarget::None;
92 }
93 self.core.mouse_selection.clear_click_history();
94 true
95 }
96 TranscriptLinkClickAction::Ignore => false,
97 }
98 }
99
100 fn modal_visible_index_at(&self, row: u16) -> Option<usize> {
101 let area = self.core.modal_list_area()?;
102 if row < area.y || row >= area.y.saturating_add(area.height) {
103 return None;
104 }
105
106 let styles = modal_render_styles(self);
107 let content_width =
108 area.width
109 .saturating_sub(inline_list::selection_padding_width() as u16) as usize;
110 let relative_row = usize::from(row.saturating_sub(area.y));
111
112 if let Some(wizard) = self.wizard_overlay() {
113 let step = wizard.steps.get(wizard.current_step)?;
114 let offset = step.list.list_state.offset();
115 let visible_indices = &step.list.visible_indices;
116 let mut consumed_rows = 0usize;
117 for (visible_index, &item_index) in visible_indices.iter().enumerate().skip(offset) {
118 let lines = modal::modal_list_item_lines(
119 &step.list,
120 visible_index,
121 item_index,
122 &styles,
123 content_width,
124 None,
125 false,
126 );
127 let height = usize::from(inline_list::row_height(&lines));
128 if relative_row < consumed_rows + height {
129 return Some(visible_index);
130 }
131 consumed_rows += height;
132 if consumed_rows >= usize::from(area.height) {
133 break;
134 }
135 }
136 return None;
137 }
138
139 let modal = self.modal_state()?;
140 let list = modal.list.as_ref()?;
141 let offset = list.list_state.offset();
142 let mut consumed_rows = 0usize;
143 for (visible_index, &item_index) in list.visible_indices.iter().enumerate().skip(offset) {
144 let lines = modal::modal_list_item_lines(
145 list,
146 visible_index,
147 item_index,
148 &styles,
149 content_width,
150 None,
151 false,
152 );
153 let height = usize::from(inline_list::row_height(&lines));
154 if relative_row < consumed_rows + height {
155 return Some(visible_index);
156 }
157 consumed_rows += height;
158 if consumed_rows >= usize::from(area.height) {
159 break;
160 }
161 }
162
163 None
164 }
165
166 fn handle_active_overlay_click(
167 &mut self,
168 mouse_event: MouseEvent,
169 events: &UnboundedSender<InlineEvent>,
170 callback: Option<&(dyn Fn(&InlineEvent) + Send + Sync + 'static)>,
171 ) -> bool {
172 let column = mouse_event.column;
173 let row = mouse_event.row;
174 let in_modal_list = self.core.modal_list_area().is_some_and(|area| {
175 row >= area.y
176 && row < area.y.saturating_add(area.height)
177 && column >= area.x
178 && column < area.x.saturating_add(area.width)
179 });
180 if !in_modal_list {
181 return self.has_active_overlay();
182 }
183
184 let Some(visible_index) = self.modal_visible_index_at(row) else {
185 return true;
186 };
187
188 if let Some(wizard) = self.wizard_overlay_mut() {
189 let result = wizard.handle_mouse_click(visible_index);
190 return self.handle_modal_list_result(result, events, callback);
191 }
192
193 if let Some(modal) = self.modal_state_mut() {
194 let result = modal.handle_list_mouse_click(visible_index);
195 return self.handle_modal_list_result(result, events, callback);
196 }
197
198 true
199 }
200
201 fn modal_text_area_contains(&self, column: u16, row: u16) -> bool {
202 self.core.modal_text_areas().iter().any(|area| {
203 row >= area.y
204 && row < area.y.saturating_add(area.height)
205 && column >= area.x
206 && column < area.x.saturating_add(area.width)
207 })
208 }
209
210 fn handle_active_overlay_scroll(
211 &mut self,
212 mouse_event: MouseEvent,
213 down: bool,
214 events: &UnboundedSender<InlineEvent>,
215 callback: Option<&(dyn Fn(&InlineEvent) + Send + Sync + 'static)>,
216 ) -> bool {
217 if !self.has_active_overlay() {
218 return false;
219 }
220
221 let column = mouse_event.column;
222 let row = mouse_event.row;
223 let in_modal_list = self.core.modal_list_area().is_some_and(|area| {
224 row >= area.y
225 && row < area.y.saturating_add(area.height)
226 && column >= area.x
227 && column < area.x.saturating_add(area.width)
228 });
229
230 if !in_modal_list {
231 return true;
232 }
233
234 if let Some(wizard) = self.wizard_overlay_mut() {
235 let result = wizard.handle_mouse_scroll(down);
236 return self.handle_modal_list_result(result, events, callback);
237 }
238
239 if let Some(modal) = self.modal_state_mut() {
240 let result = modal.handle_list_mouse_scroll(down);
241 return self.handle_modal_list_result(result, events, callback);
242 }
243
244 true
245 }
246
247 fn handle_bottom_panel_scroll(&mut self, down: bool) -> bool {
248 if self.core.bottom_panel_area().is_none() {
249 return false;
250 }
251
252 if self.agent_palette_visible() {
253 let Some(palette) = self.agent_palette.as_mut() else {
254 return true;
255 };
256 if down {
257 palette.move_selection_down();
258 } else {
259 palette.move_selection_up();
260 }
261 self.mark_dirty();
262 return true;
263 }
264
265 if self.file_palette_visible() {
266 let Some(palette) = self.file_palette.as_mut() else {
267 return true;
268 };
269 if down {
270 palette.move_selection_down();
271 } else {
272 palette.move_selection_up();
273 }
274 self.mark_dirty();
275 return true;
276 }
277
278 if self.history_picker_visible() {
279 if down {
280 self.history_picker_state.move_down();
281 } else {
282 self.history_picker_state.move_up();
283 }
284 self.mark_dirty();
285 return true;
286 }
287
288 if self.local_agents_visible() {
289 let changed = if down {
290 self.local_agents_state.move_selection_down()
291 } else {
292 self.local_agents_state.move_selection_up()
293 };
294 if changed {
295 self.mark_dirty();
296 }
297 return true;
298 }
299
300 if slash::slash_navigation_available(self) {
301 if down {
302 slash::move_slash_selection_down(self);
303 } else {
304 slash::move_slash_selection_up(self);
305 }
306 return true;
307 }
308
309 false
310 }
311
312 fn handle_bottom_panel_click(&mut self, mouse_event: MouseEvent) -> bool {
313 let column = mouse_event.column;
314 let row = mouse_event.row;
315 if !self.bottom_panel_contains(column, row) {
316 return false;
317 }
318
319 if self.agent_palette_visible() {
320 let Some(layout) = render::agent_palette_panel_layout(self) else {
321 return true;
322 };
323 let bottom_area = self.core.bottom_panel_area();
324 let Some(palette) = self.agent_palette.as_mut() else {
325 return true;
326 };
327 let local_index = bottom_area.and_then(|area| layout.row_index(area, column, row));
328 let mut apply_name = None;
329 let mut should_mark_dirty = false;
330 if !palette.has_agents() {
331 return true;
332 }
333
334 let page_items = palette.current_page_items();
335 if let Some(local_index) = local_index
336 && let Some((global_index, entry, selected)) = page_items.get(local_index)
337 {
338 if *selected {
339 apply_name = Some(entry.name.clone());
340 } else if palette.select_index(*global_index) {
341 should_mark_dirty = true;
342 }
343 }
344
345 if let Some(name) = apply_name {
346 self.insert_agent_reference(&name);
347 self.close_agent_palette();
348 self.mark_dirty();
349 } else if should_mark_dirty {
350 self.mark_dirty();
351 }
352 return true;
353 }
354
355 if self.file_palette_visible() {
356 let Some(layout) = render::file_palette_panel_layout(self) else {
357 return true;
358 };
359 let bottom_area = self.core.bottom_panel_area();
360 let Some(palette) = self.file_palette.as_mut() else {
361 return true;
362 };
363 let local_index = bottom_area.and_then(|area| layout.row_index(area, column, row));
364 let mut apply_path = None;
365 let mut should_mark_dirty = false;
366 if !palette.has_files() {
367 return true;
368 }
369
370 let page_items = palette.current_page_items();
371 if let Some(local_index) = local_index
372 && let Some((global_index, entry, selected)) = page_items.get(local_index)
373 {
374 if *selected {
375 if palette.selected_is_expandable_group() {
376 palette.toggle_selected();
377 should_mark_dirty = true;
378 } else {
379 apply_path = Some(entry.relative_path.clone());
380 }
381 } else if palette.select_index(*global_index) {
382 should_mark_dirty = true;
383 }
384 }
385
386 if let Some(path) = apply_path {
387 self.insert_file_reference(&path);
388 self.close_file_palette();
389 self.mark_dirty();
390 } else if should_mark_dirty {
391 self.mark_dirty();
392 }
393 return true;
394 }
395
396 if self.history_picker_visible() {
397 let Some(layout) = render::history_picker_panel_layout(self) else {
398 return true;
399 };
400 if let Some(local_index) = self.panel_row_index(&layout, column, row)
401 && !self.history_picker_state.matches.is_empty()
402 {
403 let actual_index = self
404 .history_picker_state
405 .scroll_offset()
406 .saturating_add(local_index);
407 if self.history_picker_state.selected_index() == Some(actual_index) {
408 let was_active = self.history_picker_visible();
409 self.history_picker_state
410 .accept(&mut self.core.input_manager);
411 self.finish_history_picker_interaction(was_active);
412 self.mark_dirty();
413 } else if self.history_picker_state.select_index(actual_index) {
414 self.mark_dirty();
415 }
416 }
417 return true;
418 }
419
420 if self.local_agents_visible() {
421 let Some(layout) = render::local_agents_panel_layout(self) else {
422 return true;
423 };
424 if let Some(local_index) = self.panel_row_index(&layout, column, row) {
425 let actual_index = self
426 .local_agents_state
427 .scroll_offset()
428 .saturating_add(local_index);
429 if self.local_agents_state.select_index(actual_index) {
430 self.mark_dirty();
431 }
432 }
433 return true;
434 }
435
436 if slash::slash_navigation_available(self) {
437 let Some(layout) = slash::slash_panel_layout(self) else {
438 return true;
439 };
440 if let Some(local_index) = self.panel_row_index(&layout, column, row) {
441 let actual_index = self
442 .slash_palette
443 .scroll_offset()
444 .saturating_add(local_index);
445 if self.slash_palette.selected_index() == Some(actual_index) {
446 slash::apply_selected_slash_suggestion(self);
447 } else {
448 slash::select_slash_suggestion_index(self, actual_index);
449 }
450 }
451 return true;
452 }
453
454 true
455 }
456
457 pub fn handle_event(
458 &mut self,
459 event: CrosstermEvent,
460 events: &UnboundedSender<InlineEvent>,
461 callback: Option<&(dyn Fn(&InlineEvent) + Send + Sync + 'static)>,
462 ) {
463 match event {
464 CrosstermEvent::Key(key) => {
465 self.update_held_key_modifiers(&key);
466 if matches!(key.kind, KeyEventKind::Press)
469 && let Some(outbound) = events::process_key(self, key)
470 {
471 events::emit_inline_event(&outbound, events, callback);
472 }
473 }
474 CrosstermEvent::Mouse(mouse_event) => {
475 if !self.core.fullscreen.interaction.mouse_capture {
476 return;
477 }
478
479 match mouse_event.kind {
480 MouseEventKind::Moved => {
481 if self
482 .update_transcript_file_link_hover(mouse_event.column, mouse_event.row)
483 {
484 self.mark_dirty();
485 }
486 }
487 MouseEventKind::ScrollDown => {
488 self.core.clear_pending_link_click();
489 self.core.mouse_selection.clear_click_history();
490 if !self.handle_active_overlay_scroll(mouse_event, true, events, callback)
491 && !self.handle_bottom_panel_scroll(true)
492 {
493 self.scroll_line_down();
494 self.mark_dirty();
495 }
496 }
497 MouseEventKind::ScrollUp => {
498 self.core.clear_pending_link_click();
499 self.core.mouse_selection.clear_click_history();
500 if !self.handle_active_overlay_scroll(mouse_event, false, events, callback)
501 && !self.handle_bottom_panel_scroll(false)
502 {
503 self.scroll_line_up();
504 self.mark_dirty();
505 }
506 }
507 MouseEventKind::Down(crossterm::event::MouseButton::Left) => {
508 self.core.clear_pending_link_click();
509 if self.core.queue_link_click_action(
510 self.transcript_file_link_click_action(
511 mouse_event.column,
512 mouse_event.row,
513 mouse_event.modifiers,
514 ),
515 ) {
516 self.core.mouse_selection.clear_click_history();
517 return;
518 }
519
520 if self.has_active_overlay() {
521 let in_modal_list = self.core.modal_list_area().is_some_and(|area| {
522 mouse_event.row >= area.y
523 && mouse_event.row < area.y.saturating_add(area.height)
524 && mouse_event.column >= area.x
525 && mouse_event.column < area.x.saturating_add(area.width)
526 });
527 if self
528 .core
529 .queue_link_click_action(self.modal_link_click_action(
530 mouse_event.column,
531 mouse_event.row,
532 mouse_event.modifiers,
533 ))
534 {
535 self.core.mouse_selection.clear_click_history();
536 return;
537 }
538
539 if self.modal_text_area_contains(mouse_event.column, mouse_event.row)
540 && !in_modal_list
541 {
542 let is_double_click = self.core.mouse_selection.register_click(
543 mouse_event.column,
544 mouse_event.row,
545 Instant::now(),
546 );
547 if is_double_click {
548 let modal_double_click_action =
549 self.core.throttle_link_click_action(
550 self.modal_link_double_click_action(
551 mouse_event.column,
552 mouse_event.row,
553 ),
554 );
555 if !matches!(
556 modal_double_click_action,
557 TranscriptLinkClickAction::Ignore
558 ) {
559 self.core.clear_pending_link_click();
560 }
561 if self.handle_link_click_action(
562 modal_double_click_action,
563 true,
564 events,
565 callback,
566 ) {
567 return;
568 }
569 }
570
571 self.core.mouse_drag_target = MouseDragTarget::ModalText;
572 self.core
573 .mouse_selection
574 .start_selection(mouse_event.column, mouse_event.row);
575 self.mark_dirty();
576 return;
577 }
578 }
579
580 if self.has_active_overlay()
581 && self.handle_active_overlay_click(mouse_event, events, callback)
582 {
583 self.core.mouse_selection.clear_click_history();
584 return;
585 }
586
587 if self.handle_bottom_panel_click(mouse_event) {
588 self.core.mouse_selection.clear_click_history();
589 return;
590 }
591
592 if self.handle_input_click(mouse_event) {
593 self.core.mouse_drag_target = MouseDragTarget::Input;
594 self.core.mouse_selection.clear();
595 return;
596 }
597
598 let is_double_click = self.core.mouse_selection.register_click(
599 mouse_event.column,
600 mouse_event.row,
601 Instant::now(),
602 );
603 if is_double_click {
604 let transcript_double_click_action =
605 self.core.throttle_link_click_action(
606 self.transcript_file_link_double_click_action(
607 mouse_event.column,
608 mouse_event.row,
609 ),
610 );
611 if !matches!(
612 transcript_double_click_action,
613 TranscriptLinkClickAction::Ignore
614 ) {
615 self.core.clear_pending_link_click();
616 }
617 if self.handle_link_click_action(
618 transcript_double_click_action,
619 true,
620 events,
621 callback,
622 ) {
623 return;
624 }
625
626 self.core.mouse_drag_target = MouseDragTarget::None;
627 let _ = self.handle_transcript_click(mouse_event);
628 if self
629 .core
630 .select_transcript_word_at(mouse_event.column, mouse_event.row)
631 {
632 self.mark_dirty();
633 } else {
634 self.core.mouse_selection.clear();
635 }
636 self.core.mouse_selection.clear_click_history();
637 return;
638 }
639
640 self.core.mouse_drag_target = MouseDragTarget::Transcript;
641 self.core
642 .mouse_selection
643 .start_selection(mouse_event.column, mouse_event.row);
644 self.mark_dirty();
645 self.handle_transcript_click(mouse_event);
646 }
647 MouseEventKind::Drag(crossterm::event::MouseButton::Left) => {
648 self.core.clear_pending_link_click();
649 match self.core.mouse_drag_target {
650 MouseDragTarget::Input => {
651 if let Some(cursor) = self.cursor_index_for_input_point(
652 mouse_event.column,
653 mouse_event.row,
654 ) && self.core.input_manager.cursor() != cursor
655 {
656 self.core.input_manager.set_cursor_with_selection(cursor);
657 self.mark_dirty();
658 }
659 }
660 MouseDragTarget::Transcript => {
661 self.core
662 .mouse_selection
663 .update_selection(mouse_event.column, mouse_event.row);
664 self.mark_dirty();
665 }
666 MouseDragTarget::ModalText => {
667 self.core
668 .mouse_selection
669 .update_selection(mouse_event.column, mouse_event.row);
670 self.mark_dirty();
671 }
672 MouseDragTarget::None => {}
673 }
674 }
675 MouseEventKind::Up(crossterm::event::MouseButton::Left) => {
676 let transcript_link_action = self.core.pending_link_click_action(
677 self.transcript_file_link_click_action(
678 mouse_event.column,
679 mouse_event.row,
680 mouse_event.modifiers,
681 ),
682 );
683 let modal_link_action =
684 self.core
685 .pending_link_click_action(self.modal_link_click_action(
686 mouse_event.column,
687 mouse_event.row,
688 mouse_event.modifiers,
689 ));
690 match self.core.mouse_drag_target {
691 MouseDragTarget::Input => {
692 if let Some(cursor) = self.cursor_index_for_input_point(
693 mouse_event.column,
694 mouse_event.row,
695 ) && self.core.input_manager.cursor() != cursor
696 {
697 self.core.input_manager.set_cursor_with_selection(cursor);
698 self.mark_dirty();
699 }
700 }
701 MouseDragTarget::Transcript => {
702 self.core
703 .mouse_selection
704 .finish_selection(mouse_event.column, mouse_event.row);
705 self.mark_dirty();
706 }
707 MouseDragTarget::ModalText => {
708 self.core
709 .mouse_selection
710 .finish_selection(mouse_event.column, mouse_event.row);
711 self.mark_dirty();
712 }
713 MouseDragTarget::None => {}
714 }
715 self.core.mouse_drag_target = MouseDragTarget::None;
716 self.core.clear_pending_link_click();
717 if self.handle_link_click_action(
718 transcript_link_action,
719 false,
720 events,
721 callback,
722 ) {
723 return;
724 }
725 if self.handle_link_click_action(modal_link_action, false, events, callback)
726 {
727 }
728 }
729 _ => {}
730 }
731 }
732 CrosstermEvent::Paste(content) => {
733 if let Some(event) = events::handle_paste(self, &content) {
734 events::emit_inline_event(&event, events, callback);
735 }
736 }
737 CrosstermEvent::Resize(_, rows) => {
738 self.apply_view_rows(rows);
739 self.mark_dirty();
740 }
741 CrosstermEvent::FocusGained => {
742 }
744 CrosstermEvent::FocusLost => {
745 self.clear_held_key_modifiers();
746 }
747 }
748 }
749
750 pub(crate) fn handle_transcript_click(&mut self, mouse_event: MouseEvent) -> bool {
751 if !matches!(
752 mouse_event.kind,
753 MouseEventKind::Down(crossterm::event::MouseButton::Left)
754 ) {
755 return false;
756 }
757
758 let Some(area) = self.core.transcript_area() else {
759 return false;
760 };
761
762 if mouse_event.row < area.y
763 || mouse_event.row >= area.y.saturating_add(area.height)
764 || mouse_event.column < area.x
765 || mouse_event.column >= area.x.saturating_add(area.width)
766 {
767 return false;
768 }
769
770 if self.core.transcript_width == 0 || self.core.transcript_rows == 0 {
771 return false;
772 }
773
774 let row_in_view = (mouse_event.row - area.y) as usize;
775 if row_in_view >= self.core.transcript_rows as usize {
776 return false;
777 }
778
779 let viewport_rows = self.core.transcript_rows.max(1) as usize;
780 let transcript_width = self.core.transcript_width;
781 let padding = usize::from(ui::INLINE_TRANSCRIPT_BOTTOM_PADDING);
782 let effective_padding = padding.min(viewport_rows.saturating_sub(1));
783 let total_rows = self.total_transcript_rows(transcript_width) + effective_padding;
784 let (top_offset, _clamped_total_rows) =
785 self.prepare_transcript_scroll(total_rows, viewport_rows);
786 let view_top = top_offset.min(self.core.scroll_manager.max_offset());
787 self.core.transcript_view_top = view_top;
788
789 let clicked_row = view_top.saturating_add(row_in_view);
790 let expanded = self.expand_collapsed_paste_at_row(transcript_width, clicked_row);
791 if expanded {
792 self.mark_dirty();
793 }
794 expanded
795 }
796
797 pub(crate) fn handle_input_click(&mut self, mouse_event: MouseEvent) -> bool {
798 if !matches!(
799 mouse_event.kind,
800 MouseEventKind::Down(crossterm::event::MouseButton::Left)
801 ) {
802 return false;
803 }
804
805 if !self.input_area_contains(mouse_event.column, mouse_event.row) {
806 return false;
807 }
808
809 let cursor_at_end =
810 self.core.input_manager.cursor() == self.core.input_manager.content().len();
811 if self.core.input_compact_mode()
812 && cursor_at_end
813 && self.input_compact_placeholder().is_some()
814 {
815 self.core.set_input_compact_mode(false);
816 self.mark_dirty();
817 return true;
818 }
819
820 if let Some(cursor) = self.cursor_index_for_input_point(mouse_event.column, mouse_event.row)
821 {
822 if self.core.input_manager.cursor() != cursor {
823 self.core.input_manager.set_cursor(cursor);
824 self.mark_dirty();
825 }
826 return true;
827 }
828
829 false
830 }
831}