style.rs

  1use crate::{
  2    color::Hsla,
  3    element::{Element, Layout},
  4    paint_context::PaintContext,
  5};
  6use gpui::{
  7    fonts::TextStyleRefinement,
  8    geometry::{
  9        AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point, PointRefinement,
 10        Size, SizeRefinement,
 11    },
 12};
 13use playground_macros::styleable_helpers;
 14use refineable::Refineable;
 15pub use taffy::style::{
 16    AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
 17    Overflow, Position,
 18};
 19
 20#[derive(Clone, Refineable)]
 21pub struct Style {
 22    /// What layout strategy should be used?
 23    pub display: Display,
 24
 25    // Overflow properties
 26    /// How children overflowing their container should affect layout
 27    #[refineable]
 28    pub overflow: Point<Overflow>,
 29    /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
 30    pub scrollbar_width: f32,
 31
 32    // Position properties
 33    /// What should the `position` value of this struct use as a base offset?
 34    pub position: Position,
 35    /// How should the position of this element be tweaked relative to the layout defined?
 36    #[refineable]
 37    pub inset: Edges<Length>,
 38
 39    // Size properies
 40    /// Sets the initial size of the item
 41    #[refineable]
 42    pub size: Size<Length>,
 43    /// Controls the minimum size of the item
 44    #[refineable]
 45    pub min_size: Size<Length>,
 46    /// Controls the maximum size of the item
 47    #[refineable]
 48    pub max_size: Size<Length>,
 49    /// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
 50    pub aspect_ratio: Option<f32>,
 51
 52    // Spacing Properties
 53    /// How large should the margin be on each side?
 54    #[refineable]
 55    pub margin: Edges<Length>,
 56    /// How large should the padding be on each side?
 57    #[refineable]
 58    pub padding: Edges<DefiniteLength>,
 59    /// How large should the border be on each side?
 60    #[refineable]
 61    pub border: Edges<DefiniteLength>,
 62
 63    // Alignment properties
 64    /// How this node's children aligned in the cross/block axis?
 65    pub align_items: Option<AlignItems>,
 66    /// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
 67    pub align_self: Option<AlignSelf>,
 68    /// How should content contained within this item be aligned in the cross/block axis
 69    pub align_content: Option<AlignContent>,
 70    /// How should contained within this item be aligned in the main/inline axis
 71    pub justify_content: Option<JustifyContent>,
 72    /// How large should the gaps between items in a flex container be?
 73    #[refineable]
 74    pub gap: Size<DefiniteLength>,
 75
 76    // Flexbox properies
 77    /// Which direction does the main axis flow in?
 78    pub flex_direction: FlexDirection,
 79    /// Should elements wrap, or stay in a single line?
 80    pub flex_wrap: FlexWrap,
 81    /// Sets the initial main axis size of the item
 82    pub flex_basis: Length,
 83    /// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
 84    pub flex_grow: f32,
 85    /// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
 86    pub flex_shrink: f32,
 87
 88    /// The fill color of this element
 89    pub fill: Option<Fill>,
 90    /// The radius of the corners of this element
 91    #[refineable]
 92    pub corner_radii: CornerRadii,
 93    /// The color of text within this element. Cascades to children unless overridden.
 94    pub text_color: Option<Hsla>,
 95}
 96
 97impl Style {
 98    pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
 99        taffy::style::Style {
100            display: self.display,
101            overflow: self.overflow.clone().into(),
102            scrollbar_width: self.scrollbar_width,
103            position: self.position,
104            inset: self.inset.to_taffy(rem_size),
105            size: self.size.to_taffy(rem_size),
106            min_size: self.min_size.to_taffy(rem_size),
107            max_size: self.max_size.to_taffy(rem_size),
108            aspect_ratio: self.aspect_ratio,
109            margin: self.margin.to_taffy(rem_size),
110            padding: self.padding.to_taffy(rem_size),
111            border: self.border.to_taffy(rem_size),
112            align_items: self.align_items,
113            align_self: self.align_self,
114            align_content: self.align_content,
115            justify_content: self.justify_content,
116            gap: self.gap.to_taffy(rem_size),
117            flex_direction: self.flex_direction,
118            flex_wrap: self.flex_wrap,
119            flex_basis: self.flex_basis.to_taffy(rem_size).into(),
120            flex_grow: self.flex_grow,
121            flex_shrink: self.flex_shrink,
122            ..Default::default() // Ignore grid properties for now
123        }
124    }
125
126    /// Paints the background of an element styled with this style.
127    /// Return the bounds in which to paint the content.
128    pub fn paint_background<V: 'static, E: Element<V>>(
129        &self,
130        layout: &mut Layout<V, E::Layout>,
131        cx: &mut PaintContext<V>,
132    ) {
133        let bounds = layout.bounds(cx);
134        let rem_size = cx.rem_pixels();
135        if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
136            cx.scene.push_quad(gpui::Quad {
137                bounds,
138                background: Some(color.into()),
139                corner_radii: self.corner_radii.to_gpui(rem_size),
140                border: Default::default(),
141            });
142        }
143    }
144}
145
146impl Default for Style {
147    fn default() -> Self {
148        Style {
149            display: Display::DEFAULT,
150            overflow: Point {
151                x: Overflow::Visible,
152                y: Overflow::Visible,
153            },
154            scrollbar_width: 0.0,
155            position: Position::Relative,
156            inset: Edges::auto(),
157            margin: Edges::<Length>::zero(),
158            padding: Edges::<DefiniteLength>::zero(),
159            border: Edges::<DefiniteLength>::zero(),
160            size: Size::auto(),
161            min_size: Size::auto(),
162            max_size: Size::auto(),
163            aspect_ratio: None,
164            gap: Size::zero(),
165            // Aligment
166            align_items: None,
167            align_self: None,
168            align_content: None,
169            justify_content: None,
170            // Flexbox
171            flex_direction: FlexDirection::Row,
172            flex_wrap: FlexWrap::NoWrap,
173            flex_grow: 0.0,
174            flex_shrink: 1.0,
175            flex_basis: Length::Auto,
176            fill: None,
177            text_color: None,
178            corner_radii: CornerRadii::default(),
179        }
180    }
181}
182
183impl StyleRefinement {
184    pub fn text_style(&self) -> Option<TextStyleRefinement> {
185        self.text_color.map(|color| TextStyleRefinement {
186            color: Some(color.into()),
187            ..Default::default()
188        })
189    }
190}
191
192pub struct OptionalTextStyle {
193    color: Option<Hsla>,
194}
195
196impl OptionalTextStyle {
197    pub fn apply(&self, style: &mut gpui::fonts::TextStyle) {
198        if let Some(color) = self.color {
199            style.color = color.into();
200        }
201    }
202}
203
204#[derive(Clone)]
205pub enum Fill {
206    Color(Hsla),
207}
208
209impl Fill {
210    pub fn color(&self) -> Option<Hsla> {
211        match self {
212            Fill::Color(color) => Some(*color),
213        }
214    }
215}
216
217impl Default for Fill {
218    fn default() -> Self {
219        Self::Color(Hsla::default())
220    }
221}
222
223impl From<Hsla> for Fill {
224    fn from(color: Hsla) -> Self {
225        Self::Color(color)
226    }
227}
228
229#[derive(Clone, Refineable, Default)]
230pub struct CornerRadii {
231    top_left: AbsoluteLength,
232    top_right: AbsoluteLength,
233    bottom_left: AbsoluteLength,
234    bottom_right: AbsoluteLength,
235}
236
237impl CornerRadii {
238    pub fn to_gpui(&self, rem_size: f32) -> gpui::scene::CornerRadii {
239        gpui::scene::CornerRadii {
240            top_left: self.top_left.to_pixels(rem_size),
241            top_right: self.top_right.to_pixels(rem_size),
242            bottom_left: self.bottom_left.to_pixels(rem_size),
243            bottom_right: self.bottom_right.to_pixels(rem_size),
244        }
245    }
246}
247
248pub trait Styleable {
249    type Style: refineable::Refineable;
250
251    fn declared_style(&mut self) -> &mut playground::style::StyleRefinement;
252
253    fn style(&mut self) -> playground::style::Style {
254        let mut style = playground::style::Style::default();
255        style.refine(self.declared_style());
256        style
257    }
258}
259
260// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
261//
262// Example:
263// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
264// fn p_2(mut self) -> Self where Self: Sized;
265use crate as playground; // Macro invocation references this crate as playground.
266pub trait StyleHelpers: Styleable<Style = Style> {
267    styleable_helpers!();
268
269    fn fill<F>(mut self, fill: F) -> Self
270    where
271        F: Into<Fill>,
272        Self: Sized,
273    {
274        self.declared_style().fill = Some(fill.into());
275        self
276    }
277
278    fn text_color<C>(mut self, color: C) -> Self
279    where
280        C: Into<Hsla>,
281        Self: Sized,
282    {
283        self.declared_style().text_color = Some(color.into());
284        self
285    }
286}