1use gpui::{
2 App, BoxShadow, Corners, DefiniteLength, Div, Edges, FocusHandle, Hsla, Pixels,
3 Refineable as _, Role, StyleRefinement, Styled, Window, div, hsla, point,
4};
5
6#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
7pub enum RoleOverride {
8 #[default]
9 Implicit,
10 Presentational,
11 Role(Role),
12}
13
14impl RoleOverride {
15 pub fn resolve(self, default: impl FnOnce() -> Role) -> Option<Role> {
16 match self {
17 Self::Implicit => Some(default()),
18 Self::Presentational => None,
19 Self::Role(role) => Some(role),
20 }
21 }
22}
23impl From<Role> for RoleOverride {
24 fn from(role: Role) -> Self {
25 Self::Role(role)
26 }
27}
28impl From<Option<Role>> for RoleOverride {
29 fn from(role: Option<Role>) -> Self {
30 role.map_or(Self::Presentational, Self::Role)
31 }
32}
33
34pub fn h_flex() -> Div {
39 div().h_flex()
40}
41
42pub fn v_flex() -> Div {
47 div().v_flex()
48}
49
50pub fn box_shadow(
51 x: impl Into<Pixels>,
52 y: impl Into<Pixels>,
53 blur: impl Into<Pixels>,
54 spread: impl Into<Pixels>,
55 color: Hsla,
56) -> BoxShadow {
57 BoxShadow {
58 offset: point(x.into(), y.into()),
59 blur_radius: blur.into(),
60 spread_radius: spread.into(),
61 inset: false,
62 color,
63 }
64}
65
66macro_rules! font_weight {
67 ($method:ident, $weight:ident) => {
68 fn $method(self) -> Self {
69 self.font_weight(gpui::FontWeight::$weight)
70 }
71 };
72}
73
74#[cfg_attr(
75 any(feature = "inspector", debug_assertions),
76 gpui_macros::derive_inspector_reflection
77)]
78pub trait StyledExt: Styled + Sized {
79 fn refine_style(mut self, style: &StyleRefinement) -> Self {
80 self.style().refine(style);
81 self
82 }
83
84 fn h_flex(self) -> Self {
106 self.flex().flex_row().items_center()
107 }
108
109 fn v_flex(self) -> Self {
114 self.flex().flex_col()
115 }
116
117 fn paddings<L>(self, paddings: impl Into<Edges<L>>) -> Self
118 where
119 L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug + PartialEq,
120 {
121 let paddings = paddings.into();
122 self.pt(paddings.top.into())
123 .pb(paddings.bottom.into())
124 .pl(paddings.left.into())
125 .pr(paddings.right.into())
126 }
127
128 fn margins<L>(self, margins: impl Into<Edges<L>>) -> Self
129 where
130 L: Into<DefiniteLength> + Clone + Default + std::fmt::Debug + PartialEq,
131 {
132 let margins = margins.into();
133 self.mt(margins.top.into())
134 .mb(margins.bottom.into())
135 .ml(margins.left.into())
136 .mr(margins.right.into())
137 }
138
139 fn debug_red(self) -> Self {
140 if cfg!(debug_assertions) {
141 self.border_1().border_color(hsl(0., 72.2, 50.6))
142 } else {
143 self
144 }
145 }
146
147 fn debug_blue(self) -> Self {
148 if cfg!(debug_assertions) {
149 self.border_1().border_color(hsl(217.2, 91.2, 59.8))
150 } else {
151 self
152 }
153 }
154
155 fn debug_yellow(self) -> Self {
156 if cfg!(debug_assertions) {
157 self.border_1().border_color(hsl(47.9, 95.8, 53.1))
158 } else {
159 self
160 }
161 }
162
163 fn debug_green(self) -> Self {
164 if cfg!(debug_assertions) {
165 self.border_1().border_color(hsl(142.1, 70.6, 45.3))
166 } else {
167 self
168 }
169 }
170
171 fn debug_pink(self) -> Self {
172 if cfg!(debug_assertions) {
173 self.border_1().border_color(hsl(330.4, 81.2, 60.4))
174 } else {
175 self
176 }
177 }
178
179 fn debug_focused(self, focus_handle: &FocusHandle, window: &Window, cx: &App) -> Self {
180 if cfg!(debug_assertions) && focus_handle.contains_focused(window, cx) {
181 self.debug_blue()
182 } else {
183 self
184 }
185 }
186
187 font_weight!(font_thin, THIN);
188 font_weight!(font_extralight, EXTRA_LIGHT);
189 font_weight!(font_light, LIGHT);
190 font_weight!(font_normal, NORMAL);
191 font_weight!(font_medium, MEDIUM);
192 font_weight!(font_semibold, SEMIBOLD);
193 font_weight!(font_bold, BOLD);
194 font_weight!(font_extrabold, EXTRA_BOLD);
195 font_weight!(font_black, BLACK);
196
197 fn corner_radii(self, radius: Corners<Pixels>) -> Self {
198 self.rounded_tl(radius.top_left)
199 .rounded_tr(radius.top_right)
200 .rounded_bl(radius.bottom_left)
201 .rounded_br(radius.bottom_right)
202 }
203}
204
205impl<E: Styled> StyledExt for E {}
206
207#[cfg(any(feature = "inspector", debug_assertions))]
208pub fn styled_ext_reflection_methods<T: Styled + 'static>()
209-> Vec<gpui::inspector_reflection::FunctionReflection<T>> {
210 styled_ext_reflection::methods::<T>()
211}
212
213fn hsl(hue: f32, saturation: f32, lightness: f32) -> Hsla {
214 hsla(hue / 360., saturation / 100., lightness / 100., 1.)
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use gpui::{
221 Context, InteractiveElement as _, IntoElement, ParentElement as _, Render, TestAppContext,
222 px,
223 };
224
225 fn column(selector: &'static str, height: f32) -> Div {
226 div()
227 .w(px(20.))
228 .h(px(height))
229 .flex_shrink_0()
230 .debug_selector(move || selector.to_string())
231 }
232
233 struct CrossAxisTest;
234
235 impl Render for CrossAxisTest {
236 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
237 div()
238 .w(px(200.))
239 .h(px(100.))
240 .child(
241 h_flex()
242 .size_full()
243 .child(column("row-child", 40.))
244 .child(column("row-overflowing-child", 140.))
245 .child(
246 div()
247 .w(px(20.))
248 .debug_selector(|| "row-stretch".to_string()),
249 ),
250 )
251 .child(
252 v_flex().size_full().child(
253 div()
254 .h(px(20.))
255 .debug_selector(|| "col-stretch".to_string()),
256 ),
257 )
258 }
259 }
260
261 #[gpui::test]
264 fn h_flex_centers_and_v_flex_stretches_on_the_cross_axis(cx: &mut TestAppContext) {
265 let (_, cx) = cx.add_window_view(|_, _| CrossAxisTest);
266 cx.run_until_parked();
267 cx.update(|window, cx| {
268 _ = window.draw(cx);
269 });
270
271 let child = cx.debug_bounds("row-child").unwrap();
274 assert_eq!(child.top(), px(30.));
275 assert_eq!(cx.debug_bounds("row-stretch").unwrap().size.height, px(0.));
276
277 let overflowing = cx.debug_bounds("row-overflowing-child").unwrap();
280 assert_eq!(overflowing.top(), px(-20.));
281
282 assert_eq!(cx.debug_bounds("col-stretch").unwrap().size.width, px(200.));
284 }
285}