elements.rs

  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}