1use alacritty_terminal::{
2 grid::{Dimensions, GridIterator, Indexed, Scroll},
3 index::{Column as GridCol, Line as GridLine, Point, Side},
4 selection::{Selection, SelectionRange, SelectionType},
5 sync::FairMutex,
6 term::{
7 cell::{Cell, Flags},
8 SizeInfo,
9 },
10 Term,
11};
12use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
13use gpui::{
14 color::Color,
15 elements::*,
16 fonts::{TextStyle, Underline},
17 geometry::{
18 rect::RectF,
19 vector::{vec2f, Vector2F},
20 },
21 json::json,
22 text_layout::{Line, RunStyle},
23 Event, FontCache, KeyDownEvent, MouseRegion, PaintContext, Quad, ScrollWheelEvent,
24 SizeConstraint, TextLayoutCache, WeakModelHandle,
25};
26use itertools::Itertools;
27use ordered_float::OrderedFloat;
28use settings::Settings;
29use theme::TerminalStyle;
30
31use std::{cmp::min, ops::Range, rc::Rc, sync::Arc};
32use std::{fmt::Debug, ops::Sub};
33
34use crate::{color_translation::convert_color, connection::TerminalConnection, ZedListener};
35
36///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
37///Scroll multiplier that is set to 3 by default. This will be removed when I
38///Implement scroll bars.
39const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
40
41///Used to display the grid as passed to Alacritty and the TTY.
42///Useful for debugging inconsistencies between behavior and display
43#[cfg(debug_assertions)]
44const DEBUG_GRID: bool = false;
45
46///The GPUI element that paints the terminal.
47///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
48pub struct TerminalEl {
49 connection: WeakModelHandle<TerminalConnection>,
50 view_id: usize,
51 modal: bool,
52}
53
54///New type pattern so I don't mix these two up
55struct CellWidth(f32);
56struct LineHeight(f32);
57
58struct LayoutLine {
59 cells: Vec<LayoutCell>,
60 highlighted_range: Option<Range<usize>>,
61}
62
63///New type pattern to ensure that we use adjusted mouse positions throughout the code base, rather than
64struct PaneRelativePos(Vector2F);
65
66///Functionally the constructor for the PaneRelativePos type, mutates the mouse_position
67fn relative_pos(mouse_position: Vector2F, origin: Vector2F) -> PaneRelativePos {
68 PaneRelativePos(mouse_position.sub(origin)) //Avoid the extra allocation by mutating
69}
70
71#[derive(Clone, Debug, Default)]
72struct LayoutCell {
73 point: Point<i32, i32>,
74 text: Line, //NOTE TO SELF THIS IS BAD PERFORMANCE RN!
75 background_color: Color,
76}
77
78impl LayoutCell {
79 fn new(point: Point<i32, i32>, text: Line, background_color: Color) -> LayoutCell {
80 LayoutCell {
81 point,
82 text,
83 background_color,
84 }
85 }
86}
87
88///The information generated during layout that is nescessary for painting
89pub struct LayoutState {
90 layout_lines: Vec<LayoutLine>,
91 line_height: LineHeight,
92 em_width: CellWidth,
93 cursor: Option<Cursor>,
94 background_color: Color,
95 cur_size: SizeInfo,
96 terminal: Arc<FairMutex<Term<ZedListener>>>,
97 selection_color: Color,
98}
99
100impl TerminalEl {
101 pub fn new(
102 view_id: usize,
103 connection: WeakModelHandle<TerminalConnection>,
104 modal: bool,
105 ) -> TerminalEl {
106 TerminalEl {
107 view_id,
108 connection,
109 modal,
110 }
111 }
112}
113
114impl Element for TerminalEl {
115 type LayoutState = LayoutState;
116 type PaintState = ();
117
118 fn layout(
119 &mut self,
120 constraint: gpui::SizeConstraint,
121 cx: &mut gpui::LayoutContext,
122 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
123 //Settings immutably borrows cx here for the settings and font cache
124 //and we need to modify the cx to resize the terminal. So instead of
125 //storing Settings or the font_cache(), we toss them ASAP and then reborrow later
126 let text_style = make_text_style(cx.font_cache(), cx.global::<Settings>());
127 let line_height = LineHeight(cx.font_cache().line_height(text_style.font_size));
128 let cell_width = CellWidth(
129 cx.font_cache()
130 .em_advance(text_style.font_id, text_style.font_size),
131 );
132 let connection_handle = self.connection.upgrade(cx).unwrap();
133
134 //Tell the view our new size. Requires a mutable borrow of cx and the view
135 let cur_size = make_new_size(constraint, &cell_width, &line_height);
136 //Note that set_size locks and mutates the terminal.
137 connection_handle.update(cx.app, |connection, _| connection.set_size(cur_size));
138
139 let (selection_color, terminal_theme) = {
140 let theme = &(cx.global::<Settings>()).theme;
141 (theme.editor.selection.selection, &theme.terminal)
142 };
143
144 let terminal_mutex = connection_handle.read(cx).term.clone();
145 let term = terminal_mutex.lock();
146 let grid = term.grid();
147 let cursor_point = grid.cursor.point;
148 let cursor_text = grid[cursor_point.line][cursor_point.column].c.to_string();
149
150 let content = term.renderable_content();
151
152 let layout_lines = layout_lines(
153 content.display_iter,
154 &text_style,
155 terminal_theme,
156 cx.text_layout_cache,
157 self.modal,
158 content.selection,
159 );
160
161 let block_text = cx.text_layout_cache.layout_str(
162 &cursor_text,
163 text_style.font_size,
164 &[(
165 cursor_text.len(),
166 RunStyle {
167 font_id: text_style.font_id,
168 color: terminal_theme.colors.background,
169 underline: Default::default(),
170 },
171 )],
172 );
173
174 let cursor = get_cursor_shape(
175 content.cursor.point.line.0 as usize,
176 content.cursor.point.column.0 as usize,
177 content.display_offset,
178 &line_height,
179 &cell_width,
180 cur_size.total_lines(),
181 &block_text,
182 )
183 .map(move |(cursor_position, block_width)| {
184 let block_width = if block_width != 0.0 {
185 block_width
186 } else {
187 cell_width.0
188 };
189
190 Cursor::new(
191 cursor_position,
192 block_width,
193 line_height.0,
194 terminal_theme.colors.cursor,
195 CursorShape::Block,
196 Some(block_text.clone()),
197 )
198 });
199 drop(term);
200
201 let background_color = if self.modal {
202 terminal_theme.colors.modal_background
203 } else {
204 terminal_theme.colors.background
205 };
206
207 (
208 constraint.max,
209 LayoutState {
210 layout_lines,
211 line_height,
212 em_width: cell_width,
213 cursor,
214 cur_size,
215 background_color,
216 terminal: terminal_mutex,
217 selection_color,
218 },
219 )
220 }
221
222 fn paint(
223 &mut self,
224 bounds: gpui::geometry::rect::RectF,
225 visible_bounds: gpui::geometry::rect::RectF,
226 layout: &mut Self::LayoutState,
227 cx: &mut gpui::PaintContext,
228 ) -> Self::PaintState {
229 //Setup element stuff
230 let clip_bounds = Some(visible_bounds);
231
232 cx.paint_layer(clip_bounds, |cx| {
233 let cur_size = layout.cur_size.clone();
234 let origin = bounds.origin() + vec2f(layout.em_width.0, 0.);
235
236 //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
237 attach_mouse_handlers(
238 origin,
239 cur_size,
240 self.view_id,
241 &layout.terminal,
242 visible_bounds,
243 cx,
244 );
245
246 cx.paint_layer(clip_bounds, |cx| {
247 //Start with a background color
248 cx.scene.push_quad(Quad {
249 bounds: RectF::new(bounds.origin(), bounds.size()),
250 background: Some(layout.background_color),
251 border: Default::default(),
252 corner_radius: 0.,
253 });
254
255 //Draw cell backgrounds
256 for layout_line in &layout.layout_lines {
257 for layout_cell in &layout_line.cells {
258 let position = vec2f(
259 origin.x() + layout_cell.point.column as f32 * layout.em_width.0,
260 origin.y() + layout_cell.point.line as f32 * layout.line_height.0,
261 );
262 let size = vec2f(layout.em_width.0, layout.line_height.0);
263
264 cx.scene.push_quad(Quad {
265 bounds: RectF::new(position, size),
266 background: Some(layout_cell.background_color),
267 border: Default::default(),
268 corner_radius: 0.,
269 })
270 }
271 }
272 });
273
274 //Draw Selection
275 cx.paint_layer(clip_bounds, |cx| {
276 let mut highlight_y = None;
277 let highlight_lines = layout
278 .layout_lines
279 .iter()
280 .filter_map(|line| {
281 if let Some(range) = &line.highlighted_range {
282 if let None = highlight_y {
283 highlight_y = Some(
284 origin.y()
285 + line.cells[0].point.line as f32 * layout.line_height.0,
286 );
287 }
288 let start_x = origin.x()
289 + line.cells[range.start].point.column as f32 * layout.em_width.0;
290 let end_x = origin.x()
291 + line.cells[range.end].point.column as f32 * layout.em_width.0
292 + layout.em_width.0;
293
294 return Some(HighlightedRangeLine { start_x, end_x });
295 } else {
296 return None;
297 }
298 })
299 .collect::<Vec<HighlightedRangeLine>>();
300
301 if let Some(y) = highlight_y {
302 let hr = HighlightedRange {
303 start_y: y, //Need to change this
304 line_height: layout.line_height.0,
305 lines: highlight_lines,
306 color: layout.selection_color,
307 //Copied from editor. TODO: move to theme or something
308 corner_radius: 0.15 * layout.line_height.0,
309 };
310 hr.paint(bounds, cx.scene);
311 }
312 });
313
314 cx.paint_layer(clip_bounds, |cx| {
315 for layout_line in &layout.layout_lines {
316 for layout_cell in &layout_line.cells {
317 let point = layout_cell.point;
318
319 //Don't actually know the start_x for a line, until here:
320 let cell_origin = vec2f(
321 origin.x() + point.column as f32 * layout.em_width.0,
322 origin.y() + point.line as f32 * layout.line_height.0,
323 );
324
325 layout_cell.text.paint(
326 cell_origin,
327 visible_bounds,
328 layout.line_height.0,
329 cx,
330 );
331 }
332 }
333 });
334
335 //Draw cursor
336 if let Some(cursor) = &layout.cursor {
337 cx.paint_layer(clip_bounds, |cx| {
338 cursor.paint(origin, cx);
339 })
340 }
341
342 #[cfg(debug_assertions)]
343 if DEBUG_GRID {
344 cx.paint_layer(clip_bounds, |cx| {
345 draw_debug_grid(bounds, layout, cx);
346 })
347 }
348 });
349 }
350
351 fn dispatch_event(
352 &mut self,
353 event: &gpui::Event,
354 _bounds: gpui::geometry::rect::RectF,
355 visible_bounds: gpui::geometry::rect::RectF,
356 layout: &mut Self::LayoutState,
357 _paint: &mut Self::PaintState,
358 cx: &mut gpui::EventContext,
359 ) -> bool {
360 match event {
361 Event::ScrollWheel(ScrollWheelEvent {
362 delta, position, ..
363 }) => visible_bounds
364 .contains_point(*position)
365 .then(|| {
366 let vertical_scroll =
367 (delta.y() / layout.line_height.0) * ALACRITTY_SCROLL_MULTIPLIER;
368
369 if let Some(connection) = self.connection.upgrade(cx.app) {
370 connection.update(cx.app, |connection, _| {
371 connection
372 .term
373 .lock()
374 .scroll_display(Scroll::Delta(vertical_scroll.round() as i32));
375 })
376 }
377 })
378 .is_some(),
379 Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
380 if !cx.is_parent_view_focused() {
381 return false;
382 }
383
384 self.connection
385 .upgrade(cx.app)
386 .map(|connection| {
387 connection
388 .update(cx.app, |connection, _| connection.try_keystroke(keystroke))
389 })
390 .unwrap_or(false)
391 }
392 _ => false,
393 }
394 }
395
396 fn debug(
397 &self,
398 _bounds: gpui::geometry::rect::RectF,
399 _layout: &Self::LayoutState,
400 _paint: &Self::PaintState,
401 _cx: &gpui::DebugContext,
402 ) -> gpui::serde_json::Value {
403 json!({
404 "type": "TerminalElement",
405 })
406 }
407}
408
409pub fn mouse_to_cell_data(
410 pos: Vector2F,
411 origin: Vector2F,
412 cur_size: SizeInfo,
413 display_offset: usize,
414) -> (Point, alacritty_terminal::index::Direction) {
415 let relative_pos = relative_pos(pos, origin);
416 let point = grid_cell(&relative_pos, cur_size, display_offset);
417 let side = cell_side(&relative_pos, cur_size);
418 (point, side)
419}
420
421///Configures a text style from the current settings.
422fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
423 TextStyle {
424 color: settings.theme.editor.text_color,
425 font_family_id: settings.buffer_font_family,
426 font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(),
427 font_id: font_cache
428 .select_font(settings.buffer_font_family, &Default::default())
429 .unwrap(),
430 font_size: settings.buffer_font_size,
431 font_properties: Default::default(),
432 underline: Default::default(),
433 }
434}
435
436///Configures a size info object from the given information.
437fn make_new_size(
438 constraint: SizeConstraint,
439 cell_width: &CellWidth,
440 line_height: &LineHeight,
441) -> SizeInfo {
442 SizeInfo::new(
443 constraint.max.x() - cell_width.0,
444 constraint.max.y(),
445 cell_width.0,
446 line_height.0,
447 0.,
448 0.,
449 false,
450 )
451}
452
453fn layout_lines(
454 grid: GridIterator<Cell>,
455 text_style: &TextStyle,
456 terminal_theme: &TerminalStyle,
457 text_layout_cache: &TextLayoutCache,
458 modal: bool,
459 selection_range: Option<SelectionRange>,
460) -> Vec<LayoutLine> {
461 let lines = grid.group_by(|i| i.point.line);
462 lines
463 .into_iter()
464 .enumerate()
465 .map(|(line_index, (_, line))| {
466 let mut highlighted_range = None;
467 let cells = line
468 .enumerate()
469 .map(|(x_index, indexed_cell)| {
470 if selection_range
471 .map(|range| range.contains(indexed_cell.point))
472 .unwrap_or(false)
473 {
474 let mut range = highlighted_range.take().unwrap_or(x_index..x_index);
475 range.end = range.end.max(x_index);
476 highlighted_range = Some(range);
477 }
478
479 let cell_text = &indexed_cell.c.to_string();
480
481 let cell_style = cell_style(&indexed_cell, terminal_theme, text_style, modal);
482
483 //This is where we might be able to get better performance
484 let layout_cell = text_layout_cache.layout_str(
485 cell_text,
486 text_style.font_size,
487 &[(cell_text.len(), cell_style)],
488 );
489
490 LayoutCell::new(
491 Point::new(line_index as i32, indexed_cell.point.column.0 as i32),
492 layout_cell,
493 convert_color(&indexed_cell.bg, &terminal_theme.colors, modal),
494 )
495 })
496 .collect::<Vec<LayoutCell>>();
497
498 LayoutLine {
499 cells,
500 highlighted_range,
501 }
502 })
503 .collect::<Vec<LayoutLine>>()
504}
505
506// Compute the cursor position and expected block width, may return a zero width if x_for_index returns
507// the same position for sequential indexes. Use em_width instead
508//TODO: This function is messy, too many arguments and too many ifs. Simplify.
509fn get_cursor_shape(
510 line: usize,
511 line_index: usize,
512 display_offset: usize,
513 line_height: &LineHeight,
514 cell_width: &CellWidth,
515 total_lines: usize,
516 text_fragment: &Line,
517) -> Option<(Vector2F, f32)> {
518 let cursor_line = line + display_offset;
519 if cursor_line <= total_lines {
520 let cursor_width = if text_fragment.width() == 0. {
521 cell_width.0
522 } else {
523 text_fragment.width()
524 };
525
526 Some((
527 vec2f(
528 line_index as f32 * cell_width.0,
529 cursor_line as f32 * line_height.0,
530 ),
531 cursor_width,
532 ))
533 } else {
534 None
535 }
536}
537
538///Convert the Alacritty cell styles to GPUI text styles and background color
539fn cell_style(
540 indexed: &Indexed<&Cell>,
541 style: &TerminalStyle,
542 text_style: &TextStyle,
543 modal: bool,
544) -> RunStyle {
545 let flags = indexed.cell.flags;
546 let fg = convert_color(&indexed.cell.fg, &style.colors, modal);
547
548 let underline = flags
549 .contains(Flags::UNDERLINE)
550 .then(|| Underline {
551 color: Some(fg),
552 squiggly: false,
553 thickness: OrderedFloat(1.),
554 })
555 .unwrap_or_default();
556
557 RunStyle {
558 color: fg,
559 font_id: text_style.font_id,
560 underline,
561 }
562}
563
564fn attach_mouse_handlers(
565 origin: Vector2F,
566 cur_size: SizeInfo,
567 view_id: usize,
568 terminal_mutex: &Arc<FairMutex<Term<ZedListener>>>,
569 visible_bounds: RectF,
570 cx: &mut PaintContext,
571) {
572 let click_mutex = terminal_mutex.clone();
573 let drag_mutex = terminal_mutex.clone();
574 let mouse_down_mutex = terminal_mutex.clone();
575
576 cx.scene.push_mouse_region(MouseRegion {
577 view_id,
578 mouse_down: Some(Rc::new(move |pos, _| {
579 let mut term = mouse_down_mutex.lock();
580 let (point, side) = mouse_to_cell_data(
581 pos,
582 origin,
583 cur_size,
584 term.renderable_content().display_offset,
585 );
586 term.selection = Some(Selection::new(SelectionType::Simple, point, side))
587 })),
588 click: Some(Rc::new(move |pos, click_count, cx| {
589 let mut term = click_mutex.lock();
590
591 let (point, side) = mouse_to_cell_data(
592 pos,
593 origin,
594 cur_size,
595 term.renderable_content().display_offset,
596 );
597
598 let selection_type = match click_count {
599 0 => return, //This is a release
600 1 => Some(SelectionType::Simple),
601 2 => Some(SelectionType::Semantic),
602 3 => Some(SelectionType::Lines),
603 _ => None,
604 };
605
606 let selection =
607 selection_type.map(|selection_type| Selection::new(selection_type, point, side));
608
609 term.selection = selection;
610 cx.focus_parent_view();
611 cx.notify();
612 })),
613 bounds: visible_bounds,
614 drag: Some(Rc::new(move |_delta, pos, cx| {
615 let mut term = drag_mutex.lock();
616
617 let (point, side) = mouse_to_cell_data(
618 pos,
619 origin,
620 cur_size,
621 term.renderable_content().display_offset,
622 );
623
624 if let Some(mut selection) = term.selection.take() {
625 selection.update(point, side);
626 term.selection = Some(selection);
627 }
628
629 cx.notify();
630 })),
631 ..Default::default()
632 });
633}
634
635///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
636fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side {
637 let x = pos.0.x() as usize;
638 let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize;
639 let half_cell_width = (cur_size.cell_width() / 2.0) as usize;
640
641 let additional_padding =
642 (cur_size.width() - cur_size.cell_width() * 2.) % cur_size.cell_width();
643 let end_of_grid = cur_size.width() - cur_size.cell_width() - additional_padding;
644
645 if cell_x > half_cell_width
646 // Edge case when mouse leaves the window.
647 || x as f32 >= end_of_grid
648 {
649 Side::Right
650 } else {
651 Side::Left
652 }
653}
654
655///Copied (with modifications) from alacritty/src/event.rs > Mouse::point()
656///Position is a pane-relative position. That means the top left corner of the mouse
657///Region should be (0,0)
658fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) -> Point {
659 let pos = pos.0;
660 let col = pos.x() / cur_size.cell_width(); //TODO: underflow...
661 let col = min(GridCol(col as usize), cur_size.last_column());
662
663 let line = pos.y() / cur_size.cell_height();
664 let line = min(line as i32, cur_size.bottommost_line().0);
665
666 //when clicking, need to ADD to get to the top left cell
667 //e.g. total_lines - viewport_height, THEN subtract display offset
668 //0 -> total_lines - viewport_height - display_offset + mouse_line
669
670 Point::new(GridLine(line - display_offset as i32), col)
671}
672
673///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between
674///Display and conceptual grid.
675#[cfg(debug_assertions)]
676fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
677 let width = layout.cur_size.width();
678 let height = layout.cur_size.height();
679 //Alacritty uses 'as usize', so shall we.
680 for col in 0..(width / layout.em_width.0).round() as usize {
681 cx.scene.push_quad(Quad {
682 bounds: RectF::new(
683 bounds.origin() + vec2f((col + 1) as f32 * layout.em_width.0, 0.),
684 vec2f(1., height),
685 ),
686 background: Some(Color::green()),
687 border: Default::default(),
688 corner_radius: 0.,
689 });
690 }
691 for row in 0..((height / layout.line_height.0) + 1.0).round() as usize {
692 cx.scene.push_quad(Quad {
693 bounds: RectF::new(
694 bounds.origin() + vec2f(layout.em_width.0, row as f32 * layout.line_height.0),
695 vec2f(width, 1.),
696 ),
697 background: Some(Color::green()),
698 border: Default::default(),
699 corner_radius: 0.,
700 });
701 }
702}
703
704mod test {
705
706 #[test]
707 fn test_mouse_to_selection() {
708 let term_width = 100.;
709 let term_height = 200.;
710 let cell_width = 10.;
711 let line_height = 20.;
712 let mouse_pos_x = 100.; //Window relative
713 let mouse_pos_y = 100.; //Window relative
714 let origin_x = 10.;
715 let origin_y = 20.;
716
717 let cur_size = alacritty_terminal::term::SizeInfo::new(
718 term_width,
719 term_height,
720 cell_width,
721 line_height,
722 0.,
723 0.,
724 false,
725 );
726
727 let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
728 let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
729 let (point, _) =
730 crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
731 assert_eq!(
732 point,
733 alacritty_terminal::index::Point::new(
734 alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
735 alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
736 )
737 );
738 }
739
740 #[test]
741 fn test_mouse_to_selection_off_edge() {
742 let term_width = 100.;
743 let term_height = 200.;
744 let cell_width = 10.;
745 let line_height = 20.;
746 let mouse_pos_x = 100.; //Window relative
747 let mouse_pos_y = 100.; //Window relative
748 let origin_x = 10.;
749 let origin_y = 20.;
750
751 let cur_size = alacritty_terminal::term::SizeInfo::new(
752 term_width,
753 term_height,
754 cell_width,
755 line_height,
756 0.,
757 0.,
758 false,
759 );
760
761 let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
762 let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
763 let (point, _) =
764 crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
765 assert_eq!(
766 point,
767 alacritty_terminal::index::Point::new(
768 alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
769 alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
770 )
771 );
772 }
773}