Skip to main content

fret_ui_kit/style/
chrome.rs

1use fret_core::Px;
2
3use super::{
4    ColorFallback, ColorRef, LengthRefinement, MetricRef, Radius, SignedMetricRef, Space,
5    ThemeTokenRead,
6};
7use crate::Corners4;
8use fret_core::scene::{DashPatternV1, Paint};
9
10#[derive(Debug, Clone, Default)]
11pub struct PaddingRefinement {
12    pub top: Option<MetricRef>,
13    pub right: Option<MetricRef>,
14    pub bottom: Option<MetricRef>,
15    pub left: Option<MetricRef>,
16}
17
18impl PaddingRefinement {
19    pub fn merge(mut self, other: PaddingRefinement) -> Self {
20        if other.top.is_some() {
21            self.top = other.top;
22        }
23        if other.right.is_some() {
24            self.right = other.right;
25        }
26        if other.bottom.is_some() {
27            self.bottom = other.bottom;
28        }
29        if other.left.is_some() {
30            self.left = other.left;
31        }
32        self
33    }
34}
35
36#[derive(Debug, Clone, Default)]
37pub struct PaddingLengthRefinement {
38    pub top: Option<LengthRefinement>,
39    pub right: Option<LengthRefinement>,
40    pub bottom: Option<LengthRefinement>,
41    pub left: Option<LengthRefinement>,
42}
43
44impl PaddingLengthRefinement {
45    pub fn merge(mut self, other: PaddingLengthRefinement) -> Self {
46        if other.top.is_some() {
47            self.top = other.top;
48        }
49        if other.right.is_some() {
50            self.right = other.right;
51        }
52        if other.bottom.is_some() {
53            self.bottom = other.bottom;
54        }
55        if other.left.is_some() {
56            self.left = other.left;
57        }
58        self
59    }
60}
61
62#[derive(Debug, Clone, Default)]
63pub struct MarginRefinement {
64    pub top: Option<MarginEdgeRefinement>,
65    pub right: Option<MarginEdgeRefinement>,
66    pub bottom: Option<MarginEdgeRefinement>,
67    pub left: Option<MarginEdgeRefinement>,
68}
69
70impl MarginRefinement {
71    pub fn merge(mut self, other: MarginRefinement) -> Self {
72        if other.top.is_some() {
73            self.top = other.top;
74        }
75        if other.right.is_some() {
76            self.right = other.right;
77        }
78        if other.bottom.is_some() {
79            self.bottom = other.bottom;
80        }
81        if other.left.is_some() {
82            self.left = other.left;
83        }
84        self
85    }
86}
87
88#[derive(Debug, Clone, Default)]
89pub struct InsetRefinement {
90    pub top: Option<InsetEdgeRefinement>,
91    pub right: Option<InsetEdgeRefinement>,
92    pub bottom: Option<InsetEdgeRefinement>,
93    pub left: Option<InsetEdgeRefinement>,
94}
95
96impl InsetRefinement {
97    pub fn merge(mut self, other: InsetRefinement) -> Self {
98        if other.top.is_some() {
99            self.top = other.top;
100        }
101        if other.right.is_some() {
102            self.right = other.right;
103        }
104        if other.bottom.is_some() {
105            self.bottom = other.bottom;
106        }
107        if other.left.is_some() {
108            self.left = other.left;
109        }
110        self
111    }
112}
113
114#[derive(Debug, Clone)]
115pub enum MarginEdgeRefinement {
116    Px(SignedMetricRef),
117    Fill,
118    /// Fraction of the containing block size (percent sizing).
119    ///
120    /// Expressed as a ratio (e.g. `0.5` for 50%).
121    Fraction(f32),
122    Auto,
123}
124
125impl MarginEdgeRefinement {
126    pub fn resolve<T: ThemeTokenRead + ?Sized>(&self, theme: &T) -> fret_ui::element::MarginEdge {
127        match self {
128            Self::Px(m) => fret_ui::element::MarginEdge::Px(m.resolve(theme)),
129            Self::Fill => fret_ui::element::MarginEdge::Fill,
130            Self::Fraction(f) => fret_ui::element::MarginEdge::Fraction(*f),
131            Self::Auto => fret_ui::element::MarginEdge::Auto,
132        }
133    }
134}
135
136#[derive(Debug, Clone)]
137pub enum InsetEdgeRefinement {
138    Px(SignedMetricRef),
139    Fill,
140    /// Fraction of the containing block size (percent sizing).
141    ///
142    /// Expressed as a ratio (e.g. `0.5` for 50%).
143    Fraction(f32),
144    Auto,
145}
146
147impl InsetEdgeRefinement {
148    pub fn resolve<T: ThemeTokenRead + ?Sized>(&self, theme: &T) -> fret_ui::element::InsetEdge {
149        match self {
150            Self::Px(m) => fret_ui::element::InsetEdge::Px(m.resolve(theme)),
151            Self::Fill => fret_ui::element::InsetEdge::Fill,
152            Self::Fraction(f) => fret_ui::element::InsetEdge::Fraction(*f),
153            Self::Auto => fret_ui::element::InsetEdge::Auto,
154        }
155    }
156}
157
158/// Control chrome style patches (colors, padding, borders, radius, etc).
159///
160/// This intentionally does **not** include layout-affecting fields like margin or absolute
161/// positioning. Those live in `LayoutRefinement` and apply only in the declarative authoring path.
162#[derive(Debug, Clone, Default)]
163pub struct ChromeRefinement {
164    pub padding: Option<PaddingRefinement>,
165    pub padding_length: Option<PaddingLengthRefinement>,
166    pub min_height: Option<MetricRef>,
167    pub radius: Option<MetricRef>,
168    pub corner_radii: Option<CornerRadiiRefinement>,
169    pub shadow: Option<ShadowPreset>,
170    pub border_width: Option<MetricRef>,
171    pub background: Option<ColorRef>,
172    pub background_paint: Option<Paint>,
173    pub border_color: Option<ColorRef>,
174    pub border_dash: Option<DashPatternV1>,
175    pub text_color: Option<ColorRef>,
176}
177
178#[derive(Debug, Clone, Default)]
179pub struct CornerRadiiRefinement {
180    pub top_left: Option<MetricRef>,
181    pub top_right: Option<MetricRef>,
182    pub bottom_right: Option<MetricRef>,
183    pub bottom_left: Option<MetricRef>,
184}
185
186impl CornerRadiiRefinement {
187    pub fn merge(mut self, other: CornerRadiiRefinement) -> Self {
188        if other.top_left.is_some() {
189            self.top_left = other.top_left;
190        }
191        if other.top_right.is_some() {
192            self.top_right = other.top_right;
193        }
194        if other.bottom_right.is_some() {
195            self.bottom_right = other.bottom_right;
196        }
197        if other.bottom_left.is_some() {
198            self.bottom_left = other.bottom_left;
199        }
200        self
201    }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq)]
205pub enum ShadowPreset {
206    None,
207    Xs,
208    Sm,
209    Md,
210    Lg,
211    Xl,
212}
213
214impl ChromeRefinement {
215    pub fn merge(mut self, other: ChromeRefinement) -> Self {
216        if let Some(p) = other.padding {
217            self.padding = Some(self.padding.unwrap_or_default().merge(p));
218        }
219        if let Some(p) = other.padding_length {
220            self.padding_length = Some(self.padding_length.unwrap_or_default().merge(p));
221        }
222        if other.min_height.is_some() {
223            self.min_height = other.min_height;
224        }
225        if other.radius.is_some() {
226            self.radius = other.radius;
227        }
228        if let Some(r) = other.corner_radii {
229            self.corner_radii = Some(self.corner_radii.unwrap_or_default().merge(r));
230        }
231        if other.shadow.is_some() {
232            self.shadow = other.shadow;
233        }
234        if other.border_width.is_some() {
235            self.border_width = other.border_width;
236        }
237        if other.background.is_some() {
238            self.background = other.background;
239            self.background_paint = None;
240        }
241        if other.background_paint.is_some() {
242            self.background_paint = other.background_paint;
243            self.background = None;
244        }
245        if other.border_color.is_some() {
246            self.border_color = other.border_color;
247        }
248        if other.border_dash.is_some() {
249            self.border_dash = other.border_dash;
250        }
251        if other.text_color.is_some() {
252            self.text_color = other.text_color;
253        }
254        self
255    }
256
257    pub fn px(mut self, space: Space) -> Self {
258        let m = MetricRef::space(space);
259        let mut padding = self.padding.unwrap_or_default();
260        padding.left = Some(m.clone());
261        padding.right = Some(m);
262        self.padding = Some(padding);
263        self
264    }
265
266    pub fn py(mut self, space: Space) -> Self {
267        let m = MetricRef::space(space);
268        let mut padding = self.padding.unwrap_or_default();
269        padding.top = Some(m.clone());
270        padding.bottom = Some(m);
271        self.padding = Some(padding);
272        self
273    }
274
275    pub fn p(mut self, space: Space) -> Self {
276        let m = MetricRef::space(space);
277        self.padding = Some(PaddingRefinement {
278            top: Some(m.clone()),
279            right: Some(m.clone()),
280            bottom: Some(m.clone()),
281            left: Some(m),
282        });
283        self
284    }
285
286    pub fn pt(mut self, space: Space) -> Self {
287        let mut padding = self.padding.unwrap_or_default();
288        padding.top = Some(MetricRef::space(space));
289        self.padding = Some(padding);
290        self
291    }
292
293    pub fn pr(mut self, space: Space) -> Self {
294        let mut padding = self.padding.unwrap_or_default();
295        padding.right = Some(MetricRef::space(space));
296        self.padding = Some(padding);
297        self
298    }
299
300    pub fn pb(mut self, space: Space) -> Self {
301        let mut padding = self.padding.unwrap_or_default();
302        padding.bottom = Some(MetricRef::space(space));
303        self.padding = Some(padding);
304        self
305    }
306
307    pub fn pl(mut self, space: Space) -> Self {
308        let mut padding = self.padding.unwrap_or_default();
309        padding.left = Some(MetricRef::space(space));
310        self.padding = Some(padding);
311        self
312    }
313
314    pub fn border_width(mut self, width: impl Into<MetricRef>) -> Self {
315        self.border_width = Some(width.into());
316        self
317    }
318
319    pub fn border_dash(mut self, dash: DashPatternV1) -> Self {
320        self.border_dash = Some(dash);
321        self
322    }
323
324    pub fn radius(mut self, radius: impl Into<MetricRef>) -> Self {
325        self.radius = Some(radius.into());
326        self
327    }
328
329    pub fn rounded(self, radius: Radius) -> Self {
330        self.radius(MetricRef::radius(radius))
331    }
332
333    pub fn corner_radii(mut self, radii: impl Into<Corners4<MetricRef>>) -> Self {
334        let radii = radii.into();
335        self.corner_radii = Some(CornerRadiiRefinement {
336            top_left: Some(radii.top_left),
337            top_right: Some(radii.top_right),
338            bottom_right: Some(radii.bottom_right),
339            bottom_left: Some(radii.bottom_left),
340        });
341        self
342    }
343
344    pub fn rounded_tl(mut self, radius: Radius) -> Self {
345        let mut radii = self.corner_radii.unwrap_or_default();
346        radii.top_left = Some(MetricRef::radius(radius));
347        self.corner_radii = Some(radii);
348        self
349    }
350
351    pub fn rounded_tr(mut self, radius: Radius) -> Self {
352        let mut radii = self.corner_radii.unwrap_or_default();
353        radii.top_right = Some(MetricRef::radius(radius));
354        self.corner_radii = Some(radii);
355        self
356    }
357
358    pub fn rounded_br(mut self, radius: Radius) -> Self {
359        let mut radii = self.corner_radii.unwrap_or_default();
360        radii.bottom_right = Some(MetricRef::radius(radius));
361        self.corner_radii = Some(radii);
362        self
363    }
364
365    pub fn rounded_bl(mut self, radius: Radius) -> Self {
366        let mut radii = self.corner_radii.unwrap_or_default();
367        radii.bottom_left = Some(MetricRef::radius(radius));
368        self.corner_radii = Some(radii);
369        self
370    }
371
372    pub fn shadow(mut self, shadow: ShadowPreset) -> Self {
373        self.shadow = Some(shadow);
374        self
375    }
376
377    pub fn shadow_none(self) -> Self {
378        self.shadow(ShadowPreset::None)
379    }
380
381    pub fn shadow_xs(self) -> Self {
382        self.shadow(ShadowPreset::Xs)
383    }
384
385    pub fn shadow_sm(self) -> Self {
386        self.shadow(ShadowPreset::Sm)
387    }
388
389    pub fn shadow_md(self) -> Self {
390        self.shadow(ShadowPreset::Md)
391    }
392
393    pub fn shadow_lg(self) -> Self {
394        self.shadow(ShadowPreset::Lg)
395    }
396
397    pub fn shadow_xl(self) -> Self {
398        self.shadow(ShadowPreset::Xl)
399    }
400
401    // Tailwind-like spacing scale, backed by namespaced tokens.
402    pub fn px_0(self) -> Self {
403        self.px(Space::N0)
404    }
405
406    pub fn px_1(self) -> Self {
407        self.px(Space::N1)
408    }
409
410    pub fn px_0p5(self) -> Self {
411        self.px(Space::N0p5)
412    }
413
414    pub fn px_1p5(self) -> Self {
415        self.px(Space::N1p5)
416    }
417
418    pub fn px_2(self) -> Self {
419        self.px(Space::N2)
420    }
421
422    pub fn px_2p5(self) -> Self {
423        self.px(Space::N2p5)
424    }
425
426    pub fn px_3(self) -> Self {
427        self.px(Space::N3)
428    }
429
430    pub fn px_4(self) -> Self {
431        self.px(Space::N4)
432    }
433
434    pub fn py_0(self) -> Self {
435        self.py(Space::N0)
436    }
437
438    pub fn py_1(self) -> Self {
439        self.py(Space::N1)
440    }
441
442    pub fn py_0p5(self) -> Self {
443        self.py(Space::N0p5)
444    }
445
446    pub fn py_1p5(self) -> Self {
447        self.py(Space::N1p5)
448    }
449
450    pub fn py_2(self) -> Self {
451        self.py(Space::N2)
452    }
453
454    pub fn py_2p5(self) -> Self {
455        self.py(Space::N2p5)
456    }
457
458    pub fn py_3(self) -> Self {
459        self.py(Space::N3)
460    }
461
462    pub fn py_4(self) -> Self {
463        self.py(Space::N4)
464    }
465
466    pub fn p_0(self) -> Self {
467        self.p(Space::N0)
468    }
469
470    pub fn p_1(self) -> Self {
471        self.p(Space::N1)
472    }
473
474    pub fn p_0p5(self) -> Self {
475        self.p(Space::N0p5)
476    }
477
478    pub fn p_1p5(self) -> Self {
479        self.p(Space::N1p5)
480    }
481
482    pub fn p_2(self) -> Self {
483        self.p(Space::N2)
484    }
485
486    pub fn p_2p5(self) -> Self {
487        self.p(Space::N2p5)
488    }
489
490    pub fn p_3(self) -> Self {
491        self.p(Space::N3)
492    }
493
494    pub fn p_4(self) -> Self {
495        self.p(Space::N4)
496    }
497
498    pub fn rounded_md(self) -> Self {
499        self.rounded(Radius::Md)
500    }
501
502    pub fn border_1(self) -> Self {
503        self.border_width(Px(1.0))
504    }
505
506    pub fn bg(mut self, color: ColorRef) -> Self {
507        self.background = Some(color);
508        self.background_paint = None;
509        self
510    }
511
512    pub fn background_paint(mut self, paint: Paint) -> Self {
513        self.background_paint = Some(paint);
514        self.background = None;
515        self
516    }
517
518    pub fn border_color(mut self, color: ColorRef) -> Self {
519        self.border_color = Some(color);
520        self
521    }
522
523    pub fn text_color(mut self, color: ColorRef) -> Self {
524        self.text_color = Some(color);
525        self
526    }
527
528    pub fn focused_border(self) -> Self {
529        self.border_1().border_color(ColorRef::Token {
530            key: "ring",
531            fallback: ColorFallback::ThemeFocusRing,
532        })
533    }
534
535    pub fn debug_border(self, color: ColorRef) -> Self {
536        if cfg!(debug_assertions) {
537            self.border_1().border_color(color)
538        } else {
539            self
540        }
541    }
542
543    pub fn debug_border_primary(self) -> Self {
544        self.debug_border(ColorRef::Token {
545            key: "primary",
546            fallback: ColorFallback::ThemeAccent,
547        })
548    }
549
550    pub fn debug_border_destructive(self) -> Self {
551        self.debug_border(ColorRef::Token {
552            key: "destructive",
553            fallback: ColorFallback::Color(fret_core::Color::from_srgb_hex_rgb(0xef_44_44)),
554        })
555    }
556
557    pub fn debug_border_ring(self) -> Self {
558        self.debug_border(ColorRef::Token {
559            key: "ring",
560            fallback: ColorFallback::ThemeFocusRing,
561        })
562    }
563}