1use floem_renderer::text::Weight;
7use peniko::color::palette;
8use peniko::{Brush, Color};
9
10use crate::theme::StyleThemeExt;
11use crate::unit::{PxPct, PxPctAuto};
12use crate::view::{IntoView, View};
13use crate::views::{ContainerExt, Decorators, Stack, TooltipExt};
14
15use super::values::{CombineResult, StrokeWrap, StylePropValue};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum PointerEvents {
20 Auto,
21 None,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum TextOverflow {
27 Wrap,
28 Clip,
29 Ellipsis,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Default)]
34pub enum CursorStyle {
35 #[default]
36 Default,
37 Pointer,
38 Progress,
39 Wait,
40 Crosshair,
41 Text,
42 Move,
43 Grab,
44 Grabbing,
45 ColResize,
46 RowResize,
47 WResize,
48 EResize,
49 SResize,
50 NResize,
51 NwResize,
52 NeResize,
53 SwResize,
54 SeResize,
55 NeswResize,
56 NwseResize,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq)]
61pub struct BoxShadow {
62 pub blur_radius: PxPct,
63 pub color: Color,
64 pub spread: PxPct,
65
66 pub left_offset: PxPct,
67 pub right_offset: PxPct,
68 pub top_offset: PxPct,
69 pub bottom_offset: PxPct,
70}
71
72impl BoxShadow {
73 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn blur_radius(mut self, radius: impl Into<PxPct>) -> Self {
81 self.blur_radius = radius.into();
82 self
83 }
84
85 pub fn spread(mut self, spread: impl Into<PxPct>) -> Self {
88 self.spread = spread.into();
89 self
90 }
91
92 pub fn color(mut self, color: impl Into<Color>) -> Self {
94 self.color = color.into();
95 self
96 }
97
98 pub fn left_offset(mut self, left_offset: impl Into<PxPct>) -> Self {
100 self.left_offset = left_offset.into();
101 self
102 }
103
104 pub fn right_offset(mut self, right_offset: impl Into<PxPct>) -> Self {
106 self.right_offset = right_offset.into();
107 self
108 }
109
110 pub fn top_offset(mut self, top_offset: impl Into<PxPct>) -> Self {
112 self.top_offset = top_offset.into();
113 self
114 }
115
116 pub fn bottom_offset(mut self, bottom_offset: impl Into<PxPct>) -> Self {
118 self.bottom_offset = bottom_offset.into();
119 self
120 }
121
122 pub fn v_offset(mut self, v_offset: impl Into<PxPct>) -> Self {
125 let offset = v_offset.into();
126 self.top_offset = -offset;
127 self.bottom_offset = offset;
128 self
129 }
130
131 pub fn h_offset(mut self, h_offset: impl Into<PxPct>) -> Self {
134 let offset = h_offset.into();
135 self.left_offset = -offset;
136 self.right_offset = offset;
137 self
138 }
139}
140
141impl Default for BoxShadow {
142 fn default() -> Self {
143 Self {
144 blur_radius: PxPct::Px(0.),
145 color: palette::css::BLACK,
146 spread: PxPct::Px(0.),
147 left_offset: PxPct::Px(0.),
148 right_offset: PxPct::Px(0.),
149 top_offset: PxPct::Px(0.),
150 bottom_offset: PxPct::Px(0.),
151 }
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Default)]
157pub struct Border {
158 pub left: Option<StrokeWrap>,
159 pub top: Option<StrokeWrap>,
160 pub right: Option<StrokeWrap>,
161 pub bottom: Option<StrokeWrap>,
162}
163
164impl Border {
165 pub fn new() -> Self {
166 Self::default()
167 }
168
169 pub fn all(border: impl Into<StrokeWrap>) -> Self {
170 let border = border.into();
171 Self {
172 left: Some(border.clone()),
173 top: Some(border.clone()),
174 right: Some(border.clone()),
175 bottom: Some(border),
176 }
177 }
178
179 pub fn left(mut self, border: impl Into<StrokeWrap>) -> Self {
180 self.left = Some(border.into());
181 self
182 }
183
184 pub fn top(mut self, border: impl Into<StrokeWrap>) -> Self {
185 self.top = Some(border.into());
186 self
187 }
188
189 pub fn right(mut self, border: impl Into<StrokeWrap>) -> Self {
190 self.right = Some(border.into());
191 self
192 }
193
194 pub fn bottom(mut self, border: impl Into<StrokeWrap>) -> Self {
195 self.bottom = Some(border.into());
196 self
197 }
198
199 pub fn horiz(mut self, border: impl Into<StrokeWrap>) -> Self {
200 let border = border.into();
201 self.left = Some(border.clone());
202 self.right = Some(border);
203 self
204 }
205
206 pub fn vert(mut self, border: impl Into<StrokeWrap>) -> Self {
207 let border = border.into();
208 self.top = Some(border.clone());
209 self.bottom = Some(border);
210 self
211 }
212}
213
214impl StylePropValue for Border {
215 fn debug_view(&self) -> Option<Box<dyn View>> {
216 let border = self.clone();
217 let details_view = move || {
218 let sides = [
219 ("Left:", border.left),
220 ("Top:", border.top),
221 ("Right:", border.right),
222 ("Bottom:", border.bottom),
223 ];
224
225 Stack::vertical_from_iter(
226 sides
227 .into_iter()
228 .filter_map(|(l, v)| v.map(|v| (l, v)))
229 .map(|(label, value)| {
230 Stack::horizontal((
231 label.style(|s| s.font_weight(Weight::BOLD).width(80.0)),
232 value.debug_view().unwrap(),
233 ))
234 .style(|s| s.items_center().gap(4.0))
235 .into_any()
236 }),
237 )
238 .style(|s| s.gap(4.0).padding(8.0))
239 };
240 Some(details_view().into_any())
241 }
242
243 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
244 Some(Self {
245 left: self.left.interpolate(&other.left, value)?,
246 top: self.top.interpolate(&other.top, value)?,
247 right: self.right.interpolate(&other.right, value)?,
248 bottom: self.bottom.interpolate(&other.bottom, value)?,
249 })
250 }
251
252 fn combine(&self, other: &Self) -> CombineResult<Self> {
253 let result = Border {
254 left: other.left.clone().or_else(|| self.left.clone()),
255 top: other.top.clone().or_else(|| self.top.clone()),
256 right: other.right.clone().or_else(|| self.right.clone()),
257 bottom: other.bottom.clone().or_else(|| self.bottom.clone()),
258 };
259
260 if result == *other {
261 CombineResult::Other
262 } else {
263 CombineResult::New(result)
264 }
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Default)]
270pub struct BorderColor {
271 pub left: Option<Brush>,
272 pub top: Option<Brush>,
273 pub right: Option<Brush>,
274 pub bottom: Option<Brush>,
275}
276
277impl BorderColor {
278 pub fn new() -> Self {
279 Self::default()
280 }
281
282 pub fn all(color: impl Into<Brush>) -> Self {
283 let color = color.into();
284 Self {
285 left: Some(color.clone()),
286 top: Some(color.clone()),
287 right: Some(color.clone()),
288 bottom: Some(color),
289 }
290 }
291
292 pub fn left(mut self, color: impl Into<Brush>) -> Self {
293 self.left = Some(color.into());
294 self
295 }
296
297 pub fn top(mut self, color: impl Into<Brush>) -> Self {
298 self.top = Some(color.into());
299 self
300 }
301
302 pub fn right(mut self, color: impl Into<Brush>) -> Self {
303 self.right = Some(color.into());
304 self
305 }
306
307 pub fn bottom(mut self, color: impl Into<Brush>) -> Self {
308 self.bottom = Some(color.into());
309 self
310 }
311
312 pub fn horiz(mut self, color: impl Into<Brush>) -> Self {
313 let color = color.into();
314 self.left = Some(color.clone());
315 self.right = Some(color);
316 self
317 }
318
319 pub fn vert(mut self, color: impl Into<Brush>) -> Self {
320 let color = color.into();
321 self.top = Some(color.clone());
322 self.bottom = Some(color);
323 self
324 }
325}
326
327impl StylePropValue for BorderColor {
328 fn debug_view(&self) -> Option<Box<dyn View>> {
329 let border_color = self.clone();
330 let details_view = move || {
331 let sides = [
332 ("Left:", border_color.left),
333 ("Top:", border_color.top),
334 ("Right:", border_color.right),
335 ("Bottom:", border_color.bottom),
336 ];
337
338 Stack::vertical_from_iter(
339 sides
340 .into_iter()
341 .filter_map(|(l, v)| v.map(|v| (l, v)))
342 .map(|(label, color)| {
343 Stack::horizontal((
344 label.style(|s| s.font_weight(Weight::BOLD).width(80.0)),
345 color.debug_view().unwrap(),
346 ))
347 .style(|s| s.items_center().gap(4.0))
348 }),
349 )
350 .style(|s| s.gap(4.0).padding(8.0))
351 };
352 Some(details_view().into_any())
353 }
354
355 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
356 Some(Self {
357 left: self.left.interpolate(&other.left, value)?,
358 top: self.top.interpolate(&other.top, value)?,
359 right: self.right.interpolate(&other.right, value)?,
360 bottom: self.bottom.interpolate(&other.bottom, value)?,
361 })
362 }
363
364 fn combine(&self, other: &Self) -> CombineResult<Self> {
365 let result = BorderColor {
366 left: other.left.clone().or_else(|| self.left.clone()),
367 top: other.top.clone().or_else(|| self.top.clone()),
368 right: other.right.clone().or_else(|| self.right.clone()),
369 bottom: other.bottom.clone().or_else(|| self.bottom.clone()),
370 };
371
372 if result == *other {
373 CombineResult::Other
374 } else {
375 CombineResult::New(result)
376 }
377 }
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Default)]
382pub struct BorderRadius {
383 pub top_left: Option<PxPct>,
384 pub top_right: Option<PxPct>,
385 pub bottom_left: Option<PxPct>,
386 pub bottom_right: Option<PxPct>,
387}
388
389impl BorderRadius {
390 pub fn new() -> Self {
391 Self::default()
392 }
393
394 pub fn all(radius: impl Into<PxPct>) -> Self {
395 let radius = radius.into();
396 Self {
397 top_left: Some(radius),
398 top_right: Some(radius),
399 bottom_left: Some(radius),
400 bottom_right: Some(radius),
401 }
402 }
403
404 pub fn top_left(mut self, radius: impl Into<PxPct>) -> Self {
405 self.top_left = Some(radius.into());
406 self
407 }
408
409 pub fn top_right(mut self, radius: impl Into<PxPct>) -> Self {
410 self.top_right = Some(radius.into());
411 self
412 }
413
414 pub fn bottom_left(mut self, radius: impl Into<PxPct>) -> Self {
415 self.bottom_left = Some(radius.into());
416 self
417 }
418
419 pub fn bottom_right(mut self, radius: impl Into<PxPct>) -> Self {
420 self.bottom_right = Some(radius.into());
421 self
422 }
423
424 pub fn top(mut self, radius: impl Into<PxPct>) -> Self {
425 let radius = radius.into();
426 self.top_left = Some(radius);
427 self.top_right = Some(radius);
428 self
429 }
430
431 pub fn bottom(mut self, radius: impl Into<PxPct>) -> Self {
432 let radius = radius.into();
433 self.bottom_left = Some(radius);
434 self.bottom_right = Some(radius);
435 self
436 }
437
438 pub fn left(mut self, radius: impl Into<PxPct>) -> Self {
439 let radius = radius.into();
440 self.top_left = Some(radius);
441 self.bottom_left = Some(radius);
442 self
443 }
444
445 pub fn right(mut self, radius: impl Into<PxPct>) -> Self {
446 let radius = radius.into();
447 self.top_right = Some(radius);
448 self.bottom_right = Some(radius);
449 self
450 }
451
452 pub fn resolve_border_radii(&self, min_side: f64) -> peniko::kurbo::RoundedRectRadii {
455 fn resolve(val: Option<PxPct>, min_side: f64) -> f64 {
456 match val {
457 Some(PxPct::Px(px)) => px,
458 Some(PxPct::Pct(pct)) => min_side * pct / 100.0,
459 None => 0.0,
460 }
461 }
462 peniko::kurbo::RoundedRectRadii {
463 top_left: resolve(self.top_left, min_side),
464 top_right: resolve(self.top_right, min_side),
465 bottom_right: resolve(self.bottom_right, min_side),
466 bottom_left: resolve(self.bottom_left, min_side),
467 }
468 }
469}
470
471impl StylePropValue for BorderRadius {
472 fn debug_view(&self) -> Option<Box<dyn View>> {
473 let border_radius = *self;
474 let details_view = move || {
475 let corners = [
476 ("Top Left:", border_radius.top_left),
477 ("Top Right:", border_radius.top_right),
478 ("Bottom Left:", border_radius.bottom_left),
479 ("Bottom Right:", border_radius.bottom_right),
480 ];
481
482 Stack::vertical_from_iter(
483 corners
484 .into_iter()
485 .filter_map(|(l, v)| v.map(|v| (l, v)))
486 .map(|(label, radius)| {
487 Stack::horizontal((
488 label.style(|s| s.font_weight(Weight::BOLD).width(80.0)),
489 radius.debug_view().unwrap(),
490 ))
491 .style(|s| s.items_center().gap(4.0))
492 }),
493 )
494 .style(|s| s.gap(4.0).padding(8.0))
495 };
496 Some(details_view().into_any())
497 }
498
499 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
500 Some(Self {
501 top_left: self.top_left.interpolate(&other.top_left, value)?,
502 top_right: self.top_right.interpolate(&other.top_right, value)?,
503 bottom_left: self.bottom_left.interpolate(&other.bottom_left, value)?,
504 bottom_right: self.bottom_right.interpolate(&other.bottom_right, value)?,
505 })
506 }
507
508 fn combine(&self, other: &Self) -> CombineResult<Self> {
509 let result = BorderRadius {
510 top_left: other.top_left.or(self.top_left),
511 top_right: other.top_right.or(self.top_right),
512 bottom_left: other.bottom_left.or(self.bottom_left),
513 bottom_right: other.bottom_right.or(self.bottom_right),
514 };
515
516 if result == *other {
517 CombineResult::Other
518 } else {
519 CombineResult::New(result)
520 }
521 }
522}
523
524#[derive(Debug, Clone, Copy, PartialEq, Default)]
526pub struct Padding {
527 pub left: Option<PxPct>,
528 pub top: Option<PxPct>,
529 pub right: Option<PxPct>,
530 pub bottom: Option<PxPct>,
531}
532
533impl Padding {
534 pub fn new() -> Self {
535 Self::default()
536 }
537
538 pub fn all(padding: impl Into<PxPct>) -> Self {
539 let padding = padding.into();
540 Self {
541 left: Some(padding),
542 top: Some(padding),
543 right: Some(padding),
544 bottom: Some(padding),
545 }
546 }
547
548 pub fn left(mut self, padding: impl Into<PxPct>) -> Self {
549 self.left = Some(padding.into());
550 self
551 }
552
553 pub fn top(mut self, padding: impl Into<PxPct>) -> Self {
554 self.top = Some(padding.into());
555 self
556 }
557
558 pub fn right(mut self, padding: impl Into<PxPct>) -> Self {
559 self.right = Some(padding.into());
560 self
561 }
562
563 pub fn bottom(mut self, padding: impl Into<PxPct>) -> Self {
564 self.bottom = Some(padding.into());
565 self
566 }
567
568 pub fn horiz(mut self, padding: impl Into<PxPct>) -> Self {
569 let padding = padding.into();
570 self.left = Some(padding);
571 self.right = Some(padding);
572 self
573 }
574
575 pub fn vert(mut self, padding: impl Into<PxPct>) -> Self {
576 let padding = padding.into();
577 self.top = Some(padding);
578 self.bottom = Some(padding);
579 self
580 }
581}
582
583impl StylePropValue for Padding {
584 fn debug_view(&self) -> Option<Box<dyn View>> {
585 let padding = *self;
586 let details_view = move || {
587 let sides = [
588 ("Left:", padding.left),
589 ("Top:", padding.top),
590 ("Right:", padding.right),
591 ("Bottom:", padding.bottom),
592 ];
593
594 Stack::vertical_from_iter(
595 sides
596 .into_iter()
597 .filter_map(|(l, v)| v.map(|v| (l, v)))
598 .map(|(label, padding)| {
599 Stack::horizontal((
600 label.style(|s| s.font_weight(Weight::BOLD).width(80.0)),
601 padding.debug_view().unwrap(),
602 ))
603 .style(|s| s.items_center().gap(4.0))
604 }),
605 )
606 .style(|s| s.gap(4.0).padding(8.0))
607 };
608 Some(details_view().into_any())
609 }
610
611 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
612 Some(Self {
613 left: self.left.interpolate(&other.left, value)?,
614 top: self.top.interpolate(&other.top, value)?,
615 right: self.right.interpolate(&other.right, value)?,
616 bottom: self.bottom.interpolate(&other.bottom, value)?,
617 })
618 }
619
620 fn combine(&self, other: &Self) -> CombineResult<Self> {
621 let result = Padding {
622 left: other.left.or(self.left),
623 top: other.top.or(self.top),
624 right: other.right.or(self.right),
625 bottom: other.bottom.or(self.bottom),
626 };
627
628 if result == *other {
629 CombineResult::Other
630 } else {
631 CombineResult::New(result)
632 }
633 }
634}
635
636#[derive(Debug, Clone, Copy, PartialEq, Default)]
638pub struct Margin {
639 pub left: Option<PxPctAuto>,
640 pub top: Option<PxPctAuto>,
641 pub right: Option<PxPctAuto>,
642 pub bottom: Option<PxPctAuto>,
643}
644
645impl Margin {
646 pub fn new() -> Self {
647 Self::default()
648 }
649
650 pub fn all(margin: impl Into<PxPctAuto>) -> Self {
651 let margin = margin.into();
652 Self {
653 left: Some(margin),
654 top: Some(margin),
655 right: Some(margin),
656 bottom: Some(margin),
657 }
658 }
659
660 pub fn left(mut self, margin: impl Into<PxPctAuto>) -> Self {
661 self.left = Some(margin.into());
662 self
663 }
664
665 pub fn top(mut self, margin: impl Into<PxPctAuto>) -> Self {
666 self.top = Some(margin.into());
667 self
668 }
669
670 pub fn right(mut self, margin: impl Into<PxPctAuto>) -> Self {
671 self.right = Some(margin.into());
672 self
673 }
674
675 pub fn bottom(mut self, margin: impl Into<PxPctAuto>) -> Self {
676 self.bottom = Some(margin.into());
677 self
678 }
679
680 pub fn horiz(mut self, margin: impl Into<PxPctAuto>) -> Self {
681 let margin = margin.into();
682 self.left = Some(margin);
683 self.right = Some(margin);
684 self
685 }
686
687 pub fn vert(mut self, margin: impl Into<PxPctAuto>) -> Self {
688 let margin = margin.into();
689 self.top = Some(margin);
690 self.bottom = Some(margin);
691 self
692 }
693}
694
695impl StylePropValue for Margin {
696 fn debug_view(&self) -> Option<Box<dyn View>> {
697 let margin = *self;
698 let details_view = move || {
699 let sides = [
700 ("Left:", margin.left),
701 ("Top:", margin.top),
702 ("Right:", margin.right),
703 ("Bottom:", margin.bottom),
704 ];
705
706 Stack::vertical_from_iter(
707 sides
708 .into_iter()
709 .filter_map(|(l, v)| v.map(|v| (l, v)))
710 .map(|(label, margin)| {
711 Stack::horizontal((
712 label.style(|s| s.font_weight(Weight::BOLD).width(80.0)),
713 margin.debug_view().unwrap(),
714 ))
715 .style(|s| s.items_center().gap(4.0))
716 }),
717 )
718 .style(|s| s.gap(4.0).padding(8.0))
719 };
720 Some(details_view().into_any())
721 }
722
723 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
724 Some(Self {
725 left: self.left.interpolate(&other.left, value)?,
726 top: self.top.interpolate(&other.top, value)?,
727 right: self.right.interpolate(&other.right, value)?,
728 bottom: self.bottom.interpolate(&other.bottom, value)?,
729 })
730 }
731
732 fn combine(&self, other: &Self) -> CombineResult<Self> {
733 let result = Margin {
734 left: other.left.or(self.left),
735 top: other.top.or(self.top),
736 right: other.right.or(self.right),
737 bottom: other.bottom.or(self.bottom),
738 };
739
740 if result == *other {
741 CombineResult::Other
742 } else {
743 CombineResult::New(result)
744 }
745 }
746}
747
748impl StylePropValue for CursorStyle {}
750impl StylePropValue for TextOverflow {}
751impl StylePropValue for PointerEvents {}
752
753impl StylePropValue for BoxShadow {
754 fn debug_view(&self) -> Option<Box<dyn View>> {
755 let shadow = *self;
757
758 let shadow_preview =
760 ().style(move |s| s.width(50.0).height(50.0))
761 .container()
762 .style(move |s| {
763 s.with_theme(|s, t| {
764 s.background(Color::TRANSPARENT)
765 .border_color(t.border())
766 .border(1.)
767 .border_radius(t.border_radius())
768 })
769 .apply_box_shadows(vec![shadow])
770 .margin(10.0)
771 });
772
773 let details_view = move || {
775 Stack::vertical((
776 Stack::horizontal((
777 "Color:".style(|s| s.font_weight(Weight::BOLD).width(80.0)),
778 shadow.color.debug_view().unwrap(),
779 ))
780 .style(|s| s.items_center().gap(4.0)),
781 Stack::horizontal((
782 "Blur:".style(|s| s.font_weight(Weight::BOLD).width(80.0)),
783 format!("{:?}", shadow.blur_radius),
784 ))
785 .style(|s| s.items_center().gap(4.0)),
786 Stack::horizontal((
787 "Spread:".style(|s| s.font_weight(Weight::BOLD).width(80.0)),
788 format!("{:?}", shadow.spread),
789 ))
790 .style(|s| s.items_center().gap(4.0)),
791 Stack::horizontal((
792 "Offset:".style(|s| s.font_weight(Weight::BOLD).width(80.0)),
793 format!(
794 "L: {:?}, R: {:?}, T: {:?}, B: {:?}",
795 shadow.left_offset,
796 shadow.right_offset,
797 shadow.top_offset,
798 shadow.bottom_offset
799 ),
800 ))
801 .style(|s| s.items_center().gap(4.0)),
802 ))
803 .style(|s| s.gap(4.0).padding(8.0))
804 };
805
806 let view = shadow_preview.tooltip(details_view);
808
809 Some(view.into_any())
810 }
811
812 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
813 Some(Self {
814 blur_radius: self
815 .blur_radius
816 .interpolate(&other.blur_radius, value)
817 .unwrap(),
818 color: self.color.interpolate(&other.color, value).unwrap(),
819 spread: self.spread.interpolate(&other.spread, value).unwrap(),
820 left_offset: self
821 .left_offset
822 .interpolate(&other.left_offset, value)
823 .unwrap(),
824 right_offset: self
825 .right_offset
826 .interpolate(&other.right_offset, value)
827 .unwrap(),
828 top_offset: self
829 .top_offset
830 .interpolate(&other.top_offset, value)
831 .unwrap(),
832 bottom_offset: self
833 .bottom_offset
834 .interpolate(&other.bottom_offset, value)
835 .unwrap(),
836 })
837 }
838}