style.rs

  1use crate::{
  2    color::Hsla,
  3    elements::hoverable::{hoverable, Hoverable},
  4    elements::pressable::{pressable, Pressable},
  5    paint_context::PaintContext,
  6};
  7pub use fonts::Style as FontStyle;
  8pub use fonts::Weight as FontWeight;
  9pub use gpui::taffy::style::{
 10    AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
 11    Overflow, Position,
 12};
 13use gpui::{
 14    fonts::{self, TextStyleRefinement},
 15    geometry::{
 16        rect::RectF, relative, vector::Vector2F, AbsoluteLength, DefiniteLength, Edges,
 17        EdgesRefinement, Length, Point, PointRefinement, Size, SizeRefinement,
 18    },
 19    scene, taffy, WindowContext,
 20};
 21use gpui2_macros::styleable_helpers;
 22use refineable::{Refineable, RefinementCascade};
 23use std::sync::Arc;
 24
 25#[derive(Clone, Refineable)]
 26pub struct Style {
 27    /// What layout strategy should be used?
 28    pub display: Display,
 29
 30    // Overflow properties
 31    /// How children overflowing their container should affect layout
 32    #[refineable]
 33    pub overflow: Point<Overflow>,
 34    /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
 35    pub scrollbar_width: f32,
 36
 37    // Position properties
 38    /// What should the `position` value of this struct use as a base offset?
 39    pub position: Position,
 40    /// How should the position of this element be tweaked relative to the layout defined?
 41    #[refineable]
 42    pub inset: Edges<Length>,
 43
 44    // Size properies
 45    /// Sets the initial size of the item
 46    #[refineable]
 47    pub size: Size<Length>,
 48    /// Controls the minimum size of the item
 49    #[refineable]
 50    pub min_size: Size<Length>,
 51    /// Controls the maximum size of the item
 52    #[refineable]
 53    pub max_size: Size<Length>,
 54    /// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
 55    pub aspect_ratio: Option<f32>,
 56
 57    // Spacing Properties
 58    /// How large should the margin be on each side?
 59    #[refineable]
 60    pub margin: Edges<Length>,
 61    /// How large should the padding be on each side?
 62    #[refineable]
 63    pub padding: Edges<DefiniteLength>,
 64    /// How large should the border be on each side?
 65    #[refineable]
 66    pub border_widths: Edges<AbsoluteLength>,
 67
 68    // Alignment properties
 69    /// How this node's children aligned in the cross/block axis?
 70    pub align_items: Option<AlignItems>,
 71    /// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
 72    pub align_self: Option<AlignSelf>,
 73    /// How should content contained within this item be aligned in the cross/block axis
 74    pub align_content: Option<AlignContent>,
 75    /// How should contained within this item be aligned in the main/inline axis
 76    pub justify_content: Option<JustifyContent>,
 77    /// How large should the gaps between items in a flex container be?
 78    #[refineable]
 79    pub gap: Size<DefiniteLength>,
 80
 81    // Flexbox properies
 82    /// Which direction does the main axis flow in?
 83    pub flex_direction: FlexDirection,
 84    /// Should elements wrap, or stay in a single line?
 85    pub flex_wrap: FlexWrap,
 86    /// Sets the initial main axis size of the item
 87    pub flex_basis: Length,
 88    /// 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.
 89    pub flex_grow: f32,
 90    /// 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.
 91    pub flex_shrink: f32,
 92
 93    /// The fill color of this element
 94    pub fill: Option<Fill>,
 95
 96    /// The border color of this element
 97    pub border_color: Option<Hsla>,
 98
 99    /// The radius of the corners of this element
100    #[refineable]
101    pub corner_radii: CornerRadii,
102
103    /// The color of text within this element. Cascades to children unless overridden.
104    pub text_color: Option<Hsla>,
105
106    /// The font size in rems.
107    pub font_size: Option<f32>,
108
109    pub font_family: Option<Arc<str>>,
110
111    pub font_weight: Option<FontWeight>,
112
113    pub font_style: Option<FontStyle>,
114}
115
116impl Style {
117    pub fn text_style(&self, cx: &WindowContext) -> Option<TextStyleRefinement> {
118        if self.text_color.is_none()
119            && self.font_size.is_none()
120            && self.font_family.is_none()
121            && self.font_weight.is_none()
122            && self.font_style.is_none()
123        {
124            return None;
125        }
126
127        Some(TextStyleRefinement {
128            color: self.text_color.map(Into::into),
129            font_family: self.font_family.clone(),
130            font_size: self.font_size.map(|size| size * cx.rem_size()),
131            font_weight: self.font_weight,
132            font_style: self.font_style,
133            underline: None,
134        })
135    }
136
137    pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
138        taffy::style::Style {
139            display: self.display,
140            overflow: self.overflow.clone().into(),
141            scrollbar_width: self.scrollbar_width,
142            position: self.position,
143            inset: self.inset.to_taffy(rem_size),
144            size: self.size.to_taffy(rem_size),
145            min_size: self.min_size.to_taffy(rem_size),
146            max_size: self.max_size.to_taffy(rem_size),
147            aspect_ratio: self.aspect_ratio,
148            margin: self.margin.to_taffy(rem_size),
149            padding: self.padding.to_taffy(rem_size),
150            border: self.border_widths.to_taffy(rem_size),
151            align_items: self.align_items,
152            align_self: self.align_self,
153            align_content: self.align_content,
154            justify_content: self.justify_content,
155            gap: self.gap.to_taffy(rem_size),
156            flex_direction: self.flex_direction,
157            flex_wrap: self.flex_wrap,
158            flex_basis: self.flex_basis.to_taffy(rem_size).into(),
159            flex_grow: self.flex_grow,
160            flex_shrink: self.flex_shrink,
161            ..Default::default() // Ignore grid properties for now
162        }
163    }
164
165    /// Paints the background of an element styled with this style.
166    pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
167        let rem_size = cx.rem_size();
168        if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
169            cx.scene.push_quad(gpui::Quad {
170                bounds,
171                background: Some(color.into()),
172                corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
173                border: Default::default(),
174            });
175        }
176    }
177
178    /// Paints the foreground of an element styled with this style.
179    pub fn paint_foreground<V: 'static>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
180        let rem_size = cx.rem_size();
181
182        if let Some(color) = self.border_color {
183            let border = self.border_widths.to_pixels(rem_size);
184            if !border.is_empty() {
185                cx.scene.push_quad(gpui::Quad {
186                    bounds,
187                    background: None,
188                    corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
189                    border: scene::Border {
190                        color: color.into(),
191                        top: border.top,
192                        right: border.right,
193                        bottom: border.bottom,
194                        left: border.left,
195                    },
196                });
197            }
198        }
199    }
200}
201
202impl Default for Style {
203    fn default() -> Self {
204        Style {
205            display: Display::Block,
206            overflow: Point {
207                x: Overflow::Visible,
208                y: Overflow::Visible,
209            },
210            scrollbar_width: 0.0,
211            position: Position::Relative,
212            inset: Edges::auto(),
213            margin: Edges::<Length>::zero(),
214            padding: Edges::<DefiniteLength>::zero(),
215            border_widths: Edges::<AbsoluteLength>::zero(),
216            size: Size::auto(),
217            min_size: Size::auto(),
218            max_size: Size::auto(),
219            aspect_ratio: None,
220            gap: Size::zero(),
221            // Aligment
222            align_items: None,
223            align_self: None,
224            align_content: None,
225            justify_content: None,
226            // Flexbox
227            flex_direction: FlexDirection::Row,
228            flex_wrap: FlexWrap::NoWrap,
229            flex_grow: 0.0,
230            flex_shrink: 1.0,
231            flex_basis: Length::Auto,
232            fill: None,
233            border_color: None,
234            corner_radii: CornerRadii::default(),
235            text_color: None,
236            font_size: Some(1.),
237            font_family: None,
238            font_weight: None,
239            font_style: None,
240        }
241    }
242}
243
244#[derive(Clone, Debug)]
245pub enum Fill {
246    Color(Hsla),
247}
248
249impl Fill {
250    pub fn color(&self) -> Option<Hsla> {
251        match self {
252            Fill::Color(color) => Some(*color),
253        }
254    }
255}
256
257impl Default for Fill {
258    fn default() -> Self {
259        Self::Color(Hsla::default())
260    }
261}
262
263impl From<Hsla> for Fill {
264    fn from(color: Hsla) -> Self {
265        Self::Color(color)
266    }
267}
268
269#[derive(Clone, Refineable, Default)]
270pub struct CornerRadii {
271    top_left: AbsoluteLength,
272    top_right: AbsoluteLength,
273    bottom_left: AbsoluteLength,
274    bottom_right: AbsoluteLength,
275}
276
277impl CornerRadii {
278    pub fn to_gpui(&self, box_size: Vector2F, rem_size: f32) -> gpui::scene::CornerRadii {
279        let max_radius = box_size.x().min(box_size.y()) / 2.;
280
281        gpui::scene::CornerRadii {
282            top_left: self.top_left.to_pixels(rem_size).min(max_radius),
283            top_right: self.top_right.to_pixels(rem_size).min(max_radius),
284            bottom_left: self.bottom_left.to_pixels(rem_size).min(max_radius),
285            bottom_right: self.bottom_right.to_pixels(rem_size).min(max_radius),
286        }
287    }
288}
289
290pub trait Styleable {
291    type Style: Refineable + Default;
292
293    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
294    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
295
296    fn computed_style(&mut self) -> Self::Style {
297        Self::Style::from_refinement(&self.style_cascade().merged())
298    }
299
300    fn hover(self) -> Hoverable<Self>
301    where
302        Self: Sized,
303    {
304        hoverable(self)
305    }
306
307    fn active(self) -> Pressable<Self>
308    where
309        Self: Sized,
310    {
311        pressable(self)
312    }
313}
314
315// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
316//
317// Example:
318// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
319// fn p_2(mut self) -> Self where Self: Sized;
320pub trait StyleHelpers: Styleable<Style = Style> {
321    styleable_helpers!();
322
323    fn h(mut self, height: Length) -> Self
324    where
325        Self: Sized,
326    {
327        self.declared_style().size.height = Some(height);
328        self
329    }
330
331    /// size_{n}: Sets width & height to {n}
332    ///
333    /// Example:
334    /// size_1: Sets width & height to 1
335    fn size(mut self, size: Length) -> Self
336    where
337        Self: Sized,
338    {
339        self.declared_style().size.height = Some(size);
340        self.declared_style().size.width = Some(size);
341        self
342    }
343
344    fn full(mut self) -> Self
345    where
346        Self: Sized,
347    {
348        self.declared_style().size.width = Some(relative(1.));
349        self.declared_style().size.height = Some(relative(1.));
350        self
351    }
352
353    fn relative(mut self) -> Self
354    where
355        Self: Sized,
356    {
357        self.declared_style().position = Some(Position::Relative);
358        self
359    }
360
361    fn absolute(mut self) -> Self
362    where
363        Self: Sized,
364    {
365        self.declared_style().position = Some(Position::Absolute);
366        self
367    }
368
369    fn block(mut self) -> Self
370    where
371        Self: Sized,
372    {
373        self.declared_style().display = Some(Display::Block);
374        self
375    }
376
377    fn flex(mut self) -> Self
378    where
379        Self: Sized,
380    {
381        self.declared_style().display = Some(Display::Flex);
382        self
383    }
384
385    fn flex_col(mut self) -> Self
386    where
387        Self: Sized,
388    {
389        self.declared_style().flex_direction = Some(FlexDirection::Column);
390        self
391    }
392
393    fn flex_row(mut self) -> Self
394    where
395        Self: Sized,
396    {
397        self.declared_style().flex_direction = Some(FlexDirection::Row);
398        self
399    }
400
401    fn flex_grow(mut self) -> Self
402    where
403        Self: Sized,
404    {
405        self.declared_style().flex_grow = Some(1.);
406        self
407    }
408
409    fn items_start(mut self) -> Self
410    where
411        Self: Sized,
412    {
413        self.declared_style().align_items = Some(AlignItems::FlexStart);
414        self
415    }
416
417    fn items_end(mut self) -> Self
418    where
419        Self: Sized,
420    {
421        self.declared_style().align_items = Some(AlignItems::FlexEnd);
422        self
423    }
424
425    fn items_center(mut self) -> Self
426    where
427        Self: Sized,
428    {
429        self.declared_style().align_items = Some(AlignItems::Center);
430        self
431    }
432
433    fn justify_between(mut self) -> Self
434    where
435        Self: Sized,
436    {
437        self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
438        self
439    }
440
441    fn justify_center(mut self) -> Self
442    where
443        Self: Sized,
444    {
445        self.declared_style().justify_content = Some(JustifyContent::Center);
446        self
447    }
448
449    fn justify_start(mut self) -> Self
450    where
451        Self: Sized,
452    {
453        self.declared_style().justify_content = Some(JustifyContent::Start);
454        self
455    }
456
457    fn justify_end(mut self) -> Self
458    where
459        Self: Sized,
460    {
461        self.declared_style().justify_content = Some(JustifyContent::End);
462        self
463    }
464
465    fn justify_around(mut self) -> Self
466    where
467        Self: Sized,
468    {
469        self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
470        self
471    }
472
473    fn fill<F>(mut self, fill: F) -> Self
474    where
475        F: Into<Fill>,
476        Self: Sized,
477    {
478        self.declared_style().fill = Some(fill.into());
479        self
480    }
481
482    fn border_color<C>(mut self, border_color: C) -> Self
483    where
484        C: Into<Hsla>,
485        Self: Sized,
486    {
487        self.declared_style().border_color = Some(border_color.into());
488        self
489    }
490
491    fn text_color<C>(mut self, color: C) -> Self
492    where
493        C: Into<Hsla>,
494        Self: Sized,
495    {
496        self.declared_style().text_color = Some(color.into());
497        self
498    }
499
500    fn text_xs(mut self) -> Self
501    where
502        Self: Sized,
503    {
504        self.declared_style().font_size = Some(0.75);
505        self
506    }
507
508    fn text_sm(mut self) -> Self
509    where
510        Self: Sized,
511    {
512        self.declared_style().font_size = Some(0.875);
513        self
514    }
515
516    fn text_base(mut self) -> Self
517    where
518        Self: Sized,
519    {
520        self.declared_style().font_size = Some(1.0);
521        self
522    }
523
524    fn text_lg(mut self) -> Self
525    where
526        Self: Sized,
527    {
528        self.declared_style().font_size = Some(1.125);
529        self
530    }
531
532    fn text_xl(mut self) -> Self
533    where
534        Self: Sized,
535    {
536        self.declared_style().font_size = Some(1.25);
537        self
538    }
539
540    fn text_2xl(mut self) -> Self
541    where
542        Self: Sized,
543    {
544        self.declared_style().font_size = Some(1.5);
545        self
546    }
547
548    fn text_3xl(mut self) -> Self
549    where
550        Self: Sized,
551    {
552        self.declared_style().font_size = Some(1.875);
553        self
554    }
555
556    fn font(mut self, family_name: impl Into<Arc<str>>) -> Self
557    where
558        Self: Sized,
559    {
560        self.declared_style().font_family = Some(family_name.into());
561        self
562    }
563}