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