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}