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