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, PaintContext, SceneBuilder, View, ViewContext};
9
10type CreatePath = fn(RectF, Color, f32) -> 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 radius: f32,
22}
23
24impl ButtonSide {
25 fn new(
26 color: Color,
27 factory: CreatePath,
28 border_adjustment: AdjustBorder,
29 radius: f32,
30 ) -> Self {
31 Self {
32 color,
33 factory,
34 border_adjustment,
35 border: None,
36 radius,
37 }
38 }
39 pub fn with_border(mut self, width: f32, color: Color) -> Self {
40 self.border = Some((width, color));
41 self
42 }
43 pub fn left(color: Color, corner_radius: f32) -> Self {
44 Self::new(
45 color,
46 left_button_side,
47 left_button_border_adjust,
48 corner_radius,
49 )
50 }
51 pub fn right(color: Color, corner_radius: f32) -> Self {
52 Self::new(
53 color,
54 right_button_side,
55 right_button_border_adjust,
56 corner_radius,
57 )
58 }
59}
60
61fn left_button_border_adjust(bounds: RectF, width: f32) -> RectF {
62 let width = width.into_vector_2f();
63 let mut lower_right = bounds.clone().lower_right();
64 lower_right.set_x(lower_right.x() + width.x());
65 RectF::from_points(bounds.origin() + width, lower_right)
66}
67fn right_button_border_adjust(bounds: RectF, width: f32) -> RectF {
68 let width = width.into_vector_2f();
69 let mut origin = bounds.clone().origin();
70 origin.set_x(origin.x() - width.x());
71 RectF::from_points(origin, bounds.lower_right() - width)
72}
73fn left_button_side(bounds: RectF, color: Color, radius: f32) -> Path {
74 use gpui::geometry::PathBuilder;
75 let mut path = PathBuilder::new();
76 path.reset(bounds.lower_right());
77 path.line_to(bounds.upper_right());
78 let mut middle_point = bounds.origin();
79 let distance_to_line = (middle_point.y() - bounds.lower_left().y()).min(-radius.abs());
80 middle_point.set_y(middle_point.y() - distance_to_line);
81 path.curve_to(middle_point, bounds.origin());
82 let mut target = bounds.lower_left();
83 target.set_y(target.y() + distance_to_line);
84 path.line_to(target);
85 path.curve_to(bounds.lower_right(), bounds.lower_left());
86 path.build(color, None)
87}
88
89fn right_button_side(bounds: RectF, color: Color, radius: f32) -> Path {
90 use gpui::geometry::PathBuilder;
91 let mut path = PathBuilder::new();
92 path.reset(bounds.lower_left());
93 path.line_to(bounds.origin());
94 let mut middle_point = bounds.upper_right();
95 let distance_to_line = (middle_point.y() - bounds.lower_right().y()).min(-radius.abs());
96 middle_point.set_y(middle_point.y() - distance_to_line);
97 path.curve_to(middle_point, bounds.upper_right());
98 let mut target = bounds.lower_right();
99 target.set_y(target.y() + distance_to_line);
100 path.line_to(target);
101 path.curve_to(bounds.lower_left(), bounds.lower_right());
102 path.build(color, None)
103}
104
105impl<V: View> Element<V> for ButtonSide {
106 type LayoutState = ();
107
108 type PaintState = ();
109
110 fn layout(
111 &mut self,
112 constraint: gpui::SizeConstraint,
113 _: &mut V,
114 _: &mut LayoutContext<V>,
115 ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
116 (constraint.max, ())
117 }
118
119 fn paint(
120 &mut self,
121 scene: &mut SceneBuilder,
122 bounds: RectF,
123 _: RectF,
124 _: &mut Self::LayoutState,
125 _: &mut V,
126 _: &mut PaintContext<V>,
127 ) -> Self::PaintState {
128 let mut bounds = bounds;
129 if let Some((border_width, border_color)) = self.border.as_ref() {
130 scene.push_path((self.factory)(bounds, border_color.clone(), self.radius));
131 bounds = (self.border_adjustment)(bounds, *border_width);
132 };
133 scene.push_path((self.factory)(bounds, self.color, self.radius));
134 }
135
136 fn rect_for_text_range(
137 &self,
138 _: Range<usize>,
139 _: RectF,
140 _: RectF,
141 _: &Self::LayoutState,
142 _: &Self::PaintState,
143 _: &V,
144 _: &ViewContext<V>,
145 ) -> Option<RectF> {
146 None
147 }
148
149 fn debug(
150 &self,
151 bounds: RectF,
152 _: &Self::LayoutState,
153 _: &Self::PaintState,
154 _: &V,
155 _: &ViewContext<V>,
156 ) -> gpui::json::Value {
157 json::json!({
158 "type": "ButtonSide",
159 "bounds": bounds.to_json(),
160 "color": self.color.to_json(),
161 })
162 }
163}