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, AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length,
 17        Point, PointRefinement, Size, SizeRefinement,
 18    },
 19    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: Edges<DefiniteLength>,
 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    /// The radius of the corners of this element
 96    #[refineable]
 97    pub corner_radii: CornerRadii,
 98
 99    /// The color of text within this element. Cascades to children unless overridden.
100    pub text_color: Option<Hsla>,
101
102    /// The font size in rems.
103    pub font_size: Option<f32>,
104
105    pub font_family: Option<Arc<str>>,
106
107    pub font_weight: Option<FontWeight>,
108
109    pub font_style: Option<FontStyle>,
110}
111
112impl Style {
113    pub fn text_style(&self, cx: &WindowContext) -> Option<TextStyleRefinement> {
114        if self.text_color.is_none()
115            && self.font_size.is_none()
116            && self.font_family.is_none()
117            && self.font_weight.is_none()
118            && self.font_style.is_none()
119        {
120            return None;
121        }
122
123        Some(TextStyleRefinement {
124            color: self.text_color.map(Into::into),
125            font_family: self.font_family.clone(),
126            font_size: self.font_size.map(|size| size * cx.rem_size()),
127            font_weight: self.font_weight,
128            font_style: self.font_style,
129            underline: None,
130        })
131    }
132
133    pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
134        taffy::style::Style {
135            display: self.display,
136            overflow: self.overflow.clone().into(),
137            scrollbar_width: self.scrollbar_width,
138            position: self.position,
139            inset: self.inset.to_taffy(rem_size),
140            size: self.size.to_taffy(rem_size),
141            min_size: self.min_size.to_taffy(rem_size),
142            max_size: self.max_size.to_taffy(rem_size),
143            aspect_ratio: self.aspect_ratio,
144            margin: self.margin.to_taffy(rem_size),
145            padding: self.padding.to_taffy(rem_size),
146            border: self.border.to_taffy(rem_size),
147            align_items: self.align_items,
148            align_self: self.align_self,
149            align_content: self.align_content,
150            justify_content: self.justify_content,
151            gap: self.gap.to_taffy(rem_size),
152            flex_direction: self.flex_direction,
153            flex_wrap: self.flex_wrap,
154            flex_basis: self.flex_basis.to_taffy(rem_size).into(),
155            flex_grow: self.flex_grow,
156            flex_shrink: self.flex_shrink,
157            ..Default::default() // Ignore grid properties for now
158        }
159    }
160
161    /// Paints the background of an element styled with this style.
162    /// Return the bounds in which to paint the content.
163    pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
164        let rem_size = cx.rem_size();
165        if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
166            cx.scene.push_quad(gpui::Quad {
167                bounds,
168                background: Some(color.into()),
169                corner_radii: self.corner_radii.to_gpui(rem_size),
170                border: Default::default(),
171            });
172        }
173    }
174}
175
176impl Default for Style {
177    fn default() -> Self {
178        Style {
179            display: Display::Block,
180            overflow: Point {
181                x: Overflow::Visible,
182                y: Overflow::Visible,
183            },
184            scrollbar_width: 0.0,
185            position: Position::Relative,
186            inset: Edges::auto(),
187            margin: Edges::<Length>::zero(),
188            padding: Edges::<DefiniteLength>::zero(),
189            border: Edges::<DefiniteLength>::zero(),
190            size: Size::auto(),
191            min_size: Size::auto(),
192            max_size: Size::auto(),
193            aspect_ratio: None,
194            gap: Size::zero(),
195            // Aligment
196            align_items: None,
197            align_self: None,
198            align_content: None,
199            justify_content: None,
200            // Flexbox
201            flex_direction: FlexDirection::Row,
202            flex_wrap: FlexWrap::NoWrap,
203            flex_grow: 0.0,
204            flex_shrink: 1.0,
205            flex_basis: Length::Auto,
206            fill: None,
207            corner_radii: CornerRadii::default(),
208            text_color: None,
209            font_size: Some(1.),
210            font_family: None,
211            font_weight: None,
212            font_style: None,
213        }
214    }
215}
216
217#[derive(Clone, Debug)]
218pub enum Fill {
219    Color(Hsla),
220}
221
222impl Fill {
223    pub fn color(&self) -> Option<Hsla> {
224        match self {
225            Fill::Color(color) => Some(*color),
226        }
227    }
228}
229
230impl Default for Fill {
231    fn default() -> Self {
232        Self::Color(Hsla::default())
233    }
234}
235
236impl From<Hsla> for Fill {
237    fn from(color: Hsla) -> Self {
238        Self::Color(color)
239    }
240}
241
242#[derive(Clone, Refineable, Default)]
243pub struct CornerRadii {
244    top_left: AbsoluteLength,
245    top_right: AbsoluteLength,
246    bottom_left: AbsoluteLength,
247    bottom_right: AbsoluteLength,
248}
249
250impl CornerRadii {
251    pub fn to_gpui(&self, rem_size: f32) -> gpui::scene::CornerRadii {
252        gpui::scene::CornerRadii {
253            top_left: self.top_left.to_pixels(rem_size),
254            top_right: self.top_right.to_pixels(rem_size),
255            bottom_left: self.bottom_left.to_pixels(rem_size),
256            bottom_right: self.bottom_right.to_pixels(rem_size),
257        }
258    }
259}
260
261pub trait Styleable {
262    type Style: Refineable + Default;
263
264    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
265    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
266
267    fn computed_style(&mut self) -> Self::Style {
268        Self::Style::from_refinement(&self.style_cascade().merged())
269    }
270
271    fn hover(self) -> Hoverable<Self>
272    where
273        Self: Sized,
274    {
275        hoverable(self)
276    }
277
278    fn active(self) -> Pressable<Self>
279    where
280        Self: Sized,
281    {
282        pressable(self)
283    }
284}
285
286// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
287//
288// Example:
289// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
290// fn p_2(mut self) -> Self where Self: Sized;
291pub trait StyleHelpers: Styleable<Style = Style> {
292    styleable_helpers!();
293
294    fn h(mut self, height: Length) -> Self
295    where
296        Self: Sized,
297    {
298        self.declared_style().size.height = Some(height);
299        self
300    }
301
302    fn full(mut self) -> Self
303    where
304        Self: Sized,
305    {
306        self.declared_style().size.width = Some(relative(1.));
307        self.declared_style().size.height = Some(relative(1.));
308        self
309    }
310
311    fn relative(mut self) -> Self
312    where
313        Self: Sized,
314    {
315        self.declared_style().position = Some(Position::Relative);
316        self
317    }
318
319    fn absolute(mut self) -> Self
320    where
321        Self: Sized,
322    {
323        self.declared_style().position = Some(Position::Absolute);
324        self
325    }
326
327    fn block(mut self) -> Self
328    where
329        Self: Sized,
330    {
331        self.declared_style().display = Some(Display::Block);
332        self
333    }
334
335    fn flex(mut self) -> Self
336    where
337        Self: Sized,
338    {
339        self.declared_style().display = Some(Display::Flex);
340        self
341    }
342
343    fn flex_col(mut self) -> Self
344    where
345        Self: Sized,
346    {
347        self.declared_style().flex_direction = Some(FlexDirection::Column);
348        self
349    }
350
351    fn flex_row(mut self) -> Self
352    where
353        Self: Sized,
354    {
355        self.declared_style().flex_direction = Some(FlexDirection::Row);
356        self
357    }
358
359    fn flex_grow(mut self) -> Self
360    where
361        Self: Sized,
362    {
363        self.declared_style().flex_grow = Some(1.);
364        self
365    }
366
367    fn items_start(mut self) -> Self
368    where
369        Self: Sized,
370    {
371        self.declared_style().align_items = Some(AlignItems::FlexStart);
372        self
373    }
374
375    fn items_end(mut self) -> Self
376    where
377        Self: Sized,
378    {
379        self.declared_style().align_items = Some(AlignItems::FlexEnd);
380        self
381    }
382
383    fn items_center(mut self) -> Self
384    where
385        Self: Sized,
386    {
387        self.declared_style().align_items = Some(AlignItems::Center);
388        self
389    }
390
391    fn justify_between(mut self) -> Self
392    where
393        Self: Sized,
394    {
395        self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
396        self
397    }
398
399    fn justify_center(mut self) -> Self
400    where
401        Self: Sized,
402    {
403        self.declared_style().justify_content = Some(JustifyContent::Center);
404        self
405    }
406
407    fn justify_start(mut self) -> Self
408    where
409        Self: Sized,
410    {
411        self.declared_style().justify_content = Some(JustifyContent::Start);
412        self
413    }
414
415    fn justify_end(mut self) -> Self
416    where
417        Self: Sized,
418    {
419        self.declared_style().justify_content = Some(JustifyContent::End);
420        self
421    }
422
423    fn justify_around(mut self) -> Self
424    where
425        Self: Sized,
426    {
427        self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
428        self
429    }
430
431    fn fill<F>(mut self, fill: F) -> Self
432    where
433        F: Into<Fill>,
434        Self: Sized,
435    {
436        self.declared_style().fill = Some(fill.into());
437        self
438    }
439
440    fn text_color<C>(mut self, color: C) -> Self
441    where
442        C: Into<Hsla>,
443        Self: Sized,
444    {
445        self.declared_style().text_color = Some(color.into());
446        self
447    }
448
449    fn text_xs(mut self) -> Self
450    where
451        Self: Sized,
452    {
453        self.declared_style().font_size = Some(0.75);
454        self
455    }
456
457    fn text_sm(mut self) -> Self
458    where
459        Self: Sized,
460    {
461        self.declared_style().font_size = Some(0.875);
462        self
463    }
464
465    fn text_base(mut self) -> Self
466    where
467        Self: Sized,
468    {
469        self.declared_style().font_size = Some(1.0);
470        self
471    }
472
473    fn text_lg(mut self) -> Self
474    where
475        Self: Sized,
476    {
477        self.declared_style().font_size = Some(1.125);
478        self
479    }
480
481    fn text_xl(mut self) -> Self
482    where
483        Self: Sized,
484    {
485        self.declared_style().font_size = Some(1.25);
486        self
487    }
488
489    fn text_2xl(mut self) -> Self
490    where
491        Self: Sized,
492    {
493        self.declared_style().font_size = Some(1.5);
494        self
495    }
496
497    fn text_3xl(mut self) -> Self
498    where
499        Self: Sized,
500    {
501        self.declared_style().font_size = Some(1.875);
502        self
503    }
504
505    fn font(mut self, family_name: impl Into<Arc<str>>) -> Self
506    where
507        Self: Sized,
508    {
509        self.declared_style().font_family = Some(family_name.into());
510        self
511    }
512}