1use floem_reactive::{RwSignal, SignalGet, SignalUpdate as _};
4use floem_renderer::Renderer;
5use floem_renderer::text::{LineHeightValue, Weight};
6use peniko::color::{HueDirection, palette};
7use peniko::kurbo::{self, Point, Stroke};
8use peniko::{
9 Brush, Color, ColorStop, ColorStops, Gradient, GradientKind, InterpolationAlphaSpace,
10 LinearGradientPosition,
11};
12use smallvec::SmallVec;
13use std::fmt::Debug;
14use taffy::GridTemplateComponent;
15use taffy::prelude::{auto, fr};
16
17#[cfg(not(target_arch = "wasm32"))]
18use std::time::Duration;
19#[cfg(target_arch = "wasm32")]
20use web_time::Duration;
21
22use taffy::style::{
23 AlignContent, AlignItems, BoxSizing, Display, FlexDirection, FlexWrap, Overflow, Position,
24};
25use taffy::{
26 geometry::{MinMax, Size},
27 prelude::{GridPlacement, Line},
28 style::{LengthPercentage, MaxTrackSizingFunction, MinTrackSizingFunction},
29};
30
31use crate::AnyView;
32use crate::prelude::ViewTuple;
33use crate::theme::StyleThemeExt;
34use crate::unit::{Pct, Px, PxPct, PxPctAuto};
35use crate::view::ViewTupleFlat;
36use crate::view::{IntoView, View};
37use crate::views::{ContainerExt, Decorators, Label, Stack, TooltipExt, canvas};
38
39use super::FontSize;
40
41pub enum CombineResult<T> {
42 Other, New(T), }
45
46pub trait StylePropValue: Clone + PartialEq + Debug {
47 fn debug_view(&self) -> Option<Box<dyn View>> {
48 None
49 }
50
51 fn interpolate(&self, _other: &Self, _value: f64) -> Option<Self> {
52 None
53 }
54
55 fn combine(&self, _other: &Self) -> CombineResult<Self> {
56 CombineResult::Other
57 }
58
59 fn content_hash(&self) -> u64 {
66 use std::hash::{Hash, Hasher};
67 let mut hasher = rustc_hash::FxHasher::default();
68 let debug_str = format!("{:?}", self);
70 debug_str.hash(&mut hasher);
71 hasher.finish()
72 }
73}
74
75impl StylePropValue for i32 {
76 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
77 Some((*self as f64 + (*other as f64 - *self as f64) * value).round() as i32)
78 }
79}
80impl StylePropValue for bool {}
81impl StylePropValue for f32 {
82 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
83 Some(*self * (1.0 - value as f32) + *other * value as f32)
84 }
85}
86impl StylePropValue for u16 {
87 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
88 Some((*self as f64 + (*other as f64 - *self as f64) * value).round() as u16)
89 }
90}
91impl StylePropValue for usize {
92 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
93 Some((*self as f64 + (*other as f64 - *self as f64) * value).round() as usize)
94 }
95}
96impl StylePropValue for f64 {
97 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
98 Some(*self * (1.0 - value) + *other * value)
99 }
100}
101impl StylePropValue for Overflow {}
102impl StylePropValue for Display {}
103impl StylePropValue for Position {}
104impl StylePropValue for FlexDirection {}
105impl StylePropValue for FlexWrap {}
106impl StylePropValue for AlignItems {}
107impl StylePropValue for BoxSizing {}
108impl StylePropValue for AlignContent {}
109impl StylePropValue for GridTemplateComponent<String> {}
110impl StylePropValue for MinTrackSizingFunction {}
111impl StylePropValue for MaxTrackSizingFunction {}
112impl<T: StylePropValue, M: StylePropValue> StylePropValue for MinMax<T, M> {}
113impl<T: StylePropValue> StylePropValue for Line<T> {}
114impl StylePropValue for taffy::GridAutoFlow {}
115impl StylePropValue for GridPlacement {}
116
117impl<A: smallvec::Array> StylePropValue for SmallVec<A>
118where
119 <A as smallvec::Array>::Item: StylePropValue,
120{
121 fn debug_view(&self) -> Option<Box<dyn View>> {
122 if self.is_empty() {
123 return Some(
124 Label::new("smallvec\n[]")
125 .style(|s| s.with_theme(|s, t| s.color(t.text_muted())))
126 .into_any(),
127 );
128 }
129
130 let count = self.len();
131 let is_spilled = self.spilled();
132
133 let preview = Label::derived(move || {
135 if is_spilled {
136 format!("smallvec\n[{}] (heap)", count)
137 } else {
138 format!("smallvec\n[{}] (inline)", count)
139 }
140 })
141 .style(|s| {
142 s.padding(2.0)
143 .padding_horiz(6.0)
144 .items_center()
145 .justify_center()
146 .text_align(floem_renderer::text::Align::Center)
147 .border(1.)
148 .border_radius(5.0)
149 .margin_left(6.0)
150 .with_theme(|s, t| s.color(t.text()).border_color(t.border()))
151 .with_context_opt::<FontSize, _>(|s, fs| s.font_size(fs * 0.85))
152 });
153
154 let items = self.clone();
156
157 let tooltip_view = move || {
158 Stack::vertical_from_iter(items.iter().enumerate().map(|(i, item)| {
159 let index_label = Label::new(format!("[{}]", i))
160 .style(|s| s.with_theme(|s, t| s.color(t.text_muted())));
161
162 let item_view = item.debug_view().unwrap_or_else(|| {
163 Label::new(format!("{:?}", item))
164 .style(|s| s.flex_grow(1.0))
165 .into_any()
166 });
167
168 Stack::new((index_label, item_view))
169 .style(|s| s.items_center().gap(8.0).padding(4.0))
170 }))
171 .style(|s| s.gap(4.0))
172 };
173
174 Some(
176 Stack::new((preview, tooltip_view()))
177 .style(|s| s.gap(8.0))
178 .into_any(),
179 )
180 }
181
182 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
183 self.iter().zip(other.iter()).try_fold(
184 SmallVec::with_capacity(self.len()),
185 |mut acc, (v1, v2)| {
186 if let Some(interpolated) = v1.interpolate(v2, value) {
187 acc.push(interpolated);
188 Some(acc)
189 } else {
190 None
191 }
192 },
193 )
194 }
195}
196impl StylePropValue for String {}
197impl StylePropValue for Weight {
198 fn debug_view(&self) -> Option<Box<dyn View>> {
199 let clone = *self;
200 Some(
201 format!("{clone:?}")
202 .style(move |s| s.font_weight(clone))
203 .into_any(),
204 )
205 }
206 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
207 self.0.interpolate(&other.0, value).map(Weight)
208 }
209}
210impl StylePropValue for crate::text::Style {
211 fn debug_view(&self) -> Option<Box<dyn View>> {
212 let clone = *self;
213 Some(
214 format!("{clone:?}")
215 .style(move |s| s.font_style(clone))
216 .into_any(),
217 )
218 }
219}
220impl StylePropValue for crate::text::Align {}
221impl StylePropValue for LineHeightValue {
222 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
223 match (self, other) {
224 (LineHeightValue::Normal(v1), LineHeightValue::Normal(v2)) => {
225 v1.interpolate(v2, value).map(LineHeightValue::Normal)
226 }
227 (LineHeightValue::Px(v1), LineHeightValue::Px(v2)) => {
228 v1.interpolate(v2, value).map(LineHeightValue::Px)
229 }
230 _ => None,
231 }
232 }
233}
234impl StylePropValue for Size<LengthPercentage> {}
235
236impl<T: StylePropValue> StylePropValue for Option<T> {
237 fn debug_view(&self) -> Option<Box<dyn View>> {
238 self.as_ref().and_then(|v| v.debug_view())
239 }
240
241 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
242 self.as_ref().and_then(|this| {
243 other
244 .as_ref()
245 .and_then(|other| this.interpolate(other, value).map(Some))
246 })
247 }
248}
249impl<T: StylePropValue + 'static> StylePropValue for Vec<T> {
250 fn debug_view(&self) -> Option<Box<dyn View>> {
251 if self.is_empty() {
252 return Some(
253 Label::new("[]")
254 .style(|s| s.with_theme(|s, t| s.color(t.text_muted())))
255 .into_any(),
256 );
257 }
258
259 let count = self.len();
260 let _preview = Label::derived(move || format!("[{}]", count)).style(|s| {
261 s.padding(2.0)
262 .padding_horiz(6.0)
263 .border(1.)
264 .border_radius(5.0)
265 .margin_left(6.0)
266 .with_theme(|s, t| s.color(t.text()).border_color(t.border()))
267 .with_context_opt::<FontSize, _>(|s, fs| s.font_size(fs * 0.85))
268 });
269
270 let items = self.clone();
271 let tooltip_view = move || {
272 Stack::vertical_from_iter(items.iter().enumerate().map(|(i, item)| {
273 let index_label = Label::new(format!("[{}]", i))
274 .style(|s| s.with_theme(|s, t| s.color(t.text_muted())));
275
276 let item_view = item.debug_view().unwrap_or_else(|| {
277 Label::new(format!("{:?}", item))
278 .style(|s| s.flex_grow(1.0))
279 .into_any()
280 });
281
282 Stack::new((index_label, item_view))
283 .style(|s| s.items_center().gap(8.0).padding(4.0))
284 }))
285 .style(|s| s.gap(4.0))
286 };
287
288 Some(
289 tooltip_view().into_any(),
291 )
292 }
293
294 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
295 self.iter().zip(other.iter()).try_fold(
296 Vec::with_capacity(self.len()),
297 |mut acc, (v1, v2)| {
298 if let Some(interpolated) = v1.interpolate(v2, value) {
299 acc.push(interpolated);
300 Some(acc)
301 } else {
302 None
303 }
304 },
305 )
306 }
307}
308impl StylePropValue for Px {
309 fn debug_view(&self) -> Option<Box<dyn View>> {
310 Some(Label::new(format!("{} px", self.0)).into_any())
311 }
312 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
313 self.0.interpolate(&other.0, value).map(Px)
314 }
315}
316impl StylePropValue for Pct {
317 fn debug_view(&self) -> Option<Box<dyn View>> {
318 Some(Label::new(format!("{}%", self.0)).into_any())
319 }
320 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
321 self.0.interpolate(&other.0, value).map(Pct)
322 }
323}
324impl StylePropValue for PxPctAuto {
325 fn debug_view(&self) -> Option<Box<dyn View>> {
326 let label = match self {
327 Self::Px(v) => format!("{v} px"),
328 Self::Pct(v) => format!("{v}%"),
329 Self::Auto => "auto".to_string(),
330 };
331 Some(Label::new(label).into_any())
332 }
333 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
334 match (self, other) {
335 (Self::Px(v1), Self::Px(v2)) => Some(Self::Px(v1 + (v2 - v1) * value)),
336 (Self::Pct(v1), Self::Pct(v2)) => Some(Self::Pct(v1 + (v2 - v1) * value)),
337 (Self::Auto, Self::Auto) => Some(Self::Auto),
338 _ => None,
340 }
341 }
342}
343impl StylePropValue for PxPct {
344 fn debug_view(&self) -> Option<Box<dyn View>> {
345 let label = match self {
346 Self::Px(v) => format!("{v} px"),
347 Self::Pct(v) => format!("{v}%"),
348 };
349 Some(Label::new(label).into_any())
350 }
351
352 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
353 match (self, other) {
354 (Self::Px(v1), Self::Px(v2)) => Some(Self::Px(v1 + (v2 - v1) * value)),
355 (Self::Pct(v1), Self::Pct(v2)) => Some(Self::Pct(v1 + (v2 - v1) * value)),
356 _ => None,
358 }
359 }
360}
361
362pub(crate) fn views(views: impl ViewTuple) -> Vec<AnyView> {
363 views.into_views()
364}
365
366impl StylePropValue for Color {
367 fn debug_view(&self) -> Option<Box<dyn View>> {
368 let color = *self;
369 let swatch = ()
370 .style(move |s| {
371 s.background(color)
372 .width(22.0)
373 .height(14.0)
374 .border(1.)
375 .border_color(palette::css::WHITE.with_alpha(0.5))
376 .border_radius(5.0)
377 })
378 .container()
379 .style(|s| {
380 s.border(1.)
381 .border_color(palette::css::BLACK.with_alpha(0.5))
382 .border_radius(5.0)
383 });
384
385 let tooltip_view = move || {
386 let c = color.to_rgba8();
388 let (r, g, b, a) = (c.r, c.g, c.b, c.a);
389
390 let hex = if a == 255 {
392 format!("#{:02X}{:02X}{:02X}", r, g, b)
393 } else {
394 format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a)
395 };
396
397 let rgba_str = format!("rgba({}, {}, {}, {:.3})", r, g, b, a as f32 / 255.0);
399
400 let alpha_str = format!(
402 "{:.1}% ({:.3})",
403 (a as f32 / 255.0) * 100.0,
404 a as f32 / 255.0
405 );
406
407 let components = color.components;
408 let color_space_str = format!("{:?}", color.cs);
409
410 let hex = views((
411 "Hex:".style(|s| s.font_bold().min_width(80.0).justify_end()),
412 Label::derived(move || hex.clone()),
413 ));
414 let rgba = views((
415 "RGBA:".style(|s| s.font_bold().min_width(80.0).justify_end()),
416 Label::derived(move || rgba_str.clone()),
417 ));
418 let components = views((
419 "Components:".style(|s| s.font_bold().min_width(80.0).justify_end()),
420 (
421 Label::derived(move || format!("[0]: {:.3}", components[0])),
422 Label::derived(move || format!("[1]: {:.3}", components[1])),
423 Label::derived(move || format!("[2]: {:.3}", components[2])),
424 Label::derived(move || format!("[3]: {:.3}", components[3])),
425 )
426 .v_stack()
427 .style(|s| s.gap(2.0)),
428 ));
429 let color_space = views((
430 "Color Space:".style(|s| s.font_bold().min_width(80.0).justify_end()),
431 Label::derived(move || color_space_str.clone()),
432 ));
433 let alpha = views((
434 "Alpha:".style(|s| s.font_bold().min_width(80.0).justify_end()),
435 Label::derived(move || alpha_str.clone()),
436 ));
437 (hex, rgba, components, color_space, alpha)
438 .flatten()
439 .style(|s| {
440 s.grid()
441 .grid_template_columns([auto(), fr(1.)])
442 .justify_center()
443 .items_center()
444 .row_gap(20)
445 .col_gap(10)
446 .padding(30)
447 })
448 };
449
450 Some(
451 swatch
452 .tooltip(tooltip_view)
453 .style(|s| s.items_center())
454 .into_any(),
455 )
456 }
457
458 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
459 Some(self.lerp(*other, value as f32, HueDirection::default()))
460 }
461}
462
463impl StylePropValue for Gradient {
464 fn debug_view(&self) -> Option<Box<dyn View>> {
465 let box_width = 22.;
466 let box_height = 14.;
467 let mut grad = self.clone();
468 grad.kind = match grad.kind {
469 GradientKind::Linear(LinearGradientPosition { start, end }) => {
470 let dx = end.x - start.x;
471 let dy = end.y - start.y;
472
473 let scale_x = box_width / dx.abs();
474 let scale_y = box_height / dy.abs();
475 let scale = scale_x.min(scale_y);
476
477 let new_dx = dx * scale;
478 let new_dy = dy * scale;
479
480 let new_start = Point {
481 x: if dx > 0.0 { 0.0 } else { box_width },
482 y: if dy > 0.0 { 0.0 } else { box_height },
483 };
484
485 let new_end = Point {
486 x: new_start.x + new_dx,
487 y: new_start.y + new_dy,
488 };
489
490 GradientKind::Linear(LinearGradientPosition {
491 start: new_start,
492 end: new_end,
493 })
494 }
495 _ => grad.kind,
496 };
497 let color = ().style(move |s| {
498 s.background(grad.clone())
499 .width(box_width)
500 .height(box_height)
501 .border(1.)
502 .border_color(palette::css::WHITE.with_alpha(0.5))
503 .border_radius(5.0)
504 });
505 let color = color.container().style(|s| {
506 s.border(1.)
507 .border_color(palette::css::BLACK.with_alpha(0.5))
508 .border_radius(5.0)
509 .margin_left(6.0)
510 });
511 Some(
512 Stack::new((Label::new(format!("{self:?}")), color))
513 .style(|s| s.items_center())
514 .into_any(),
515 )
516 }
517
518 fn interpolate(&self, _other: &Self, _value: f64) -> Option<Self> {
519 None
520 }
521}
522
523#[derive(Clone, Debug, Default)]
525pub struct StrokeWrap(pub Stroke);
526impl StrokeWrap {
527 pub fn new(width: f64) -> Self {
528 Self(Stroke::new(width))
529 }
530}
531impl PartialEq for StrokeWrap {
532 fn eq(&self, other: &Self) -> bool {
533 let self_stroke = &self.0;
534 let other_stroke = &other.0;
535
536 self_stroke.width == other_stroke.width
537 && self_stroke.join == other_stroke.join
538 && self_stroke.miter_limit == other_stroke.miter_limit
539 && self_stroke.start_cap == other_stroke.start_cap
540 && self_stroke.end_cap == other_stroke.end_cap
541 && self_stroke.dash_pattern == other_stroke.dash_pattern
542 && self_stroke.dash_offset == other_stroke.dash_offset
543 }
544}
545impl From<Stroke> for StrokeWrap {
546 fn from(value: Stroke) -> Self {
547 Self(value)
548 }
549}
550impl From<f32> for StrokeWrap {
551 fn from(value: f32) -> Self {
552 Self(Stroke::new(value.into()))
553 }
554}
555impl From<f64> for StrokeWrap {
556 fn from(value: f64) -> Self {
557 Self(Stroke::new(value))
558 }
559}
560impl From<i32> for StrokeWrap {
561 fn from(value: i32) -> Self {
562 Self(Stroke::new(value.into()))
563 }
564}
565impl StylePropValue for StrokeWrap {
566 fn debug_view(&self) -> Option<Box<dyn View>> {
567 let stroke = self.0.clone();
568 let clone = stroke.clone();
569
570 let color = RwSignal::new(palette::css::RED);
571
572 let preview = canvas(move |cx, size| {
574 cx.stroke(
575 &kurbo::Line::new(
576 Point::new(0., size.height / 2.),
577 Point::new(size.width, size.height / 2.),
578 ),
579 color.get(),
580 &clone,
581 );
582 })
583 .style(move |s| s.width(80.0).height(20.0))
584 .container()
585 .style(move |s| {
586 s.with_theme(move |s, t| {
587 color.set(t.primary());
588 s.border_color(t.border())
589 })
590 .padding(4.0)
591 });
592
593 let tooltip_view = move || {
594 let stroke = stroke.clone();
595
596 let width_row = views((
597 "Width:".style(|s| s.font_bold().min_width(100.0).justify_end()),
598 Label::derived(move || format!("{:.1}px", stroke.width)),
599 ));
600
601 let join_row = views((
602 "Join:".style(|s| s.font_bold().min_width(100.0).justify_end()),
603 Label::derived(move || format!("{:?}", stroke.join)),
604 ));
605
606 let miter_row = views((
607 "Miter Limit:".style(|s| s.font_bold().min_width(100.0).justify_end()),
608 Label::derived(move || format!("{:.2}", stroke.miter_limit)),
609 ));
610
611 let start_cap_row = views((
612 "Start Cap:".style(|s| s.font_bold().min_width(100.0).justify_end()),
613 Label::derived(move || format!("{:?}", stroke.start_cap)),
614 ));
615
616 let end_cap_row = views((
617 "End Cap:".style(|s| s.font_bold().min_width(100.0).justify_end()),
618 Label::derived(move || format!("{:?}", stroke.end_cap)),
619 ));
620
621 let pattern_clone = stroke.dash_pattern.clone();
622
623 let dash_pattern_row = views((
624 "Dash Pattern:".style(|s| s.font_bold().min_width(100.0).justify_end()),
625 Label::derived(move || {
626 if pattern_clone.is_empty() {
627 "Solid".to_string()
628 } else {
629 format!("{:?}", pattern_clone.as_slice())
630 }
631 }),
632 ));
633
634 let dash_offset_row = if !stroke.dash_pattern.is_empty() {
635 Some(views((
636 "Dash Offset:".style(|s| s.font_bold().min_width(100.0).justify_end()),
637 Label::derived(move || format!("{:.1}", stroke.dash_offset)),
638 )))
639 } else {
640 None
641 };
642
643 let mut rows = vec![
644 width_row.into_any(),
645 join_row.into_any(),
646 miter_row.into_any(),
647 start_cap_row.into_any(),
648 end_cap_row.into_any(),
649 dash_pattern_row.into_any(),
650 ];
651
652 if let Some(offset_row) = dash_offset_row {
653 rows.push(offset_row.into_any());
654 }
655
656 Stack::vertical_from_iter(rows).style(|s| {
657 s.grid()
658 .grid_template_columns([auto(), fr(1.)])
659 .justify_center()
660 .items_center()
661 .row_gap(12)
662 .col_gap(10)
663 .padding(20)
664 })
665 };
666
667 Some(
668 preview
669 .tooltip(tooltip_view)
670 .style(|s| s.items_center())
671 .into_any(),
672 )
673 }
674}
675impl StylePropValue for Brush {
676 fn debug_view(&self) -> Option<Box<dyn View>> {
677 match self {
678 Brush::Solid(color) => color.debug_view(),
679 Brush::Gradient(grad) => grad.debug_view(),
680 Brush::Image(_) => None,
681 }
682 }
683
684 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
685 match (self, other) {
686 (Brush::Solid(color), Brush::Solid(other)) => Some(Self::Solid(color.lerp(
687 *other,
688 value as f32,
689 HueDirection::default(),
690 ))),
691 (Brush::Gradient(gradient), Brush::Solid(solid)) => {
692 let interpolated_stops: Vec<ColorStop> = gradient
693 .stops
694 .iter()
695 .map(|stop| {
696 let interpolated_color = stop.color.to_alpha_color().lerp(
697 *solid,
698 value as f32,
699 HueDirection::default(),
700 );
701 ColorStop::from((stop.offset, interpolated_color))
702 })
703 .collect();
704 Some(Brush::Gradient(Gradient {
705 kind: gradient.kind,
706 extend: gradient.extend,
707 interpolation_cs: gradient.interpolation_cs,
708 hue_direction: gradient.hue_direction,
709 stops: ColorStops::from(&*interpolated_stops),
710 interpolation_alpha_space: InterpolationAlphaSpace::Premultiplied,
711 }))
712 }
713 (Brush::Solid(solid), Brush::Gradient(gradient)) => {
714 let interpolated_stops: Vec<ColorStop> = gradient
715 .stops
716 .iter()
717 .map(|stop| {
718 let interpolated_color = solid.lerp(
719 stop.color.to_alpha_color(),
720 value as f32,
721 HueDirection::default(),
722 );
723 ColorStop::from((stop.offset, interpolated_color))
724 })
725 .collect();
726 Some(Brush::Gradient(Gradient {
727 kind: gradient.kind,
728 extend: gradient.extend,
729 interpolation_cs: gradient.interpolation_cs,
730 hue_direction: gradient.hue_direction,
731 stops: ColorStops::from(&*interpolated_stops),
732 interpolation_alpha_space: InterpolationAlphaSpace::Premultiplied,
733 }))
734 }
735
736 (Brush::Gradient(gradient1), Brush::Gradient(gradient2)) => {
737 gradient1.interpolate(gradient2, value).map(Brush::Gradient)
738 }
739 _ => None,
740 }
741 }
742}
743impl StylePropValue for Duration {
744 fn debug_view(&self) -> Option<Box<dyn View>> {
745 None
746 }
747
748 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
749 self.as_secs_f64()
750 .interpolate(&other.as_secs_f64(), value)
751 .map(Duration::from_secs_f64)
752 }
753}
754
755impl StylePropValue for super::Angle {
756 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
757 let self_rad = self.to_radians();
758 let other_rad = other.to_radians();
759 self_rad
760 .interpolate(&other_rad, value)
761 .map(super::Angle::Rad)
762 }
763}
764
765impl StylePropValue for super::AnchorAbout {
766 fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
767 Some(Self {
768 x: self.x + (other.x - self.x) * value,
769 y: self.y + (other.y - self.y) * value,
770 })
771 }
772}
773
774#[derive(Debug, Clone, Copy, PartialEq, Eq)]
779pub enum StyleMapValue<T> {
780 Animated(T),
782 Val(T),
784 Unset,
786}
787
788impl<T> StyleMapValue<T> {
789 pub(crate) fn as_ref(&self) -> Option<&T> {
790 match self {
791 Self::Val(v) => Some(v),
792 Self::Animated(v) => Some(v),
793 Self::Unset => None,
794 }
795 }
796}
797
798#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
803pub enum StyleValue<T> {
804 Animated(T),
806 Val(T),
808 Unset,
810 #[default]
813 Base,
814}
815
816impl<T> StyleValue<T> {
817 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> StyleValue<U> {
818 match self {
819 Self::Val(x) => StyleValue::Val(f(x)),
820 Self::Animated(x) => StyleValue::Animated(f(x)),
821 Self::Unset => StyleValue::Unset,
822 Self::Base => StyleValue::Base,
823 }
824 }
825
826 pub fn unwrap_or(self, default: T) -> T {
827 match self {
828 Self::Val(x) => x,
829 Self::Animated(x) => x,
830 Self::Unset => default,
831 Self::Base => default,
832 }
833 }
834
835 pub fn unwrap_or_else(self, f: impl FnOnce() -> T) -> T {
836 match self {
837 Self::Val(x) => x,
838 Self::Animated(x) => x,
839 Self::Unset => f(),
840 Self::Base => f(),
841 }
842 }
843
844 pub fn as_mut(&mut self) -> Option<&mut T> {
845 match self {
846 Self::Val(x) => Some(x),
847 Self::Animated(x) => Some(x),
848 Self::Unset => None,
849 Self::Base => None,
850 }
851 }
852}
853
854impl<T> From<T> for StyleValue<T> {
855 fn from(x: T) -> Self {
856 Self::Val(x)
857 }
858}