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}