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