1use std::ops::Range;
2
3use gpui::color::Color;
4use gpui::geometry::rect::RectF;
5use gpui::geometry::vector::IntoVector2F;
6use gpui::json::{self, ToJson};
7use gpui::{scene::Path, LayoutContext};
8use gpui::{Element, SceneBuilder, View, ViewContext};
9
10type CreatePath = fn(RectF, Color) -> Path;
11type AdjustBorder = fn(RectF, f32) -> RectF;
12type BorderThickness = f32;
13
14pub(crate) struct ButtonSide {
15 color: Color,
16 factory: CreatePath,
17 /// After the outline is drawn with border color,
18 /// the drawing bounds have to be adjusted by different factors in different dimensions.
19 border_adjustment: AdjustBorder,
20 border: Option<(BorderThickness, Color)>,
21}
22
23impl ButtonSide {
24 fn new(color: Color, factory: CreatePath, border_adjustment: AdjustBorder) -> Self {
25 Self {
26 color,
27 factory,
28 border_adjustment,
29 border: None,
30 }
31 }
32 pub fn with_border(mut self, width: f32, color: Color) -> Self {
33 self.border = Some((width, color));
34 self
35 }
36 pub fn left(color: Color) -> Self {
37 Self::new(color, left_button_side, left_button_border_adjust)
38 }
39 pub fn right(color: Color) -> Self {
40 Self::new(color, right_button_side, right_button_border_adjust)
41 }
42}
43
44fn left_button_border_adjust(bounds: RectF, width: f32) -> RectF {
45 let width = width.into_vector_2f();
46 let mut lower_right = bounds.clone().lower_right();
47 lower_right.set_x(lower_right.x() + width.x());
48 RectF::from_points(bounds.origin() + width, lower_right)
49}
50fn right_button_border_adjust(bounds: RectF, width: f32) -> RectF {
51 let width = width.into_vector_2f();
52 let mut origin = bounds.clone().origin();
53 origin.set_x(origin.x() - width.x());
54 RectF::from_points(origin, bounds.lower_right() - width)
55}
56fn left_button_side(bounds: RectF, color: Color) -> Path {
57 use gpui::geometry::PathBuilder;
58 let mut path = PathBuilder::new();
59 path.reset(bounds.lower_right());
60 path.line_to(bounds.upper_right());
61 let mut middle_point = bounds.origin();
62 let distance_to_line = (middle_point.y() - bounds.lower_left().y()) / 4.;
63 middle_point.set_y(middle_point.y() - distance_to_line);
64 path.curve_to(middle_point, bounds.origin());
65 let mut target = bounds.lower_left();
66 target.set_y(target.y() + distance_to_line);
67 path.line_to(target);
68 path.curve_to(bounds.lower_right(), bounds.lower_left());
69 path.build(color, None)
70}
71
72fn right_button_side(bounds: RectF, color: Color) -> Path {
73 use gpui::geometry::PathBuilder;
74 let mut path = PathBuilder::new();
75 path.reset(bounds.lower_left());
76 path.line_to(bounds.origin());
77 let mut middle_point = bounds.upper_right();
78 let distance_to_line = (middle_point.y() - bounds.lower_right().y()) / 4.;
79 middle_point.set_y(middle_point.y() - distance_to_line);
80 path.curve_to(middle_point, bounds.upper_right());
81 let mut target = bounds.lower_right();
82 target.set_y(target.y() + distance_to_line);
83 path.line_to(target);
84 path.curve_to(bounds.lower_left(), bounds.lower_right());
85 path.build(color, None)
86}
87
88impl<V: View> Element<V> for ButtonSide {
89 type LayoutState = ();
90
91 type PaintState = ();
92
93 fn layout(
94 &mut self,
95 constraint: gpui::SizeConstraint,
96 _: &mut V,
97 _: &mut LayoutContext<V>,
98 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
99 (constraint.max, ())
100 }
101
102 fn paint(
103 &mut self,
104 scene: &mut SceneBuilder,
105 bounds: RectF,
106 _: RectF,
107 _: &mut Self::LayoutState,
108 _: &mut V,
109 _: &mut ViewContext<V>,
110 ) -> Self::PaintState {
111 let mut bounds = bounds;
112 if let Some((border_width, border_color)) = self.border.as_ref() {
113 scene.push_path((self.factory)(bounds, border_color.clone()));
114 bounds = (self.border_adjustment)(bounds, *border_width);
115 };
116 scene.push_path((self.factory)(bounds, self.color));
117 }
118
119 fn rect_for_text_range(
120 &self,
121 _: Range<usize>,
122 _: RectF,
123 _: RectF,
124 _: &Self::LayoutState,
125 _: &Self::PaintState,
126 _: &V,
127 _: &ViewContext<V>,
128 ) -> Option<RectF> {
129 None
130 }
131
132 fn debug(
133 &self,
134 bounds: RectF,
135 _: &Self::LayoutState,
136 _: &Self::PaintState,
137 _: &V,
138 _: &ViewContext<V>,
139 ) -> gpui::json::Value {
140 json::json!({
141 "type": "ButtonSide",
142 "bounds": bounds.to_json(),
143 "color": self.color.to_json(),
144 })
145 }
146}