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}