1use alacritty_terminal::{
2 ansi::{Color::Named, NamedColor},
3 event::WindowSize,
4 grid::{Dimensions, GridIterator, Indexed, Scroll},
5 index::{Column as GridCol, Line as GridLine, Point, Side},
6 selection::SelectionRange,
7 term::cell::{Cell, Flags},
8};
9use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
10use gpui::{
11 color::Color,
12 elements::*,
13 fonts::{TextStyle, Underline},
14 geometry::{
15 rect::RectF,
16 vector::{vec2f, Vector2F},
17 },
18 json::json,
19 text_layout::{Line, RunStyle},
20 Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion,
21 PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle,
22};
23use itertools::Itertools;
24use ordered_float::OrderedFloat;
25use settings::Settings;
26use theme::TerminalStyle;
27use util::ResultExt;
28
29use std::{cmp::min, ops::Range};
30use std::{fmt::Debug, ops::Sub};
31
32use crate::{mappings::colors::convert_color, model::Terminal, ConnectedView};
33
34///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
35///Scroll multiplier that is set to 3 by default. This will be removed when I
36///Implement scroll bars.
37const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
38
39///The information generated during layout that is nescessary for painting
40pub struct LayoutState {
41 cells: Vec<LayoutCell>,
42 rects: Vec<LayoutRect>,
43 highlights: Vec<RelativeHighlightedRange>,
44 cursor: Option<Cursor>,
45 background_color: Color,
46 selection_color: Color,
47 size: TermDimensions,
48}
49
50///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
51struct DisplayCursor {
52 line: i32,
53 col: usize,
54}
55
56impl DisplayCursor {
57 fn from(cursor_point: Point, display_offset: usize) -> Self {
58 Self {
59 line: cursor_point.line.0 + display_offset as i32,
60 col: cursor_point.column.0,
61 }
62 }
63
64 pub fn line(&self) -> i32 {
65 self.line
66 }
67
68 pub fn col(&self) -> usize {
69 self.col
70 }
71}
72
73#[derive(Clone, Copy, Debug)]
74pub struct TermDimensions {
75 cell_width: f32,
76 line_height: f32,
77 height: f32,
78 width: f32,
79}
80
81impl TermDimensions {
82 pub fn new(line_height: f32, cell_width: f32, size: Vector2F) -> Self {
83 TermDimensions {
84 cell_width,
85 line_height,
86 width: size.x(),
87 height: size.y(),
88 }
89 }
90
91 pub fn num_lines(&self) -> usize {
92 (self.height / self.line_height).floor() as usize
93 }
94
95 pub fn num_columns(&self) -> usize {
96 (self.width / self.cell_width).floor() as usize
97 }
98
99 pub fn height(&self) -> f32 {
100 self.height
101 }
102
103 pub fn width(&self) -> f32 {
104 self.width
105 }
106
107 pub fn cell_width(&self) -> f32 {
108 self.cell_width
109 }
110
111 pub fn line_height(&self) -> f32 {
112 self.line_height
113 }
114}
115
116impl Into<WindowSize> for TermDimensions {
117 fn into(self) -> WindowSize {
118 WindowSize {
119 num_lines: self.num_lines() as u16,
120 num_cols: self.num_columns() as u16,
121 cell_width: self.cell_width() as u16,
122 cell_height: self.line_height() as u16,
123 }
124 }
125}
126
127impl Dimensions for TermDimensions {
128 fn total_lines(&self) -> usize {
129 self.screen_lines() //TODO: Check that this is fine. This is supposed to be for the back buffer...
130 }
131
132 fn screen_lines(&self) -> usize {
133 self.num_lines()
134 }
135
136 fn columns(&self) -> usize {
137 self.num_columns()
138 }
139}
140
141#[derive(Clone, Debug, Default)]
142struct LayoutCell {
143 point: Point<i32, i32>,
144 text: Line,
145}
146
147impl LayoutCell {
148 fn new(point: Point<i32, i32>, text: Line) -> LayoutCell {
149 LayoutCell { point, text }
150 }
151
152 fn paint(
153 &self,
154 origin: Vector2F,
155 layout: &LayoutState,
156 visible_bounds: RectF,
157 cx: &mut PaintContext,
158 ) {
159 let pos = point_to_absolute(origin, self.point, layout);
160 self.text
161 .paint(pos, visible_bounds, layout.size.line_height, cx);
162 }
163}
164
165#[derive(Clone, Debug, Default)]
166struct LayoutRect {
167 point: Point<i32, i32>,
168 num_of_cells: usize,
169 color: Color,
170}
171
172impl LayoutRect {
173 fn new(point: Point<i32, i32>, num_of_cells: usize, color: Color) -> LayoutRect {
174 LayoutRect {
175 point,
176 num_of_cells,
177 color,
178 }
179 }
180
181 fn extend(&self) -> Self {
182 LayoutRect {
183 point: self.point,
184 num_of_cells: self.num_of_cells + 1,
185 color: self.color,
186 }
187 }
188
189 fn paint(&self, origin: Vector2F, layout: &LayoutState, cx: &mut PaintContext) {
190 let position = point_to_absolute(origin, self.point, layout);
191
192 let size = vec2f(
193 (layout.size.cell_width.ceil() * self.num_of_cells as f32).ceil(),
194 layout.size.line_height,
195 );
196
197 cx.scene.push_quad(Quad {
198 bounds: RectF::new(position, size),
199 background: Some(self.color),
200 border: Default::default(),
201 corner_radius: 0.,
202 })
203 }
204}
205
206fn point_to_absolute(origin: Vector2F, point: Point<i32, i32>, layout: &LayoutState) -> Vector2F {
207 vec2f(
208 (origin.x() + point.column as f32 * layout.size.cell_width).floor(),
209 origin.y() + point.line as f32 * layout.size.line_height,
210 )
211}
212
213#[derive(Clone, Debug, Default)]
214struct RelativeHighlightedRange {
215 line_index: usize,
216 range: Range<usize>,
217}
218
219impl RelativeHighlightedRange {
220 fn new(line_index: usize, range: Range<usize>) -> Self {
221 RelativeHighlightedRange { line_index, range }
222 }
223
224 fn to_highlighted_range_line(
225 &self,
226 origin: Vector2F,
227 layout: &LayoutState,
228 ) -> HighlightedRangeLine {
229 let start_x = origin.x() + self.range.start as f32 * layout.size.cell_width;
230 let end_x =
231 origin.x() + self.range.end as f32 * layout.size.cell_width + layout.size.cell_width;
232
233 return HighlightedRangeLine { start_x, end_x };
234 }
235}
236
237///The GPUI element that paints the terminal.
238///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?
239pub struct TerminalEl {
240 terminal: WeakModelHandle<Terminal>,
241 view: WeakViewHandle<ConnectedView>,
242 modal: bool,
243}
244
245impl TerminalEl {
246 pub fn new(
247 view: WeakViewHandle<ConnectedView>,
248 terminal: WeakModelHandle<Terminal>,
249 modal: bool,
250 ) -> TerminalEl {
251 TerminalEl {
252 view,
253 terminal,
254 modal,
255 }
256 }
257
258 fn layout_grid(
259 grid: GridIterator<Cell>,
260 text_style: &TextStyle,
261 terminal_theme: &TerminalStyle,
262 text_layout_cache: &TextLayoutCache,
263 modal: bool,
264 selection_range: Option<SelectionRange>,
265 ) -> (
266 Vec<LayoutCell>,
267 Vec<LayoutRect>,
268 Vec<RelativeHighlightedRange>,
269 ) {
270 let mut cells = vec![];
271 let mut rects = vec![];
272 let mut highlight_ranges = vec![];
273
274 let mut cur_rect: Option<LayoutRect> = None;
275 let mut cur_alac_color = None;
276 let mut highlighted_range = None;
277
278 let linegroups = grid.group_by(|i| i.point.line);
279 for (line_index, (_, line)) in linegroups.into_iter().enumerate() {
280 for (x_index, cell) in line.enumerate() {
281 //Increase selection range
282 {
283 if selection_range
284 .map(|range| range.contains(cell.point))
285 .unwrap_or(false)
286 {
287 let mut range = highlighted_range.take().unwrap_or(x_index..x_index);
288 range.end = range.end.max(x_index);
289 highlighted_range = Some(range);
290 }
291 }
292
293 //Expand background rect range
294 {
295 if matches!(cell.bg, Named(NamedColor::Background)) {
296 //Continue to next cell, resetting variables if nescessary
297 cur_alac_color = None;
298 if let Some(rect) = cur_rect {
299 rects.push(rect);
300 cur_rect = None
301 }
302 } else {
303 match cur_alac_color {
304 Some(cur_color) => {
305 if cell.bg == cur_color {
306 cur_rect = cur_rect.take().map(|rect| rect.extend());
307 } else {
308 cur_alac_color = Some(cell.bg);
309 if let Some(_) = cur_rect {
310 rects.push(cur_rect.take().unwrap());
311 }
312 cur_rect = Some(LayoutRect::new(
313 Point::new(line_index as i32, cell.point.column.0 as i32),
314 1,
315 convert_color(&cell.bg, &terminal_theme.colors, modal),
316 ));
317 }
318 }
319 None => {
320 cur_alac_color = Some(cell.bg);
321 cur_rect = Some(LayoutRect::new(
322 Point::new(line_index as i32, cell.point.column.0 as i32),
323 1,
324 convert_color(&cell.bg, &terminal_theme.colors, modal),
325 ));
326 }
327 }
328 }
329 }
330
331 //Layout current cell text
332 {
333 let cell_text = &cell.c.to_string();
334 if cell_text != " " {
335 let cell_style =
336 TerminalEl::cell_style(&cell, terminal_theme, text_style, modal);
337
338 let layout_cell = text_layout_cache.layout_str(
339 cell_text,
340 text_style.font_size,
341 &[(cell_text.len(), cell_style)],
342 );
343
344 cells.push(LayoutCell::new(
345 Point::new(line_index as i32, cell.point.column.0 as i32),
346 layout_cell,
347 ))
348 }
349 };
350 }
351
352 if highlighted_range.is_some() {
353 highlight_ranges.push(RelativeHighlightedRange::new(
354 line_index,
355 highlighted_range.take().unwrap(),
356 ))
357 }
358
359 if cur_rect.is_some() {
360 rects.push(cur_rect.take().unwrap());
361 }
362 }
363
364 (cells, rects, highlight_ranges)
365 }
366
367 // Compute the cursor position and expected block width, may return a zero width if x_for_index returns
368 // the same position for sequential indexes. Use em_width instead
369 fn shape_cursor(
370 cursor_point: DisplayCursor,
371 size: TermDimensions,
372 text_fragment: &Line,
373 ) -> Option<(Vector2F, f32)> {
374 if cursor_point.line() < size.total_lines() as i32 {
375 let cursor_width = if text_fragment.width() == 0. {
376 size.cell_width()
377 } else {
378 text_fragment.width()
379 };
380
381 Some((
382 vec2f(
383 cursor_point.col() as f32 * size.cell_width(),
384 cursor_point.line() as f32 * size.line_height(),
385 ),
386 cursor_width,
387 ))
388 } else {
389 None
390 }
391 }
392
393 ///Convert the Alacritty cell styles to GPUI text styles and background color
394 fn cell_style(
395 indexed: &Indexed<&Cell>,
396 style: &TerminalStyle,
397 text_style: &TextStyle,
398 modal: bool,
399 ) -> RunStyle {
400 let flags = indexed.cell.flags;
401 let fg = convert_color(&indexed.cell.fg, &style.colors, modal);
402
403 let underline = flags
404 .contains(Flags::UNDERLINE)
405 .then(|| Underline {
406 color: Some(fg),
407 squiggly: false,
408 thickness: OrderedFloat(1.),
409 })
410 .unwrap_or_default();
411
412 RunStyle {
413 color: fg,
414 font_id: text_style.font_id,
415 underline,
416 }
417 }
418
419 fn attach_mouse_handlers(
420 &self,
421 origin: Vector2F,
422 view_id: usize,
423 visible_bounds: RectF,
424 cur_size: TermDimensions,
425 cx: &mut PaintContext,
426 ) {
427 let mouse_down_connection = self.terminal.clone();
428 let click_connection = self.terminal.clone();
429 let drag_connection = self.terminal.clone();
430 cx.scene.push_mouse_region(
431 MouseRegion::new(view_id, None, visible_bounds)
432 .on_down(
433 MouseButton::Left,
434 move |MouseButtonEvent { position, .. }, cx| {
435 if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) {
436 conn_handle.update(cx.app, |terminal, cx| {
437 let (point, side) = TerminalEl::mouse_to_cell_data(
438 position,
439 origin,
440 cur_size,
441 terminal.get_display_offset(),
442 );
443
444 terminal.mouse_down(point, side);
445
446 cx.notify();
447 })
448 }
449 },
450 )
451 .on_click(
452 MouseButton::Left,
453 move |MouseButtonEvent {
454 position,
455 click_count,
456 ..
457 },
458 cx| {
459 cx.focus_parent_view();
460 if let Some(conn_handle) = click_connection.upgrade(cx.app) {
461 conn_handle.update(cx.app, |terminal, cx| {
462 let (point, side) = TerminalEl::mouse_to_cell_data(
463 position,
464 origin,
465 cur_size,
466 terminal.get_display_offset(),
467 );
468
469 terminal.click(point, side, click_count);
470
471 cx.notify();
472 });
473 }
474 },
475 )
476 .on_drag(
477 MouseButton::Left,
478 move |_, MouseMovedEvent { position, .. }, cx| {
479 if let Some(conn_handle) = drag_connection.upgrade(cx.app) {
480 conn_handle.update(cx.app, |terminal, cx| {
481 let (point, side) = TerminalEl::mouse_to_cell_data(
482 position,
483 origin,
484 cur_size,
485 terminal.get_display_offset(),
486 );
487
488 terminal.drag(point, side);
489
490 cx.notify()
491 });
492 }
493 },
494 ),
495 );
496 }
497
498 ///Configures a text style from the current settings.
499 pub fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
500 // Pull the font family from settings properly overriding
501 let family_id = settings
502 .terminal_overrides
503 .font_family
504 .as_ref()
505 .or_else(|| settings.terminal_defaults.font_family.as_ref())
506 .and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
507 .unwrap_or(settings.buffer_font_family);
508
509 let font_size = settings
510 .terminal_overrides
511 .font_size
512 .or(settings.terminal_defaults.font_size)
513 .unwrap_or(settings.buffer_font_size);
514
515 let font_id = font_cache
516 .select_font(family_id, &Default::default())
517 .unwrap();
518
519 TextStyle {
520 color: settings.theme.editor.text_color,
521 font_family_id: family_id,
522 font_family_name: font_cache.family_name(family_id).unwrap(),
523 font_id,
524 font_size,
525 font_properties: Default::default(),
526 underline: Default::default(),
527 }
528 }
529
530 pub fn mouse_to_cell_data(
531 pos: Vector2F,
532 origin: Vector2F,
533 cur_size: TermDimensions,
534 display_offset: usize,
535 ) -> (Point, alacritty_terminal::index::Direction) {
536 let pos = pos.sub(origin);
537 let point = {
538 let col = pos.x() / cur_size.cell_width; //TODO: underflow...
539 let col = min(GridCol(col as usize), cur_size.last_column());
540
541 let line = pos.y() / cur_size.line_height;
542 let line = min(line as i32, cur_size.bottommost_line().0);
543
544 Point::new(GridLine(line - display_offset as i32), col)
545 };
546
547 //Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
548 let side = {
549 let x = pos.0.x() as usize;
550 let cell_x =
551 x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize;
552 let half_cell_width = (cur_size.cell_width / 2.0) as usize;
553
554 let additional_padding =
555 (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width;
556 let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding;
557 //Width: Pixels or columns?
558 if cell_x > half_cell_width
559 // Edge case when mouse leaves the window.
560 || x as f32 >= end_of_grid
561 {
562 Side::Right
563 } else {
564 Side::Left
565 }
566 };
567
568 (point, side)
569 }
570}
571
572impl Element for TerminalEl {
573 type LayoutState = LayoutState;
574 type PaintState = ();
575
576 fn layout(
577 &mut self,
578 constraint: gpui::SizeConstraint,
579 cx: &mut gpui::LayoutContext,
580 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
581 let settings = cx.global::<Settings>();
582 let font_cache = &cx.font_cache();
583
584 //Setup layout information
585 let terminal_theme = &settings.theme.terminal;
586 let text_style = TerminalEl::make_text_style(font_cache, &settings);
587 let selection_color = settings.theme.editor.selection.selection;
588 let dimensions = {
589 let line_height = font_cache.line_height(text_style.font_size);
590 let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
591 TermDimensions::new(line_height, cell_width, constraint.max)
592 };
593
594 let terminal = self.terminal.upgrade(cx).unwrap().read(cx);
595
596 let (cursor, cells, rects, highlights) =
597 terminal.render_lock(Some(dimensions.clone()), |content, cursor_text| {
598 let (cells, rects, highlights) = TerminalEl::layout_grid(
599 content.display_iter,
600 &text_style,
601 terminal_theme,
602 cx.text_layout_cache,
603 self.modal,
604 content.selection,
605 );
606
607 //Layout cursor
608 let cursor = {
609 let cursor_point =
610 DisplayCursor::from(content.cursor.point, content.display_offset);
611 let cursor_text = {
612 let str_trxt = cursor_text.to_string();
613 cx.text_layout_cache.layout_str(
614 &str_trxt,
615 text_style.font_size,
616 &[(
617 str_trxt.len(),
618 RunStyle {
619 font_id: text_style.font_id,
620 color: terminal_theme.colors.background,
621 underline: Default::default(),
622 },
623 )],
624 )
625 };
626
627 TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map(
628 move |(cursor_position, block_width)| {
629 Cursor::new(
630 cursor_position,
631 block_width,
632 dimensions.line_height,
633 terminal_theme.colors.cursor,
634 CursorShape::Block,
635 Some(cursor_text.clone()),
636 )
637 },
638 )
639 };
640
641 (cursor, cells, rects, highlights)
642 });
643
644 //Select background color
645 let background_color = if self.modal {
646 terminal_theme.colors.modal_background
647 } else {
648 terminal_theme.colors.background
649 };
650
651 //Done!
652 (
653 constraint.max,
654 LayoutState {
655 cells,
656 cursor,
657 background_color,
658 selection_color,
659 size: dimensions,
660 rects,
661 highlights,
662 },
663 )
664 }
665
666 fn paint(
667 &mut self,
668 bounds: gpui::geometry::rect::RectF,
669 visible_bounds: gpui::geometry::rect::RectF,
670 layout: &mut Self::LayoutState,
671 cx: &mut gpui::PaintContext,
672 ) -> Self::PaintState {
673 //Setup element stuff
674 let clip_bounds = Some(visible_bounds);
675
676 cx.paint_layer(clip_bounds, |cx| {
677 let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
678
679 //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
680 self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.size, cx);
681
682 cx.paint_layer(clip_bounds, |cx| {
683 //Start with a background color
684 cx.scene.push_quad(Quad {
685 bounds: RectF::new(bounds.origin(), bounds.size()),
686 background: Some(layout.background_color),
687 border: Default::default(),
688 corner_radius: 0.,
689 });
690
691 for rect in &layout.rects {
692 rect.paint(origin, &layout, cx)
693 }
694 });
695
696 //Draw Selection
697 cx.paint_layer(clip_bounds, |cx| {
698 let start_y = layout.highlights.get(0).map(|highlight| {
699 origin.y() + highlight.line_index as f32 * layout.size.line_height
700 });
701
702 if let Some(y) = start_y {
703 let range_lines = layout
704 .highlights
705 .iter()
706 .map(|relative_highlight| {
707 relative_highlight.to_highlighted_range_line(origin, layout)
708 })
709 .collect::<Vec<HighlightedRangeLine>>();
710
711 let hr = HighlightedRange {
712 start_y: y, //Need to change this
713 line_height: layout.size.line_height,
714 lines: range_lines,
715 color: layout.selection_color,
716 //Copied from editor. TODO: move to theme or something
717 corner_radius: 0.15 * layout.size.line_height,
718 };
719 hr.paint(bounds, cx.scene);
720 }
721 });
722
723 //Draw the text cells
724 cx.paint_layer(clip_bounds, |cx| {
725 for cell in &layout.cells {
726 cell.paint(origin, layout, visible_bounds, cx);
727 }
728 });
729
730 //Draw cursor
731 if let Some(cursor) = &layout.cursor {
732 cx.paint_layer(clip_bounds, |cx| {
733 cursor.paint(origin, cx);
734 })
735 }
736 });
737 }
738
739 fn dispatch_event(
740 &mut self,
741 event: &gpui::Event,
742 _bounds: gpui::geometry::rect::RectF,
743 visible_bounds: gpui::geometry::rect::RectF,
744 layout: &mut Self::LayoutState,
745 _paint: &mut Self::PaintState,
746 cx: &mut gpui::EventContext,
747 ) -> bool {
748 match event {
749 Event::ScrollWheel(ScrollWheelEvent {
750 delta, position, ..
751 }) => visible_bounds
752 .contains_point(*position)
753 .then(|| {
754 let vertical_scroll =
755 (delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER;
756
757 self.terminal.upgrade(cx.app).map(|terminal| {
758 terminal
759 .read(cx.app)
760 .scroll(Scroll::Delta(vertical_scroll.round() as i32));
761 });
762 })
763 .is_some(),
764 Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
765 if !cx.is_parent_view_focused() {
766 return false;
767 }
768
769 //TODO Talk to keith about how to catch events emitted from an element.
770 if let Some(view) = self.view.upgrade(cx.app) {
771 view.update(cx.app, |view, cx| view.clear_bel(cx))
772 }
773
774 self.terminal
775 .upgrade(cx.app)
776 .map(|model_handle| model_handle.read(cx.app))
777 .map(|term| term.try_keystroke(keystroke))
778 .unwrap_or(false)
779 }
780 _ => false,
781 }
782 }
783
784 fn metadata(&self) -> Option<&dyn std::any::Any> {
785 None
786 }
787
788 fn debug(
789 &self,
790 _bounds: gpui::geometry::rect::RectF,
791 _layout: &Self::LayoutState,
792 _paint: &Self::PaintState,
793 _cx: &gpui::DebugContext,
794 ) -> gpui::serde_json::Value {
795 json!({
796 "type": "TerminalElement",
797 })
798 }
799
800 fn rect_for_text_range(
801 &self,
802 _: Range<usize>,
803 bounds: RectF,
804 _: RectF,
805 layout: &Self::LayoutState,
806 _: &Self::PaintState,
807 _: &gpui::MeasurementContext,
808 ) -> Option<RectF> {
809 // Use the same origin that's passed to `Cursor::paint` in the paint
810 // method bove.
811 let mut origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
812
813 // TODO - Why is it necessary to move downward one line to get correct
814 // positioning? I would think that we'd want the same rect that is
815 // painted for the cursor.
816 origin += vec2f(0., layout.size.line_height);
817
818 Some(layout.cursor.as_ref()?.bounding_rect(origin))
819 }
820}
821
822mod test {
823
824 #[test]
825 fn test_mouse_to_selection() {
826 let term_width = 100.;
827 let term_height = 200.;
828 let cell_width = 10.;
829 let line_height = 20.;
830 let mouse_pos_x = 100.; //Window relative
831 let mouse_pos_y = 100.; //Window relative
832 let origin_x = 10.;
833 let origin_y = 20.;
834
835 let cur_size = crate::connected_el::TermDimensions::new(
836 line_height,
837 cell_width,
838 gpui::geometry::vector::vec2f(term_width, term_height),
839 );
840
841 let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
842 let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
843 let (point, _) =
844 crate::connected_el::TerminalEl::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
845 assert_eq!(
846 point,
847 alacritty_terminal::index::Point::new(
848 alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
849 alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
850 )
851 );
852 }
853}