1use super::{DisplayPoint, Editor, SelectAction, Snapshot};
2use crate::time::ReplicaId;
3use gpui::{
4 color::ColorU,
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::{cmp::Ordering, ops::Range};
18use std::{
19 cmp::{self},
20 collections::HashMap,
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_gutter(&mut self, rect: RectF, layout: &LayoutState, cx: &mut PaintContext) {
186 let settings = self.view(cx.app).settings.borrow();
187 let theme = &settings.theme;
188 let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height;
189
190 cx.scene.push_layer(Some(rect));
191 cx.scene.push_quad(Quad {
192 bounds: rect,
193 background: Some(theme.editor.gutter_background.0),
194 border: Border::new(0., ColorU::transparent_black()),
195 corner_radius: 0.,
196 });
197
198 for (ix, line) in layout.line_number_layouts.iter().enumerate() {
199 if let Some(line) = line {
200 let line_origin = rect.origin()
201 + vec2f(
202 rect.width() - line.width() - layout.gutter_padding,
203 ix as f32 * layout.line_height - (scroll_top % layout.line_height),
204 );
205 line.paint(
206 line_origin,
207 RectF::new(vec2f(0., 0.), vec2f(line.width(), layout.line_height)),
208 cx,
209 );
210 }
211 }
212
213 cx.scene.pop_layer();
214 }
215
216 fn paint_text(&mut self, bounds: RectF, layout: &LayoutState, cx: &mut PaintContext) {
217 let view = self.view(cx.app);
218 let settings = self.view(cx.app).settings.borrow();
219 let theme = &settings.theme.editor;
220 let scroll_position = layout.snapshot.scroll_position();
221 let start_row = scroll_position.y() as u32;
222 let scroll_top = scroll_position.y() * layout.line_height;
223 let end_row = ((scroll_top + bounds.height()) / layout.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
224 let max_glyph_width = layout.em_width;
225 let scroll_left = scroll_position.x() * max_glyph_width;
226
227 cx.scene.push_layer(Some(bounds));
228 cx.scene.push_quad(Quad {
229 bounds,
230 background: Some(theme.background.0),
231 border: Border::new(0., ColorU::transparent_black()),
232 corner_radius: 0.,
233 });
234
235 // Draw selections
236 let corner_radius = 2.5;
237 let mut cursors = SmallVec::<[Cursor; 32]>::new();
238
239 let content_origin = bounds.origin() + layout.text_offset;
240
241 for (replica_id, selections) in &layout.selections {
242 let replica_theme = theme.replicas[*replica_id as usize % theme.replicas.len()];
243
244 for selection in selections {
245 if selection.start != selection.end {
246 let range_start = cmp::min(selection.start, selection.end);
247 let range_end = cmp::max(selection.start, selection.end);
248 let row_range = if range_end.column() == 0 {
249 cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row)
250 } else {
251 cmp::max(range_start.row(), start_row)
252 ..cmp::min(range_end.row() + 1, end_row)
253 };
254
255 let selection = Selection {
256 color: replica_theme.selection.0,
257 line_height: layout.line_height,
258 start_y: content_origin.y() + row_range.start as f32 * layout.line_height
259 - scroll_top,
260 lines: row_range
261 .into_iter()
262 .map(|row| {
263 let line_layout = &layout.line_layouts[(row - start_row) as usize];
264 SelectionLine {
265 start_x: if row == range_start.row() {
266 content_origin.x()
267 + line_layout.x_for_index(range_start.column() as usize)
268 - scroll_left
269 } else {
270 content_origin.x() - scroll_left
271 },
272 end_x: if row == range_end.row() {
273 content_origin.x()
274 + line_layout.x_for_index(range_end.column() as usize)
275 - scroll_left
276 } else {
277 content_origin.x()
278 + line_layout.width()
279 + corner_radius * 2.0
280 - scroll_left
281 },
282 }
283 })
284 .collect(),
285 };
286
287 selection.paint(bounds, cx.scene);
288 }
289
290 if view.cursors_visible() {
291 let cursor_position = selection.end;
292 if (start_row..end_row).contains(&cursor_position.row()) {
293 let cursor_row_layout =
294 &layout.line_layouts[(selection.end.row() - start_row) as usize];
295 let x = cursor_row_layout.x_for_index(selection.end.column() as usize)
296 - scroll_left;
297 let y = selection.end.row() as f32 * layout.line_height - scroll_top;
298 cursors.push(Cursor {
299 color: replica_theme.cursor.0,
300 origin: content_origin + vec2f(x, y),
301 line_height: layout.line_height,
302 });
303 }
304 }
305 }
306 }
307
308 // Draw glyphs
309 for (ix, line) in layout.line_layouts.iter().enumerate() {
310 let row = start_row + ix as u32;
311 line.paint(
312 content_origin + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top),
313 RectF::new(
314 vec2f(scroll_left, 0.),
315 vec2f(bounds.width(), layout.line_height),
316 ),
317 cx,
318 );
319 }
320
321 cx.scene.push_layer(Some(bounds));
322 for cursor in cursors {
323 cursor.paint(cx);
324 }
325 cx.scene.pop_layer();
326
327 cx.scene.pop_layer();
328 }
329}
330
331impl Element for EditorElement {
332 type LayoutState = Option<LayoutState>;
333 type PaintState = Option<PaintState>;
334
335 fn layout(
336 &mut self,
337 constraint: SizeConstraint,
338 cx: &mut LayoutContext,
339 ) -> (Vector2F, Self::LayoutState) {
340 let mut size = constraint.max;
341 if size.x().is_infinite() {
342 unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
343 }
344
345 let font_cache = &cx.font_cache;
346 let layout_cache = &cx.text_layout_cache;
347 let snapshot = self.snapshot(cx.app);
348 let line_height = snapshot.line_height(font_cache);
349
350 let gutter_padding;
351 let gutter_width;
352 if snapshot.gutter_visible {
353 gutter_padding = snapshot.em_width(cx.font_cache);
354 match snapshot.max_line_number_width(cx.font_cache, cx.text_layout_cache) {
355 Err(error) => {
356 log::error!("error computing max line number width: {}", error);
357 return (size, None);
358 }
359 Ok(width) => gutter_width = width + gutter_padding * 2.0,
360 }
361 } else {
362 gutter_padding = 0.0;
363 gutter_width = 0.0
364 };
365
366 let text_width = size.x() - gutter_width;
367 let text_offset = vec2f(-snapshot.font_descent(cx.font_cache), 0.);
368 let em_width = snapshot.em_width(font_cache);
369 let overscroll = vec2f(em_width, 0.);
370 let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
371 let snapshot = self.update_view(cx.app, |view, cx| {
372 if view.set_wrap_width(wrap_width, cx) {
373 view.snapshot(cx)
374 } else {
375 snapshot
376 }
377 });
378 if size.y().is_infinite() {
379 size.set_y((snapshot.max_point().row() + 1) as f32 * line_height);
380 }
381 let gutter_size = vec2f(gutter_width, size.y());
382 let text_size = vec2f(text_width, size.y());
383
384 let (autoscroll_horizontally, mut snapshot) = self.update_view(cx.app, |view, cx| {
385 let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, cx);
386 let snapshot = view.snapshot(cx);
387 (autoscroll_horizontally, snapshot)
388 });
389
390 let line_number_layouts = if snapshot.gutter_visible {
391 let settings = self
392 .view
393 .upgrade(cx.app)
394 .unwrap()
395 .read(cx.app)
396 .settings
397 .borrow();
398 match snapshot.layout_line_numbers(
399 size.y(),
400 cx.font_cache,
401 cx.text_layout_cache,
402 &settings.theme,
403 ) {
404 Err(error) => {
405 log::error!("error laying out line numbers: {}", error);
406 return (size, None);
407 }
408 Ok(layouts) => layouts,
409 }
410 } else {
411 Vec::new()
412 };
413
414 let scroll_position = snapshot.scroll_position();
415 let start_row = scroll_position.y() as u32;
416 let scroll_top = scroll_position.y() * line_height;
417 let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen
418
419 let mut max_visible_line_width = 0.0;
420 let line_layouts = match snapshot.layout_lines(start_row..end_row, font_cache, layout_cache)
421 {
422 Err(error) => {
423 log::error!("error laying out lines: {}", error);
424 return (size, None);
425 }
426 Ok(layouts) => {
427 for line in &layouts {
428 if line.width() > max_visible_line_width {
429 max_visible_line_width = line.width();
430 }
431 }
432
433 layouts
434 }
435 };
436
437 let mut selections = HashMap::new();
438 self.update_view(cx.app, |view, cx| {
439 for selection_set_id in view.active_selection_sets(cx).collect::<Vec<_>>() {
440 selections.insert(
441 selection_set_id.replica_id,
442 view.selections_in_range(
443 selection_set_id,
444 DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0),
445 cx,
446 )
447 .collect(),
448 );
449 }
450 });
451
452 let mut layout = LayoutState {
453 size,
454 gutter_size,
455 gutter_padding,
456 text_size,
457 overscroll,
458 text_offset,
459 snapshot,
460 line_layouts,
461 line_number_layouts,
462 line_height,
463 em_width,
464 selections,
465 max_visible_line_width,
466 };
467
468 self.update_view(cx.app, |view, cx| {
469 let clamped = view.clamp_scroll_left(layout.scroll_max(font_cache, layout_cache).x());
470 let autoscrolled;
471 if autoscroll_horizontally {
472 autoscrolled = view.autoscroll_horizontally(
473 start_row,
474 layout.text_size.x(),
475 layout.scroll_width(font_cache, layout_cache),
476 layout.snapshot.em_width(font_cache),
477 &layout.line_layouts,
478 cx,
479 );
480 } else {
481 autoscrolled = false;
482 }
483
484 if clamped || autoscrolled {
485 layout.snapshot = view.snapshot(cx);
486 }
487 });
488
489 (size, Some(layout))
490 }
491
492 fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
493 }
494
495 fn paint(
496 &mut self,
497 bounds: RectF,
498 layout: &mut Self::LayoutState,
499 cx: &mut PaintContext,
500 ) -> Self::PaintState {
501 if let Some(layout) = layout {
502 let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
503 let text_bounds = RectF::new(
504 bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
505 layout.text_size,
506 );
507
508 if layout.gutter_size.x() > 0. {
509 self.paint_gutter(gutter_bounds, layout, cx);
510 }
511 self.paint_text(text_bounds, layout, cx);
512
513 Some(PaintState {
514 bounds,
515 text_bounds,
516 })
517 } else {
518 None
519 }
520 }
521
522 fn dispatch_event(
523 &mut self,
524 event: &Event,
525 _: RectF,
526 layout: &mut Self::LayoutState,
527 paint: &mut Self::PaintState,
528 cx: &mut EventContext,
529 ) -> bool {
530 if let (Some(layout), Some(paint)) = (layout, paint) {
531 match event {
532 Event::LeftMouseDown { position, cmd } => {
533 self.mouse_down(*position, *cmd, layout, paint, cx)
534 }
535 Event::LeftMouseUp { position } => self.mouse_up(*position, cx),
536 Event::LeftMouseDragged { position } => {
537 self.mouse_dragged(*position, layout, paint, cx)
538 }
539 Event::ScrollWheel {
540 position,
541 delta,
542 precise,
543 } => self.scroll(*position, *delta, *precise, layout, paint, cx),
544 Event::KeyDown { chars, .. } => self.key_down(chars, cx),
545 _ => false,
546 }
547 } else {
548 false
549 }
550 }
551
552 fn debug(
553 &self,
554 bounds: RectF,
555 _: &Self::LayoutState,
556 _: &Self::PaintState,
557 _: &gpui::DebugContext,
558 ) -> json::Value {
559 json!({
560 "type": "BufferElement",
561 "bounds": bounds.to_json()
562 })
563 }
564}
565
566pub struct LayoutState {
567 size: Vector2F,
568 gutter_size: Vector2F,
569 gutter_padding: f32,
570 text_size: Vector2F,
571 snapshot: Snapshot,
572 line_layouts: Vec<text_layout::Line>,
573 line_number_layouts: Vec<Option<text_layout::Line>>,
574 line_height: f32,
575 em_width: f32,
576 selections: HashMap<ReplicaId, Vec<Range<DisplayPoint>>>,
577 overscroll: Vector2F,
578 text_offset: Vector2F,
579 max_visible_line_width: f32,
580}
581
582impl LayoutState {
583 fn scroll_width(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> f32 {
584 let row = self.snapshot.longest_row();
585 let longest_line_width = self
586 .snapshot
587 .layout_line(row, font_cache, layout_cache)
588 .unwrap()
589 .width();
590 longest_line_width.max(self.max_visible_line_width) + self.overscroll.x()
591 }
592
593 fn scroll_max(&self, font_cache: &FontCache, layout_cache: &TextLayoutCache) -> Vector2F {
594 let text_width = self.text_size.x();
595 let scroll_width = self.scroll_width(font_cache, layout_cache);
596 let em_width = self.snapshot.em_width(font_cache);
597 let max_row = self.snapshot.max_point().row();
598
599 vec2f(
600 ((scroll_width - text_width) / em_width).max(0.0),
601 max_row.saturating_sub(1) as f32,
602 )
603 }
604}
605
606pub struct PaintState {
607 bounds: RectF,
608 text_bounds: RectF,
609}
610
611impl PaintState {
612 fn point_for_position(
613 &self,
614 snapshot: &Snapshot,
615 layout: &LayoutState,
616 position: Vector2F,
617 ) -> DisplayPoint {
618 let scroll_position = snapshot.scroll_position();
619 let position = position - self.text_bounds.origin();
620 let y = position.y().max(0.0).min(layout.size.y());
621 let row = ((y / layout.line_height) + scroll_position.y()) as u32;
622 let row = cmp::min(row, snapshot.max_point().row());
623 let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize];
624 let x = position.x() + (scroll_position.x() * layout.em_width);
625
626 let column = if x >= 0.0 {
627 line.index_for_x(x)
628 .map(|ix| ix as u32)
629 .unwrap_or(snapshot.line_len(row))
630 } else {
631 0
632 };
633
634 DisplayPoint::new(row, column)
635 }
636}
637
638struct Cursor {
639 origin: Vector2F,
640 line_height: f32,
641 color: ColorU,
642}
643
644impl Cursor {
645 fn paint(&self, cx: &mut PaintContext) {
646 cx.scene.push_quad(Quad {
647 bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
648 background: Some(self.color),
649 border: Border::new(0., ColorU::black()),
650 corner_radius: 0.,
651 });
652 }
653}
654
655#[derive(Debug)]
656struct Selection {
657 start_y: f32,
658 line_height: f32,
659 lines: Vec<SelectionLine>,
660 color: ColorU,
661}
662
663#[derive(Debug)]
664struct SelectionLine {
665 start_x: f32,
666 end_x: f32,
667}
668
669impl Selection {
670 fn paint(&self, bounds: RectF, scene: &mut Scene) {
671 if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
672 self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
673 self.paint_lines(
674 self.start_y + self.line_height,
675 &self.lines[1..],
676 bounds,
677 scene,
678 );
679 } else {
680 self.paint_lines(self.start_y, &self.lines, bounds, scene);
681 }
682 }
683
684 fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], bounds: RectF, scene: &mut Scene) {
685 if lines.is_empty() {
686 return;
687 }
688
689 let mut path = PathBuilder::new();
690 let corner_radius = 0.15 * self.line_height;
691 let first_line = lines.first().unwrap();
692 let last_line = lines.last().unwrap();
693
694 let first_top_left = vec2f(first_line.start_x, start_y);
695 let first_top_right = vec2f(first_line.end_x, start_y);
696
697 let curve_height = vec2f(0., corner_radius);
698 let curve_width = |start_x: f32, end_x: f32| {
699 let max = (end_x - start_x) / 2.;
700 let width = if max < corner_radius {
701 max
702 } else {
703 corner_radius
704 };
705
706 vec2f(width, 0.)
707 };
708
709 let top_curve_width = curve_width(first_line.start_x, first_line.end_x);
710 path.reset(first_top_right - top_curve_width);
711 path.curve_to(first_top_right + curve_height, first_top_right);
712
713 let mut iter = lines.iter().enumerate().peekable();
714 while let Some((ix, line)) = iter.next() {
715 let bottom_right = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height);
716
717 if let Some((_, next_line)) = iter.peek() {
718 let next_top_right = vec2f(next_line.end_x, bottom_right.y());
719
720 match next_top_right.x().partial_cmp(&bottom_right.x()).unwrap() {
721 Ordering::Equal => {
722 path.line_to(bottom_right);
723 }
724 Ordering::Less => {
725 let curve_width = curve_width(next_top_right.x(), bottom_right.x());
726 path.line_to(bottom_right - curve_height);
727 path.curve_to(bottom_right - curve_width, bottom_right);
728 path.line_to(next_top_right + curve_width);
729 path.curve_to(next_top_right + curve_height, next_top_right);
730 }
731 Ordering::Greater => {
732 let curve_width = curve_width(bottom_right.x(), next_top_right.x());
733 path.line_to(bottom_right - curve_height);
734 path.curve_to(bottom_right + curve_width, bottom_right);
735 path.line_to(next_top_right - curve_width);
736 path.curve_to(next_top_right + curve_height, next_top_right);
737 }
738 }
739 } else {
740 let curve_width = curve_width(line.start_x, line.end_x);
741 path.line_to(bottom_right - curve_height);
742 path.curve_to(bottom_right - curve_width, bottom_right);
743
744 let bottom_left = vec2f(line.start_x, bottom_right.y());
745 path.line_to(bottom_left + curve_width);
746 path.curve_to(bottom_left - curve_height, bottom_left);
747 }
748 }
749
750 if first_line.start_x > last_line.start_x {
751 let curve_width = curve_width(last_line.start_x, first_line.start_x);
752 let second_top_left = vec2f(last_line.start_x, start_y + self.line_height);
753 path.line_to(second_top_left + curve_height);
754 path.curve_to(second_top_left + curve_width, second_top_left);
755 let first_bottom_left = vec2f(first_line.start_x, second_top_left.y());
756 path.line_to(first_bottom_left - curve_width);
757 path.curve_to(first_bottom_left - curve_height, first_bottom_left);
758 }
759
760 path.line_to(first_top_left + curve_height);
761 path.curve_to(first_top_left + top_curve_width, first_top_left);
762 path.line_to(first_top_right - top_curve_width);
763
764 scene.push_path(path.build(self.color, Some(bounds)));
765 }
766}
767
768fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 {
769 delta.powf(1.5) / 100.0
770}
771
772fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 {
773 delta.powf(1.2) / 300.0
774}