buffer_element.rs

  1use super::{BufferView, DisplayPoint, SelectAction};
  2use gpui::{
  3    color::{ColorF, ColorU},
  4    geometry::{
  5        rect::RectF,
  6        vector::{vec2f, Vector2F},
  7        PathBuilder,
  8    },
  9    text_layout::{self, TextLayoutCache},
 10    AfterLayoutContext, AppContext, Border, Element, Event, EventContext, FontCache, LayoutContext,
 11    PaintContext, Quad, Scene, SizeConstraint, ViewHandle,
 12};
 13use smallvec::SmallVec;
 14use std::cmp::Ordering;
 15use std::{
 16    cmp::{self},
 17    sync::Arc,
 18};
 19
 20pub struct BufferElement {
 21    view: ViewHandle<BufferView>,
 22}
 23
 24impl BufferElement {
 25    pub fn new(view: ViewHandle<BufferView>) -> Self {
 26        Self { view }
 27    }
 28
 29    fn mouse_down(
 30        &self,
 31        position: Vector2F,
 32        cmd: bool,
 33        layout: &mut LayoutState,
 34        paint: &mut PaintState,
 35        ctx: &mut EventContext,
 36    ) -> bool {
 37        if paint.text_bounds.contains_point(position) {
 38            let view = self.view.as_ref(ctx.app);
 39            let position =
 40                paint.point_for_position(view, layout, position, ctx.font_cache, ctx.app);
 41            ctx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd });
 42            true
 43        } else {
 44            false
 45        }
 46    }
 47
 48    fn mouse_up(&self, _position: Vector2F, ctx: &mut EventContext) -> bool {
 49        if self.view.as_ref(ctx.app).is_selecting() {
 50            ctx.dispatch_action("buffer:select", SelectAction::End);
 51            true
 52        } else {
 53            false
 54        }
 55    }
 56
 57    fn mouse_dragged(
 58        &self,
 59        position: Vector2F,
 60        layout: &mut LayoutState,
 61        paint: &mut PaintState,
 62        ctx: &mut EventContext,
 63    ) -> bool {
 64        let view = self.view.as_ref(ctx.app);
 65
 66        if view.is_selecting() {
 67            let rect = paint.text_bounds;
 68            let mut scroll_delta = Vector2F::zero();
 69
 70            let vertical_margin = view.line_height(ctx.font_cache).min(rect.height() / 3.0);
 71            let top = rect.origin_y() + vertical_margin;
 72            let bottom = rect.lower_left().y() - vertical_margin;
 73            if position.y() < top {
 74                scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y()))
 75            }
 76            if position.y() > bottom {
 77                scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom))
 78            }
 79
 80            let horizontal_margin = view.line_height(ctx.font_cache).min(rect.width() / 3.0);
 81            let left = rect.origin_x() + horizontal_margin;
 82            let right = rect.upper_right().x() - horizontal_margin;
 83            if position.x() < left {
 84                scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta(
 85                    left - position.x(),
 86                ))
 87            }
 88            if position.x() > right {
 89                scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta(
 90                    position.x() - right,
 91                ))
 92            }
 93
 94            ctx.dispatch_action(
 95                "buffer:select",
 96                SelectAction::Update {
 97                    position: paint.point_for_position(
 98                        view,
 99                        layout,
100                        position,
101                        ctx.font_cache,
102                        ctx.app,
103                    ),
104                    scroll_position: (view.scroll_position() + scroll_delta).clamp(
105                        Vector2F::zero(),
106                        layout.scroll_max(view, ctx.font_cache, ctx.text_layout_cache, ctx.app),
107                    ),
108                },
109            );
110            true
111        } else {
112            false
113        }
114    }
115
116    fn key_down(&self, chars: &str, ctx: &mut EventContext) -> bool {
117        if self.view.is_focused(ctx.app) {
118            if chars.is_empty() {
119                false
120            } else {
121                if chars.chars().any(|c| c.is_control()) {
122                    false
123                } else {
124                    ctx.dispatch_action("buffer:insert", chars.to_string());
125                    true
126                }
127            }
128        } else {
129            false
130        }
131    }
132
133    fn scroll(
134        &self,
135        position: Vector2F,
136        mut delta: Vector2F,
137        precise: bool,
138        layout: &mut LayoutState,
139        paint: &mut PaintState,
140        ctx: &mut EventContext,
141    ) -> bool {
142        if !paint.bounds.contains_point(position) {
143            return false;
144        }
145
146        let view = self.view.as_ref(ctx.app);
147        let font_cache = &ctx.font_cache;
148        let layout_cache = &ctx.text_layout_cache;
149        let max_glyph_width = view.em_width(font_cache);
150        let line_height = view.line_height(font_cache);
151        if !precise {
152            delta *= vec2f(max_glyph_width, line_height);
153        }
154
155        let x = (view.scroll_position().x() * max_glyph_width - delta.x()) / max_glyph_width;
156        let y = (view.scroll_position().y() * line_height - delta.y()) / line_height;
157        let scroll_position = vec2f(x, y).clamp(
158            Vector2F::zero(),
159            layout.scroll_max(view, font_cache, layout_cache, ctx.app),
160        );
161
162        ctx.dispatch_action("buffer:scroll", scroll_position);
163
164        true
165    }
166
167    fn paint_gutter(&mut self, rect: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
168        let view = self.view.as_ref(ctx.app);
169        let line_height = view.line_height(ctx.font_cache);
170        let scroll_top = view.scroll_position().y() * line_height;
171
172        ctx.scene.push_layer(Some(rect));
173        ctx.scene.push_quad(Quad {
174            bounds: rect,
175            background: Some(ColorU::white()),
176            border: Border::new(0., ColorU::transparent_black()),
177            corner_radius: 0.,
178        });
179
180        for (ix, line) in layout.line_number_layouts.iter().enumerate() {
181            let line_origin = rect.origin()
182                + vec2f(
183                    rect.width() - line.width - layout.gutter_padding,
184                    ix as f32 * line_height - (scroll_top % line_height),
185                );
186            line.paint(
187                line_origin,
188                RectF::new(vec2f(0., 0.), vec2f(line.width, line_height)),
189                &[(0..line.len, ColorU::black())],
190                ctx,
191            );
192        }
193
194        ctx.scene.pop_layer();
195    }
196
197    fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, ctx: &mut PaintContext) {
198        let view = self.view.as_ref(ctx.app);
199        let line_height = view.line_height(ctx.font_cache);
200        let descent = view.font_descent(ctx.font_cache);
201        let start_row = view.scroll_position().y() as u32;
202        let scroll_top = view.scroll_position().y() * line_height;
203        let end_row = ((scroll_top + bounds.height()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
204        let max_glyph_width = view.em_width(ctx.font_cache);
205        let scroll_left = view.scroll_position().x() * max_glyph_width;
206
207        ctx.scene.push_layer(Some(bounds));
208        ctx.scene.push_quad(Quad {
209            bounds,
210            background: Some(ColorU::white()),
211            border: Border::new(0., ColorU::transparent_black()),
212            corner_radius: 0.,
213        });
214
215        // Draw selections
216        let corner_radius = 2.5;
217        let mut cursors = SmallVec::<[Cursor; 32]>::new();
218
219        let content_origin = bounds.origin() + vec2f(-descent, 0.0);
220
221        for selection in view.selections_in_range(
222            DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
223            ctx.app,
224        ) {
225            if selection.start != selection.end {
226                let range_start = cmp::min(selection.start, selection.end);
227                let range_end = cmp::max(selection.start, selection.end);
228                let row_range = if range_end.column() == 0 {
229                    cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row)
230                } else {
231                    cmp::max(range_start.row(), start_row)..cmp::min(range_end.row() + 1, end_row)
232                };
233
234                let selection = Selection {
235                    line_height,
236                    start_y: content_origin.y() + row_range.start as f32 * line_height - scroll_top,
237                    lines: row_range
238                        .into_iter()
239                        .map(|row| {
240                            let line_layout = &layout.line_layouts[(row - start_row) as usize];
241                            SelectionLine {
242                                start_x: if row == range_start.row() {
243                                    content_origin.x()
244                                        + line_layout.x_for_index(range_start.column() as usize)
245                                        - scroll_left
246                                } else {
247                                    content_origin.x() - scroll_left
248                                },
249                                end_x: if row == range_end.row() {
250                                    content_origin.x()
251                                        + line_layout.x_for_index(range_end.column() as usize)
252                                        - scroll_left
253                                } else {
254                                    content_origin.x() + line_layout.width + corner_radius * 2.0
255                                        - scroll_left
256                                },
257                            }
258                        })
259                        .collect(),
260                };
261
262                selection.paint(bounds, ctx.scene);
263            }
264
265            if view.cursors_visible() {
266                let cursor_position = selection.end;
267                if (start_row..end_row).contains(&cursor_position.row()) {
268                    let cursor_row_layout =
269                        &layout.line_layouts[(selection.end.row() - start_row) as usize];
270                    let x = cursor_row_layout.x_for_index(selection.end.column() as usize)
271                        - scroll_left;
272                    let y = selection.end.row() as f32 * line_height - scroll_top;
273                    cursors.push(Cursor {
274                        origin: content_origin + vec2f(x, y),
275                        line_height,
276                    });
277                }
278            }
279        }
280
281        // Draw glyphs
282        for (ix, line) in layout.line_layouts.iter().enumerate() {
283            let row = start_row + ix as u32;
284            line.paint(
285                content_origin + vec2f(-scroll_left, row as f32 * line_height - scroll_top),
286                RectF::new(vec2f(scroll_left, 0.), vec2f(bounds.width(), line_height)),
287                &[(0..line.len, ColorU::black())],
288                ctx,
289            );
290        }
291
292        ctx.scene.push_layer(Some(bounds));
293        for cursor in cursors {
294            cursor.paint(ctx);
295        }
296        ctx.scene.pop_layer();
297
298        ctx.scene.pop_layer();
299    }
300}
301
302impl Element for BufferElement {
303    type LayoutState = Option<LayoutState>;
304    type PaintState = Option<PaintState>;
305
306    fn layout(
307        &mut self,
308        constraint: SizeConstraint,
309        ctx: &mut LayoutContext,
310    ) -> (Vector2F, Self::LayoutState) {
311        let app = ctx.app;
312        let mut size = constraint.max;
313        if size.y().is_infinite() {
314            let view = self.view.as_ref(app);
315            size.set_y((view.max_point(app).row() + 1) as f32 * view.line_height(ctx.font_cache));
316        }
317        if size.x().is_infinite() {
318            unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
319        }
320
321        let view = self.view.as_ref(app);
322        let font_cache = &ctx.font_cache;
323        let layout_cache = &ctx.text_layout_cache;
324        let line_height = view.line_height(font_cache);
325
326        let gutter_padding;
327        let gutter_width;
328        if view.is_gutter_visible() {
329            gutter_padding = view.em_width(ctx.font_cache);
330            match view.max_line_number_width(ctx.font_cache, ctx.text_layout_cache, app) {
331                Err(error) => {
332                    log::error!("error computing max line number width: {}", error);
333                    return (size, None);
334                }
335                Ok(width) => gutter_width = width + gutter_padding * 2.0,
336            }
337        } else {
338            gutter_padding = 0.0;
339            gutter_width = 0.0
340        };
341
342        let gutter_size = vec2f(gutter_width, size.y());
343        let text_size = size - vec2f(gutter_width, 0.0);
344
345        let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, app);
346
347        let line_number_layouts = if view.is_gutter_visible() {
348            match view.layout_line_numbers(size.y(), ctx.font_cache, ctx.text_layout_cache, app) {
349                Err(error) => {
350                    log::error!("error laying out line numbers: {}", error);
351                    return (size, None);
352                }
353                Ok(layouts) => layouts,
354            }
355        } else {
356            Vec::new()
357        };
358
359        let start_row = view.scroll_position().y() as u32;
360        let scroll_top = view.scroll_position().y() * line_height;
361        let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
362
363        let mut max_visible_line_width = 0.0;
364        let line_layouts =
365            match view.layout_lines(start_row..end_row, font_cache, layout_cache, app) {
366                Err(error) => {
367                    log::error!("error laying out lines: {}", error);
368                    return (size, None);
369                }
370                Ok(layouts) => {
371                    for line in &layouts {
372                        if line.width > max_visible_line_width {
373                            max_visible_line_width = line.width;
374                        }
375                    }
376
377                    layouts
378                }
379            };
380
381        (
382            size,
383            Some(LayoutState {
384                size,
385                gutter_size,
386                gutter_padding,
387                text_size,
388                line_layouts,
389                line_number_layouts,
390                max_visible_line_width,
391                autoscroll_horizontally,
392            }),
393        )
394    }
395
396    fn after_layout(
397        &mut self,
398        _: Vector2F,
399        layout: &mut Option<LayoutState>,
400        ctx: &mut AfterLayoutContext,
401    ) {
402        if let Some(layout) = layout {
403            let app = ctx.app.downgrade();
404
405            let view = self.view.as_ref(app);
406            view.clamp_scroll_left(
407                layout
408                    .scroll_max(view, ctx.font_cache, ctx.text_layout_cache, app)
409                    .x(),
410            );
411
412            if layout.autoscroll_horizontally {
413                view.autoscroll_horizontally(
414                    view.scroll_position().y() as u32,
415                    layout.text_size.x(),
416                    layout.scroll_width(view, ctx.font_cache, ctx.text_layout_cache, app),
417                    view.em_width(ctx.font_cache),
418                    &layout.line_layouts,
419                    app,
420                );
421            }
422        }
423    }
424
425    fn paint(
426        &mut self,
427        bounds: RectF,
428        layout: &mut Self::LayoutState,
429        ctx: &mut PaintContext,
430    ) -> Self::PaintState {
431        if let Some(layout) = layout {
432            let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
433            let text_bounds = RectF::new(
434                bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
435                layout.text_size,
436            );
437
438            if self.view.as_ref(ctx.app).is_gutter_visible() {
439                self.paint_gutter(gutter_bounds, layout, ctx);
440            }
441            self.paint_text(text_bounds, layout, ctx);
442
443            Some(PaintState {
444                bounds,
445                text_bounds,
446            })
447        } else {
448            None
449        }
450    }
451
452    fn dispatch_event(
453        &mut self,
454        event: &Event,
455        _: RectF,
456        layout: &mut Self::LayoutState,
457        paint: &mut Self::PaintState,
458        ctx: &mut EventContext,
459    ) -> bool {
460        if let (Some(layout), Some(paint)) = (layout, paint) {
461            match event {
462                Event::LeftMouseDown { position, cmd } => {
463                    self.mouse_down(*position, *cmd, layout, paint, ctx)
464                }
465                Event::LeftMouseUp { position } => self.mouse_up(*position, ctx),
466                Event::LeftMouseDragged { position } => {
467                    self.mouse_dragged(*position, layout, paint, ctx)
468                }
469                Event::ScrollWheel {
470                    position,
471                    delta,
472                    precise,
473                } => self.scroll(*position, *delta, *precise, layout, paint, ctx),
474                Event::KeyDown { chars, .. } => self.key_down(chars, ctx),
475            }
476        } else {
477            false
478        }
479    }
480}
481
482pub struct LayoutState {
483    size: Vector2F,
484    gutter_size: Vector2F,
485    gutter_padding: f32,
486    text_size: Vector2F,
487    line_layouts: Vec<Arc<text_layout::Line>>,
488    line_number_layouts: Vec<Arc<text_layout::Line>>,
489    max_visible_line_width: f32,
490    autoscroll_horizontally: bool,
491}
492
493impl LayoutState {
494    fn scroll_width(
495        &self,
496        view: &BufferView,
497        font_cache: &FontCache,
498        layout_cache: &TextLayoutCache,
499        app: &AppContext,
500    ) -> f32 {
501        let row = view.rightmost_point(app).row();
502        let longest_line_width = view
503            .layout_line(row, font_cache, layout_cache, app)
504            .unwrap()
505            .width;
506        longest_line_width.max(self.max_visible_line_width) + view.em_width(font_cache)
507    }
508
509    fn scroll_max(
510        &self,
511        view: &BufferView,
512        font_cache: &FontCache,
513        layout_cache: &TextLayoutCache,
514        app: &AppContext,
515    ) -> Vector2F {
516        vec2f(
517            ((self.scroll_width(view, font_cache, layout_cache, app) - self.text_size.x())
518                / view.em_width(font_cache))
519            .max(0.0),
520            view.max_point(app).row().saturating_sub(1) as f32,
521        )
522    }
523}
524
525pub struct PaintState {
526    bounds: RectF,
527    text_bounds: RectF,
528}
529
530impl PaintState {
531    fn point_for_position(
532        &self,
533        view: &BufferView,
534        layout: &LayoutState,
535        position: Vector2F,
536        font_cache: &FontCache,
537        app: &AppContext,
538    ) -> DisplayPoint {
539        let scroll_position = view.scroll_position();
540        let position = position - self.text_bounds.origin();
541        let y = position.y().max(0.0).min(layout.size.y());
542        let row = ((y / view.line_height(font_cache)) + scroll_position.y()) as u32;
543        let row = cmp::min(row, view.max_point(app).row());
544        let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
545        let x = position.x() + (scroll_position.x() * view.em_width(font_cache));
546
547        let column = if x >= 0.0 {
548            line.index_for_x(x)
549                .map(|ix| ix as u32)
550                .unwrap_or(view.line_len(row, app).unwrap())
551        } else {
552            0
553        };
554
555        DisplayPoint::new(row, column)
556    }
557}
558
559struct Cursor {
560    origin: Vector2F,
561    line_height: f32,
562}
563
564impl Cursor {
565    fn paint(&self, ctx: &mut PaintContext) {
566        ctx.scene.push_quad(Quad {
567            bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
568            background: Some(ColorU::black()),
569            border: Border::new(0., ColorU::black()),
570            corner_radius: 0.,
571        });
572    }
573}
574
575#[derive(Debug)]
576struct Selection {
577    start_y: f32,
578    line_height: f32,
579    lines: Vec<SelectionLine>,
580}
581
582#[derive(Debug)]
583struct SelectionLine {
584    start_x: f32,
585    end_x: f32,
586}
587
588impl Selection {
589    fn paint(&self, bounds: RectF, scene: &mut Scene) {
590        if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
591            self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
592            self.paint_lines(
593                self.start_y + self.line_height,
594                &self.lines[1..],
595                bounds,
596                scene,
597            );
598        } else {
599            self.paint_lines(self.start_y, &self.lines, bounds, scene);
600        }
601    }
602
603    fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], bounds: RectF, scene: &mut Scene) {
604        if lines.is_empty() {
605            return;
606        }
607
608        let mut path = PathBuilder::new();
609        let corner_radius = 0.15 * self.line_height;
610        let first_line = lines.first().unwrap();
611        let last_line = lines.last().unwrap();
612
613        let first_top_left = vec2f(first_line.start_x, start_y);
614        let first_top_right = vec2f(first_line.end_x, start_y);
615
616        let curve_height = vec2f(0., corner_radius);
617        let curve_width = |start_x: f32, end_x: f32| {
618            let max = (end_x - start_x) / 2.;
619            let width = if max < corner_radius {
620                max
621            } else {
622                corner_radius
623            };
624
625            vec2f(width, 0.)
626        };
627
628        let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
629        path.reset(first_top_right - top_curve_width);
630        path.curve_to(first_top_right + curve_height, first_top_right);
631
632        let mut iter = lines.iter().enumerate().peekable();
633        while let Some((ix, line)) = iter.next() {
634            let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
635
636            if let Some((_, next_line)) = iter.peek() {
637                let next_top_right = vec2f(next_line.end_x, bottom_right.y());
638
639                match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
640                    Ordering::Equal => {
641                        path.line_to(bottom_right);
642                    }
643                    Ordering::Less => {
644                        let curve_width = curve_width(next_top_right.x(), bottom_right.x());
645                        path.line_to(bottom_right - curve_height);
646                        path.curve_to(bottom_right - curve_width, bottom_right);
647                        path.line_to(next_top_right + curve_width);
648                        path.curve_to(next_top_right + curve_height, next_top_right);
649                    }
650                    Ordering::Greater => {
651                        let curve_width = curve_width(bottom_right.x(), next_top_right.x());
652                        path.line_to(bottom_right - curve_height);
653                        path.curve_to(bottom_right + curve_width, bottom_right);
654                        path.line_to(next_top_right - curve_width);
655                        path.curve_to(next_top_right + curve_height, next_top_right);
656                    }
657                }
658            } else {
659                let curve_width = curve_width(line.start_x, line.end_x);
660                path.line_to(bottom_right - curve_height);
661                path.curve_to(bottom_right - curve_width, bottom_right);
662
663                let bottom_left = vec2f(line.start_x, bottom_right.y());
664                path.line_to(bottom_left + curve_width);
665                path.curve_to(bottom_left - curve_height, bottom_left);
666            }
667        }
668
669        if first_line.start_x > last_line.start_x {
670            let curve_width = curve_width(last_line.start_x, first_line.start_x);
671            let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
672            path.line_to(second_top_left + curve_height);
673            path.curve_to(second_top_left + curve_width, second_top_left);
674            let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
675            path.line_to(first_bottom_left - curve_width);
676            path.curve_to(first_bottom_left - curve_height, first_bottom_left);
677        }
678
679        path.line_to(first_top_left + curve_height);
680        path.curve_to(first_top_left + top_curve_width, first_top_left);
681        path.line_to(first_top_right - top_curve_width);
682
683        scene.push_path(path.build(ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8(), Some(bounds)));
684    }
685}
686
687fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
688    delta.powf(1.5) / 100.0
689}
690
691fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
692    delta.powf(1.2) / 300.0
693}