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