1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum PointerEvents {
22 Auto,
23 None,
24}
25
26#[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#[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#[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 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn blur_radius(mut self, radius: impl Into<Length>) -> Self {
91 self.blur_radius = radius.into();
92 self
93 }
94
95 pub fn spread(mut self, spread: impl Into<Length>) -> Self {
98 self.spread = spread.into();
99 self
100 }
101
102 pub fn color(mut self, color: impl Into<Color>) -> Self {
104 self.color = color.into();
105 self
106 }
107
108 pub fn left_offset(mut self, left_offset: impl Into<Length>) -> Self {
110 self.left_offset = left_offset.into();
111 self
112 }
113
114 pub fn right_offset(mut self, right_offset: impl Into<Length>) -> Self {
116 self.right_offset = right_offset.into();
117 self
118 }
119
120 pub fn top_offset(mut self, top_offset: impl Into<Length>) -> Self {
122 self.top_offset = top_offset.into();
123 self
124 }
125
126 pub fn bottom_offset(mut self, bottom_offset: impl Into<Length>) -> Self {
128 self.bottom_offset = bottom_offset.into();
129 self
130 }
131
132 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 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#[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#[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#[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 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#[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#[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
729impl 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 let shadow = *self;
771
772 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
885pub enum Focus {
886 #[default]
892 None,
893
894 PointerAndProgrammatic,
906
907 Keyboard,
917}
918
919impl Focus {
920 #[inline]
924 pub fn is_focusable(self) -> bool {
925 !matches!(self, Focus::None)
926 }
927
928 #[inline]
930 pub fn allows_pointer_focus(self) -> bool {
931 matches!(self, Focus::PointerAndProgrammatic | Focus::Keyboard)
932 }
933
934 #[inline]
936 pub fn allows_programmatic_focus(self) -> bool {
937 matches!(self, Focus::PointerAndProgrammatic | Focus::Keyboard)
938 }
939
940 #[inline]
944 pub fn allows_keyboard_navigation(self) -> bool {
945 matches!(self, Focus::Keyboard)
946 }
947
948 #[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}