Skip to main content

floem/style/
components.rs

1//! Style component types for borders, shadows, padding, and margins.
2//!
3//! This module provides composite style types that represent multi-value
4//! CSS properties like borders and padding.
5
6use floem_renderer::text::FontWeight;
7use parley::style::{OverflowWrap, WordBreakStrength};
8use peniko::color::palette;
9use peniko::kurbo::Stroke;
10use peniko::{Brush, Color};
11
12use crate::theme::StyleThemeExt;
13use crate::unit::{FontSizeCx, Length, LengthAuto};
14use crate::view::{IntoView, View};
15use crate::views::{ContainerExt, Decorators, Stack, TooltipExt};
16
17use super::values::{StrokeWrap, StylePropValue};
18
19/// Pointer event handling mode
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum PointerEvents {
22    Auto,
23    None,
24}
25
26/// Text overflow behavior
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum TextOverflow {
29    NoWrap(NoWrapOverflow),
30    Wrap {
31        overflow_wrap: OverflowWrap,
32        word_break: WordBreakStrength,
33    },
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum NoWrapOverflow {
38    Clip,
39    Ellipsis,
40}
41
42/// Cursor style
43#[derive(Debug, Clone, Copy, PartialEq, Default)]
44pub enum CursorStyle {
45    #[default]
46    Default,
47    Pointer,
48    Progress,
49    Wait,
50    Crosshair,
51    Text,
52    Move,
53    Grab,
54    Grabbing,
55    ColResize,
56    RowResize,
57    WResize,
58    EResize,
59    SResize,
60    NResize,
61    NwResize,
62    NeResize,
63    SwResize,
64    SeResize,
65    NeswResize,
66    NwseResize,
67}
68
69/// Structure holding data about the shadow.
70#[derive(Debug, Clone, Copy, PartialEq)]
71pub struct BoxShadow {
72    pub blur_radius: Length,
73    pub color: Color,
74    pub spread: Length,
75
76    pub left_offset: Length,
77    pub right_offset: Length,
78    pub top_offset: Length,
79    pub bottom_offset: Length,
80}
81
82impl BoxShadow {
83    /// Create new default shadow.
84    pub fn new() -> Self {
85        Self::default()
86    }
87
88    /// Specifies shadow blur. The larger this value, the bigger the blur,
89    /// so the shadow becomes bigger and lighter.
90    pub fn blur_radius(mut self, radius: impl Into<Length>) -> Self {
91        self.blur_radius = radius.into();
92        self
93    }
94
95    /// Specifies shadow blur spread. Positive values will cause the shadow
96    /// to expand and grow bigger, negative values will cause the shadow to shrink.
97    pub fn spread(mut self, spread: impl Into<Length>) -> Self {
98        self.spread = spread.into();
99        self
100    }
101
102    /// Specifies color for the current shadow.
103    pub fn color(mut self, color: impl Into<Color>) -> Self {
104        self.color = color.into();
105        self
106    }
107
108    /// Specifies the offset of the left edge.
109    pub fn left_offset(mut self, left_offset: impl Into<Length>) -> Self {
110        self.left_offset = left_offset.into();
111        self
112    }
113
114    /// Specifies the offset of the right edge.
115    pub fn right_offset(mut self, right_offset: impl Into<Length>) -> Self {
116        self.right_offset = right_offset.into();
117        self
118    }
119
120    /// Specifies the offset of the top edge.
121    pub fn top_offset(mut self, top_offset: impl Into<Length>) -> Self {
122        self.top_offset = top_offset.into();
123        self
124    }
125
126    /// Specifies the offset of the bottom edge.
127    pub fn bottom_offset(mut self, bottom_offset: impl Into<Length>) -> Self {
128        self.bottom_offset = bottom_offset.into();
129        self
130    }
131
132    /// Specifies the offset on vertical axis.
133    /// Negative offset value places the shadow above the element.
134    pub fn v_offset(mut self, v_offset: impl Into<Length>) -> Self {
135        let offset = v_offset.into();
136        self.top_offset = -offset;
137        self.bottom_offset = offset;
138        self
139    }
140
141    /// Specifies the offset on horizontal axis.
142    /// Negative offset value places the shadow to the left of the element.
143    pub fn h_offset(mut self, h_offset: impl Into<Length>) -> Self {
144        let offset = h_offset.into();
145        self.left_offset = -offset;
146        self.right_offset = offset;
147        self
148    }
149}
150
151impl Default for BoxShadow {
152    fn default() -> Self {
153        Self {
154            blur_radius: Length::Pt(0.),
155            color: palette::css::BLACK,
156            spread: Length::Pt(0.),
157            left_offset: Length::Pt(0.),
158            right_offset: Length::Pt(0.),
159            top_offset: Length::Pt(0.),
160            bottom_offset: Length::Pt(0.),
161        }
162    }
163}
164
165/// Structure holding border widths for all four sides
166#[derive(Debug, Clone, PartialEq, Default)]
167pub struct Border {
168    pub left: Option<Stroke>,
169    pub top: Option<Stroke>,
170    pub right: Option<Stroke>,
171    pub bottom: Option<Stroke>,
172}
173
174impl Border {
175    pub fn new() -> Self {
176        Self::default()
177    }
178
179    pub fn all(border: impl Into<StrokeWrap>) -> Self {
180        let border = border.into();
181        Self {
182            left: Some(border.0.clone()),
183            top: Some(border.0.clone()),
184            right: Some(border.0.clone()),
185            bottom: Some(border.0),
186        }
187    }
188
189    pub fn left(mut self, border: impl Into<StrokeWrap>) -> Self {
190        self.left = Some(border.into().0);
191        self
192    }
193
194    pub fn top(mut self, border: impl Into<StrokeWrap>) -> Self {
195        self.top = Some(border.into().0);
196        self
197    }
198
199    pub fn right(mut self, border: impl Into<StrokeWrap>) -> Self {
200        self.right = Some(border.into().0);
201        self
202    }
203
204    pub fn bottom(mut self, border: impl Into<StrokeWrap>) -> Self {
205        self.bottom = Some(border.into().0);
206        self
207    }
208
209    pub fn horiz(mut self, border: impl Into<StrokeWrap>) -> Self {
210        let border = border.into();
211        self.left = Some(border.0.clone());
212        self.right = Some(border.0);
213        self
214    }
215
216    pub fn vert(mut self, border: impl Into<StrokeWrap>) -> Self {
217        let border = border.into();
218        self.top = Some(border.0.clone());
219        self.bottom = Some(border.0);
220        self
221    }
222}
223
224impl StylePropValue for Border {
225    fn content_hash(&self) -> u64 {
226        use std::hash::{Hash, Hasher};
227        let mut h = rustc_hash::FxHasher::default();
228        self.left.content_hash().hash(&mut h);
229        self.top.content_hash().hash(&mut h);
230        self.right.content_hash().hash(&mut h);
231        self.bottom.content_hash().hash(&mut h);
232        h.finish()
233    }
234    fn debug_view(&self) -> Option<Box<dyn View>> {
235        let border = self.clone();
236        let details_view = move || {
237            let sides = [
238                ("Left:", border.left),
239                ("Top:", border.top),
240                ("Right:", border.right),
241                ("Bottom:", border.bottom),
242            ];
243
244            Stack::vertical_from_iter(
245                sides
246                    .into_iter()
247                    .filter_map(|(l, v)| v.map(|v| (l, v)))
248                    .map(|(label, value)| {
249                        Stack::horizontal((
250                            label.style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
251                            value.debug_view().unwrap(),
252                        ))
253                        .style(|s| s.items_center().gap(4.0))
254                        .into_any()
255                    }),
256            )
257            .style(|s| s.gap(4.0).padding(8.0))
258        };
259        Some(details_view().into_any())
260    }
261
262    fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
263        Some(Self {
264            left: self.left.interpolate(&other.left, value)?,
265            top: self.top.interpolate(&other.top, value)?,
266            right: self.right.interpolate(&other.right, value)?,
267            bottom: self.bottom.interpolate(&other.bottom, value)?,
268        })
269    }
270}
271
272/// Structure holding border colors for all four sides
273#[derive(Debug, Clone, PartialEq, Default)]
274pub struct BorderColor {
275    pub left: Option<Brush>,
276    pub top: Option<Brush>,
277    pub right: Option<Brush>,
278    pub bottom: Option<Brush>,
279}
280
281impl BorderColor {
282    pub fn new() -> Self {
283        Self::default()
284    }
285
286    pub fn all(color: impl Into<Brush>) -> Self {
287        let color = color.into();
288        Self {
289            left: Some(color.clone()),
290            top: Some(color.clone()),
291            right: Some(color.clone()),
292            bottom: Some(color),
293        }
294    }
295
296    pub fn left(mut self, color: impl Into<Brush>) -> Self {
297        self.left = Some(color.into());
298        self
299    }
300
301    pub fn top(mut self, color: impl Into<Brush>) -> Self {
302        self.top = Some(color.into());
303        self
304    }
305
306    pub fn right(mut self, color: impl Into<Brush>) -> Self {
307        self.right = Some(color.into());
308        self
309    }
310
311    pub fn bottom(mut self, color: impl Into<Brush>) -> Self {
312        self.bottom = Some(color.into());
313        self
314    }
315
316    pub fn horiz(mut self, color: impl Into<Brush>) -> Self {
317        let color = color.into();
318        self.left = Some(color.clone());
319        self.right = Some(color);
320        self
321    }
322
323    pub fn vert(mut self, color: impl Into<Brush>) -> Self {
324        let color = color.into();
325        self.top = Some(color.clone());
326        self.bottom = Some(color);
327        self
328    }
329}
330
331impl StylePropValue for BorderColor {
332    fn content_hash(&self) -> u64 {
333        use std::hash::{Hash, Hasher};
334        let mut h = rustc_hash::FxHasher::default();
335        self.left.content_hash().hash(&mut h);
336        self.top.content_hash().hash(&mut h);
337        self.right.content_hash().hash(&mut h);
338        self.bottom.content_hash().hash(&mut h);
339        h.finish()
340    }
341    fn debug_view(&self) -> Option<Box<dyn View>> {
342        let border_color = self.clone();
343        let details_view = move || {
344            let sides = [
345                ("Left:", border_color.left),
346                ("Top:", border_color.top),
347                ("Right:", border_color.right),
348                ("Bottom:", border_color.bottom),
349            ];
350
351            Stack::vertical_from_iter(
352                sides
353                    .into_iter()
354                    .filter_map(|(l, v)| v.map(|v| (l, v)))
355                    .map(|(label, color)| {
356                        Stack::horizontal((
357                            label.style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
358                            color.debug_view().unwrap(),
359                        ))
360                        .style(|s| s.items_center().gap(4.0))
361                    }),
362            )
363            .style(|s| s.gap(4.0).padding(8.0))
364        };
365        Some(details_view().into_any())
366    }
367
368    fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
369        Some(Self {
370            left: self.left.interpolate(&other.left, value)?,
371            top: self.top.interpolate(&other.top, value)?,
372            right: self.right.interpolate(&other.right, value)?,
373            bottom: self.bottom.interpolate(&other.bottom, value)?,
374        })
375    }
376}
377
378/// Structure holding border radius for all four corners
379#[derive(Debug, Clone, Copy, PartialEq, Default)]
380pub struct BorderRadius {
381    pub top_left: Option<Length>,
382    pub top_right: Option<Length>,
383    pub bottom_left: Option<Length>,
384    pub bottom_right: Option<Length>,
385}
386
387impl BorderRadius {
388    pub fn new() -> Self {
389        Self::default()
390    }
391
392    pub fn all(radius: impl Into<Length>) -> Self {
393        let radius = radius.into();
394        Self {
395            top_left: Some(radius),
396            top_right: Some(radius),
397            bottom_left: Some(radius),
398            bottom_right: Some(radius),
399        }
400    }
401
402    pub fn top_left(mut self, radius: impl Into<Length>) -> Self {
403        self.top_left = Some(radius.into());
404        self
405    }
406
407    pub fn top_right(mut self, radius: impl Into<Length>) -> Self {
408        self.top_right = Some(radius.into());
409        self
410    }
411
412    pub fn bottom_left(mut self, radius: impl Into<Length>) -> Self {
413        self.bottom_left = Some(radius.into());
414        self
415    }
416
417    pub fn bottom_right(mut self, radius: impl Into<Length>) -> Self {
418        self.bottom_right = Some(radius.into());
419        self
420    }
421
422    pub fn top(mut self, radius: impl Into<Length>) -> Self {
423        let radius = radius.into();
424        self.top_left = Some(radius);
425        self.top_right = Some(radius);
426        self
427    }
428
429    pub fn bottom(mut self, radius: impl Into<Length>) -> Self {
430        let radius = radius.into();
431        self.bottom_left = Some(radius);
432        self.bottom_right = Some(radius);
433        self
434    }
435
436    pub fn left(mut self, radius: impl Into<Length>) -> Self {
437        let radius = radius.into();
438        self.top_left = Some(radius);
439        self.bottom_left = Some(radius);
440        self
441    }
442
443    pub fn right(mut self, radius: impl Into<Length>) -> Self {
444        let radius = radius.into();
445        self.top_right = Some(radius);
446        self.bottom_right = Some(radius);
447        self
448    }
449
450    /// Resolve border radii to absolute pixels given the min side of the element.
451    /// Percentage values are resolved relative to the min side.
452    pub fn resolve_border_radii(
453        &self,
454        min_side: f64,
455        resolve_cx: &FontSizeCx,
456    ) -> peniko::kurbo::RoundedRectRadii {
457        fn resolve(val: Option<Length>, min_side: f64, resolve_cx: &FontSizeCx) -> f64 {
458            val.map(|length| length.resolve(min_side, resolve_cx))
459                .unwrap_or(0.0)
460        }
461        peniko::kurbo::RoundedRectRadii {
462            top_left: resolve(self.top_left, min_side, resolve_cx),
463            top_right: resolve(self.top_right, min_side, resolve_cx),
464            bottom_right: resolve(self.bottom_right, min_side, resolve_cx),
465            bottom_left: resolve(self.bottom_left, min_side, resolve_cx),
466        }
467    }
468}
469
470impl StylePropValue for BorderRadius {
471    fn content_hash(&self) -> u64 {
472        use std::hash::{Hash, Hasher};
473        let mut h = rustc_hash::FxHasher::default();
474        self.top_left.content_hash().hash(&mut h);
475        self.top_right.content_hash().hash(&mut h);
476        self.bottom_left.content_hash().hash(&mut h);
477        self.bottom_right.content_hash().hash(&mut h);
478        h.finish()
479    }
480    fn debug_view(&self) -> Option<Box<dyn View>> {
481        let border_radius = *self;
482        let details_view = move || {
483            let corners = [
484                ("Top Left:", border_radius.top_left),
485                ("Top Right:", border_radius.top_right),
486                ("Bottom Left:", border_radius.bottom_left),
487                ("Bottom Right:", border_radius.bottom_right),
488            ];
489
490            Stack::vertical_from_iter(
491                corners
492                    .into_iter()
493                    .filter_map(|(l, v)| v.map(|v| (l, v)))
494                    .map(|(label, radius)| {
495                        Stack::horizontal((
496                            label.style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
497                            radius.debug_view().unwrap(),
498                        ))
499                        .style(|s| s.items_center().gap(4.0))
500                    }),
501            )
502            .style(|s| s.gap(4.0).padding(8.0))
503        };
504        Some(details_view().into_any())
505    }
506
507    fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
508        Some(Self {
509            top_left: self.top_left.interpolate(&other.top_left, value)?,
510            top_right: self.top_right.interpolate(&other.top_right, value)?,
511            bottom_left: self.bottom_left.interpolate(&other.bottom_left, value)?,
512            bottom_right: self.bottom_right.interpolate(&other.bottom_right, value)?,
513        })
514    }
515}
516
517/// Structure holding padding values for all four sides
518#[derive(Debug, Clone, Copy, PartialEq, Default)]
519pub struct Padding {
520    pub left: Option<Length>,
521    pub top: Option<Length>,
522    pub right: Option<Length>,
523    pub bottom: Option<Length>,
524}
525
526impl Padding {
527    pub fn new() -> Self {
528        Self::default()
529    }
530
531    pub fn all(padding: impl Into<Length>) -> Self {
532        let padding = padding.into();
533        Self {
534            left: Some(padding),
535            top: Some(padding),
536            right: Some(padding),
537            bottom: Some(padding),
538        }
539    }
540
541    pub fn left(mut self, padding: impl Into<Length>) -> Self {
542        self.left = Some(padding.into());
543        self
544    }
545
546    pub fn top(mut self, padding: impl Into<Length>) -> Self {
547        self.top = Some(padding.into());
548        self
549    }
550
551    pub fn right(mut self, padding: impl Into<Length>) -> Self {
552        self.right = Some(padding.into());
553        self
554    }
555
556    pub fn bottom(mut self, padding: impl Into<Length>) -> Self {
557        self.bottom = Some(padding.into());
558        self
559    }
560
561    pub fn horiz(mut self, padding: impl Into<Length>) -> Self {
562        let padding = padding.into();
563        self.left = Some(padding);
564        self.right = Some(padding);
565        self
566    }
567
568    pub fn vert(mut self, padding: impl Into<Length>) -> Self {
569        let padding = padding.into();
570        self.top = Some(padding);
571        self.bottom = Some(padding);
572        self
573    }
574}
575
576impl StylePropValue for Padding {
577    fn content_hash(&self) -> u64 {
578        use std::hash::{Hash, Hasher};
579        let mut h = rustc_hash::FxHasher::default();
580        self.left.content_hash().hash(&mut h);
581        self.top.content_hash().hash(&mut h);
582        self.right.content_hash().hash(&mut h);
583        self.bottom.content_hash().hash(&mut h);
584        h.finish()
585    }
586    fn debug_view(&self) -> Option<Box<dyn View>> {
587        let padding = *self;
588        let details_view = move || {
589            let sides = [
590                ("Left:", padding.left),
591                ("Top:", padding.top),
592                ("Right:", padding.right),
593                ("Bottom:", padding.bottom),
594            ];
595
596            Stack::vertical_from_iter(
597                sides
598                    .into_iter()
599                    .filter_map(|(l, v)| v.map(|v| (l, v)))
600                    .map(|(label, padding)| {
601                        Stack::horizontal((
602                            label.style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
603                            padding.debug_view().unwrap(),
604                        ))
605                        .style(|s| s.items_center().gap(4.0))
606                    }),
607            )
608            .style(|s| s.gap(4.0).padding(8.0))
609        };
610        Some(details_view().into_any())
611    }
612
613    fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
614        Some(Self {
615            left: self.left.interpolate(&other.left, value)?,
616            top: self.top.interpolate(&other.top, value)?,
617            right: self.right.interpolate(&other.right, value)?,
618            bottom: self.bottom.interpolate(&other.bottom, value)?,
619        })
620    }
621}
622
623/// Structure holding margin values for all four sides
624#[derive(Debug, Clone, Copy, PartialEq, Default)]
625pub struct Margin {
626    pub left: Option<LengthAuto>,
627    pub top: Option<LengthAuto>,
628    pub right: Option<LengthAuto>,
629    pub bottom: Option<LengthAuto>,
630}
631
632impl Margin {
633    pub fn new() -> Self {
634        Self::default()
635    }
636
637    pub fn all(margin: impl Into<LengthAuto>) -> Self {
638        let margin = margin.into();
639        Self {
640            left: Some(margin),
641            top: Some(margin),
642            right: Some(margin),
643            bottom: Some(margin),
644        }
645    }
646
647    pub fn left(mut self, margin: impl Into<LengthAuto>) -> Self {
648        self.left = Some(margin.into());
649        self
650    }
651
652    pub fn top(mut self, margin: impl Into<LengthAuto>) -> Self {
653        self.top = Some(margin.into());
654        self
655    }
656
657    pub fn right(mut self, margin: impl Into<LengthAuto>) -> Self {
658        self.right = Some(margin.into());
659        self
660    }
661
662    pub fn bottom(mut self, margin: impl Into<LengthAuto>) -> Self {
663        self.bottom = Some(margin.into());
664        self
665    }
666
667    pub fn horiz(mut self, margin: impl Into<LengthAuto>) -> Self {
668        let margin = margin.into();
669        self.left = Some(margin);
670        self.right = Some(margin);
671        self
672    }
673
674    pub fn vert(mut self, margin: impl Into<LengthAuto>) -> Self {
675        let margin = margin.into();
676        self.top = Some(margin);
677        self.bottom = Some(margin);
678        self
679    }
680}
681
682impl StylePropValue for Margin {
683    fn content_hash(&self) -> u64 {
684        use std::hash::{Hash, Hasher};
685        let mut h = rustc_hash::FxHasher::default();
686        self.left.content_hash().hash(&mut h);
687        self.top.content_hash().hash(&mut h);
688        self.right.content_hash().hash(&mut h);
689        self.bottom.content_hash().hash(&mut h);
690        h.finish()
691    }
692    fn debug_view(&self) -> Option<Box<dyn View>> {
693        let margin = *self;
694        let details_view = move || {
695            let sides = [
696                ("Left:", margin.left),
697                ("Top:", margin.top),
698                ("Right:", margin.right),
699                ("Bottom:", margin.bottom),
700            ];
701
702            Stack::vertical_from_iter(
703                sides
704                    .into_iter()
705                    .filter_map(|(l, v)| v.map(|v| (l, v)))
706                    .map(|(label, margin)| {
707                        Stack::horizontal((
708                            label.style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
709                            margin.debug_view().unwrap(),
710                        ))
711                        .style(|s| s.items_center().gap(4.0))
712                    }),
713            )
714            .style(|s| s.gap(4.0).padding(8.0))
715        };
716        Some(details_view().into_any())
717    }
718
719    fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
720        Some(Self {
721            left: self.left.interpolate(&other.left, value)?,
722            top: self.top.interpolate(&other.top, value)?,
723            right: self.right.interpolate(&other.right, value)?,
724            bottom: self.bottom.interpolate(&other.bottom, value)?,
725        })
726    }
727}
728
729// Simple StylePropValue implementations for enums
730impl StylePropValue for CursorStyle {
731    fn content_hash(&self) -> u64 {
732        use std::hash::{Hash, Hasher};
733        let mut h = rustc_hash::FxHasher::default();
734        std::mem::discriminant(self).hash(&mut h);
735        h.finish()
736    }
737}
738impl StylePropValue for TextOverflow {
739    fn content_hash(&self) -> u64 {
740        use std::hash::{Hash, Hasher};
741        let mut h = rustc_hash::FxHasher::default();
742        std::mem::discriminant(self).hash(&mut h);
743        h.finish()
744    }
745}
746impl StylePropValue for PointerEvents {
747    fn content_hash(&self) -> u64 {
748        use std::hash::{Hash, Hasher};
749        let mut h = rustc_hash::FxHasher::default();
750        std::mem::discriminant(self).hash(&mut h);
751        h.finish()
752    }
753}
754
755impl StylePropValue for BoxShadow {
756    fn content_hash(&self) -> u64 {
757        use std::hash::{Hash, Hasher};
758        let mut h = rustc_hash::FxHasher::default();
759        self.blur_radius.content_hash().hash(&mut h);
760        self.color.content_hash().hash(&mut h);
761        self.spread.content_hash().hash(&mut h);
762        self.left_offset.content_hash().hash(&mut h);
763        self.right_offset.content_hash().hash(&mut h);
764        self.top_offset.content_hash().hash(&mut h);
765        self.bottom_offset.content_hash().hash(&mut h);
766        h.finish()
767    }
768    fn debug_view(&self) -> Option<Box<dyn View>> {
769        // Create a preview container that shows a visual representation of the shadow
770        let shadow = *self;
771
772        // Shadow preview box
773        let shadow_preview =
774            ().style(move |s| s.width(50.0).height(50.0))
775                .container()
776                .style(move |s| {
777                    s.background(Color::TRANSPARENT)
778                        .border(1.)
779                        .apply_box_shadows(vec![shadow])
780                        .margin(10.0)
781                        .with_theme(|s, t| {
782                            s.border_color(t.border()).border_radius(t.border_radius())
783                        })
784                });
785
786        // Create a details section showing the shadow properties
787        let details_view = move || {
788            Stack::vertical((
789                Stack::horizontal((
790                    "Color:".style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
791                    shadow.color.debug_view().unwrap(),
792                ))
793                .style(|s| s.items_center().gap(4.0)),
794                Stack::horizontal((
795                    "Blur:".style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
796                    format!("{:?}", shadow.blur_radius),
797                ))
798                .style(|s| s.items_center().gap(4.0)),
799                Stack::horizontal((
800                    "Spread:".style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
801                    format!("{:?}", shadow.spread),
802                ))
803                .style(|s| s.items_center().gap(4.0)),
804                Stack::horizontal((
805                    "Offset:".style(|s| s.font_weight(FontWeight::BOLD).width(80.0)),
806                    format!(
807                        "L: {:?}, R: {:?}, T: {:?}, B: {:?}",
808                        shadow.left_offset,
809                        shadow.right_offset,
810                        shadow.top_offset,
811                        shadow.bottom_offset
812                    ),
813                ))
814                .style(|s| s.items_center().gap(4.0)),
815            ))
816            .style(|s| s.gap(4.0).padding(8.0))
817        };
818
819        // Combine preview and details
820        let view = shadow_preview.tooltip(details_view);
821
822        Some(view.into_any())
823    }
824
825    fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
826        Some(Self {
827            blur_radius: self
828                .blur_radius
829                .interpolate(&other.blur_radius, value)
830                .unwrap(),
831            color: self.color.interpolate(&other.color, value).unwrap(),
832            spread: self.spread.interpolate(&other.spread, value).unwrap(),
833            left_offset: self
834                .left_offset
835                .interpolate(&other.left_offset, value)
836                .unwrap(),
837            right_offset: self
838                .right_offset
839                .interpolate(&other.right_offset, value)
840                .unwrap(),
841            top_offset: self
842                .top_offset
843                .interpolate(&other.top_offset, value)
844                .unwrap(),
845            bottom_offset: self
846                .bottom_offset
847                .interpolate(&other.bottom_offset, value)
848                .unwrap(),
849        })
850    }
851}
852
853/// Controls whether and how a view can receive focus.
854///
855/// Focus determines which element receives keyboard input and is used for accessibility
856/// and keyboard navigation. This enum provides three levels of focus behavior, where
857/// each level includes the capabilities of the previous level. In particular,
858/// [`Focus::Keyboard`] always implies full focusability for pointer and
859/// programmatic focus too.
860///
861/// # Focus Sources
862///
863/// - **Programmatic**: Focus set via code (e.g., `view.request_focus()`)
864/// - **Pointer**: Focus set by clicking or tapping the view
865/// - **Keyboard**: Focus set by sequential navigation (Tab key, arrow keys, etc.)
866///
867/// # Examples
868///
869/// ```rust
870/// # use floem::prelude::*;
871/// // A button that can be focused by any means
872/// let _button = Button::new("Click me")
873///     .style(|s| s.keyboard_navigable());
874///
875/// // A custom scrollable container: focusable for arrow keys, but not in tab order
876/// let _scrollable = Empty::new()
877///     .scroll()
878///     .style(|s| s.focusable());
879///
880/// // A purely decorative element that should never receive focus
881/// let _decorative = Container::new(Empty::new())
882///     .style(|s| s.focus_none());
883/// ```
884#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
885pub enum Focus {
886    /// The view cannot receive focus through any means.
887    ///
888    /// Use this for decorative elements, labels, or containers that should never
889    /// be interactive. Clicking the view will not focus it, and programmatic
890    /// focus requests will be ignored.
891    #[default]
892    None,
893
894    /// The view can receive focus programmatically or via pointer, but is not
895    /// included in keyboard navigation order.
896    ///
897    /// Use this for:
898    /// - Custom containers that need focus for scroll or keyboard handling
899    /// - Elements that should be clickable but not tab-able
900    /// - Roving tabindex scenarios where only one item is keyboard navigable
901    /// - Dialog/modal containers that need programmatic focus
902    ///
903    /// The view will not be reachable via Tab or arrow key navigation, but can
904    /// be focused by clicking or calling `request_focus()`.
905    PointerAndProgrammatic,
906
907    /// The view can receive focus through all means: programmatically, via pointer,
908    /// and via keyboard navigation.
909    ///
910    /// Use this for interactive controls like buttons, inputs, links, and custom
911    /// widgets that should be fully accessible via keyboard. The view will be
912    /// included in the sequential focus navigation order (Tab/Shift+Tab) and
913    /// spatial navigation (arrow keys).
914    ///
915    /// This is the recommended setting for all interactive UI elements.
916    Keyboard,
917}
918
919impl Focus {
920    /// Returns `true` if the view can receive focus in any way.
921    ///
922    /// This includes programmatic focus, pointer focus, and keyboard navigation.
923    #[inline]
924    pub fn is_focusable(self) -> bool {
925        !matches!(self, Focus::None)
926    }
927
928    /// Returns `true` if the view can receive focus via pointer (click/tap).
929    #[inline]
930    pub fn allows_pointer_focus(self) -> bool {
931        matches!(self, Focus::PointerAndProgrammatic | Focus::Keyboard)
932    }
933
934    /// Returns `true` if the view can receive focus programmatically (via code).
935    #[inline]
936    pub fn allows_programmatic_focus(self) -> bool {
937        matches!(self, Focus::PointerAndProgrammatic | Focus::Keyboard)
938    }
939
940    /// Returns `true` if the view is included in keyboard navigation order.
941    ///
942    /// This means the view can be reached via Tab, Shift+Tab, or arrow key navigation.
943    #[inline]
944    pub fn allows_keyboard_navigation(self) -> bool {
945        matches!(self, Focus::Keyboard)
946    }
947
948    /// Returns `true` if the view should be excluded from all focus mechanisms.
949    #[inline]
950    pub fn is_none(self) -> bool {
951        matches!(self, Focus::None)
952    }
953}
954impl StylePropValue for Focus {
955    fn content_hash(&self) -> u64 {
956        use std::hash::{Hash, Hasher};
957        let mut h = rustc_hash::FxHasher::default();
958        self.hash(&mut h);
959        h.finish()
960    }
961}