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
319use crate as gpui2;
320
321// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
322//
323// Example:
324// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
325// fn p_2(mut self) -> Self;
326pub trait StyleHelpers: Sized + Styleable<Style = Style> {
327 styleable_helpers!();
328
329 fn full(mut self) -> Self {
330 self.declared_style().size.width = Some(relative(1.).into());
331 self.declared_style().size.height = Some(relative(1.).into());
332 self
333 }
334
335 fn relative(mut self) -> Self {
336 self.declared_style().position = Some(Position::Relative);
337 self
338 }
339
340 fn absolute(mut self) -> Self {
341 self.declared_style().position = Some(Position::Absolute);
342 self
343 }
344
345 fn block(mut self) -> Self {
346 self.declared_style().display = Some(Display::Block);
347 self
348 }
349
350 fn flex(mut self) -> Self {
351 self.declared_style().display = Some(Display::Flex);
352 self
353 }
354
355 fn flex_col(mut self) -> Self {
356 self.declared_style().flex_direction = Some(FlexDirection::Column);
357 self
358 }
359
360 fn flex_row(mut self) -> Self {
361 self.declared_style().flex_direction = Some(FlexDirection::Row);
362 self
363 }
364
365 fn flex_1(mut self) -> Self {
366 self.declared_style().flex_grow = Some(1.);
367 self.declared_style().flex_shrink = Some(1.);
368 self.declared_style().flex_basis = Some(relative(0.).into());
369 self
370 }
371
372 fn flex_auto(mut self) -> Self {
373 self.declared_style().flex_grow = Some(1.);
374 self.declared_style().flex_shrink = Some(1.);
375 self.declared_style().flex_basis = Some(Length::Auto);
376 self
377 }
378
379 fn flex_initial(mut self) -> Self {
380 self.declared_style().flex_grow = Some(0.);
381 self.declared_style().flex_shrink = Some(1.);
382 self.declared_style().flex_basis = Some(Length::Auto);
383 self
384 }
385
386 fn flex_none(mut self) -> Self {
387 self.declared_style().flex_grow = Some(0.);
388 self.declared_style().flex_shrink = Some(0.);
389 self
390 }
391
392 fn grow(mut self) -> Self {
393 self.declared_style().flex_grow = Some(1.);
394 self
395 }
396
397 fn items_start(mut self) -> Self {
398 self.declared_style().align_items = Some(AlignItems::FlexStart);
399 self
400 }
401
402 fn items_end(mut self) -> Self {
403 self.declared_style().align_items = Some(AlignItems::FlexEnd);
404 self
405 }
406
407 fn items_center(mut self) -> Self {
408 self.declared_style().align_items = Some(AlignItems::Center);
409 self
410 }
411
412 fn justify_between(mut self) -> Self {
413 self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
414 self
415 }
416
417 fn justify_center(mut self) -> Self {
418 self.declared_style().justify_content = Some(JustifyContent::Center);
419 self
420 }
421
422 fn justify_start(mut self) -> Self {
423 self.declared_style().justify_content = Some(JustifyContent::Start);
424 self
425 }
426
427 fn justify_end(mut self) -> Self {
428 self.declared_style().justify_content = Some(JustifyContent::End);
429 self
430 }
431
432 fn justify_around(mut self) -> Self {
433 self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
434 self
435 }
436
437 fn fill<F>(mut self, fill: F) -> Self
438 where
439 F: Into<Fill>,
440 {
441 self.declared_style().fill = Some(fill.into());
442 self
443 }
444
445 fn border_color<C>(mut self, border_color: C) -> Self
446 where
447 C: Into<Hsla>,
448 {
449 self.declared_style().border_color = Some(border_color.into());
450 self
451 }
452
453 fn text_color<C>(mut self, color: C) -> Self
454 where
455 C: Into<Hsla>,
456 {
457 self.declared_style().text_color = Some(color.into());
458 self
459 }
460
461 fn text_xs(mut self) -> Self {
462 self.declared_style().font_size = Some(0.75);
463 self
464 }
465
466 fn text_sm(mut self) -> Self {
467 self.declared_style().font_size = Some(0.875);
468 self
469 }
470
471 fn text_base(mut self) -> Self {
472 self.declared_style().font_size = Some(1.0);
473 self
474 }
475
476 fn text_lg(mut self) -> Self {
477 self.declared_style().font_size = Some(1.125);
478 self
479 }
480
481 fn text_xl(mut self) -> Self {
482 self.declared_style().font_size = Some(1.25);
483 self
484 }
485
486 fn text_2xl(mut self) -> Self {
487 self.declared_style().font_size = Some(1.5);
488 self
489 }
490
491 fn text_3xl(mut self) -> Self {
492 self.declared_style().font_size = Some(1.875);
493 self
494 }
495
496 fn font(mut self, family_name: impl Into<Arc<str>>) -> Self {
497 self.declared_style().font_family = Some(family_name.into());
498 self
499 }
500}