freya_components/
skeleton.rs1use std::time::Duration;
2
3use freya_animation::prelude::*;
4use freya_core::prelude::*;
5use torin::{
6 position::Position,
7 size::Size,
8};
9
10use crate::{
11 define_theme,
12 get_theme,
13 theming::{
14 component_themes::ColorsSheet,
15 macros::{
16 Preference,
17 ResolvablePreference,
18 },
19 },
20};
21
22#[derive(PartialEq, Clone, Copy, Default, Debug)]
24pub enum SkeletonAnimation {
25 #[default]
26 Pulse,
27 Shimmer,
28}
29
30impl ResolvablePreference<SkeletonAnimation> for Preference<SkeletonAnimation> {
31 fn resolve(&self, _: &ColorsSheet) -> SkeletonAnimation {
32 match self {
33 Self::Reference(_) => panic!("Only Colors support references."),
34 Self::Specific(v) => *v,
35 }
36 }
37}
38
39define_theme! {
40 %[component]
41 pub Skeleton {
42 %[fields]
43 background: Color,
44 shimmer_color: Color,
45 duration: Duration,
46 animation: SkeletonAnimation,
47 corner_radius: CornerRadius,
48 shimmer_from: f32,
49 shimmer_to: f32,
50 shimmer_width: f32,
51 }
52}
53
54#[derive(PartialEq)]
72pub struct Skeleton {
73 pub(crate) theme: Option<SkeletonThemePartial>,
74 loading: bool,
75 elements: Vec<Element>,
76 layout: LayoutData,
77 key: DiffKey,
78}
79
80impl KeyExt for Skeleton {
81 fn write_key(&mut self) -> &mut DiffKey {
82 &mut self.key
83 }
84}
85
86impl ChildrenExt for Skeleton {
87 fn get_children(&mut self) -> &mut Vec<Element> {
88 &mut self.elements
89 }
90}
91
92impl LayoutExt for Skeleton {
93 fn get_layout(&mut self) -> &mut LayoutData {
94 &mut self.layout
95 }
96}
97
98impl ContainerExt for Skeleton {}
99
100impl Default for Skeleton {
101 fn default() -> Self {
102 Self::new(false)
103 }
104}
105
106impl Skeleton {
107 pub fn new(loading: bool) -> Self {
108 Self {
109 theme: None,
110 loading,
111 elements: Vec::new(),
112 layout: LayoutData::default(),
113 key: DiffKey::None,
114 }
115 }
116
117 pub fn theme(mut self, theme: SkeletonThemePartial) -> Self {
119 self.theme = Some(theme);
120 self
121 }
122}
123
124impl Component for Skeleton {
125 fn render(&self) -> impl IntoElement {
126 let loading = self.loading;
127 let elements = self.elements.clone();
128
129 let theme = get_theme!(&self.theme, SkeletonThemePreference, "skeleton");
130
131 let animation = use_animation_with_dependencies(&theme, |conf, theme| {
132 conf.on_creation(OnCreation::Run);
133 conf.on_change(OnChange::Rerun);
134 match theme.animation {
135 SkeletonAnimation::Pulse => {
136 conf.on_finish(OnFinish::reverse());
137 AnimNum::new(0.4, 1.0).duration(theme.duration)
138 }
139 SkeletonAnimation::Shimmer => {
140 conf.on_finish(OnFinish::restart());
141 AnimNum::new(theme.shimmer_from, theme.shimmer_to).duration(theme.duration)
142 }
143 }
144 });
145
146 let value = animation.get().value();
147 let is_pulse = theme.animation == SkeletonAnimation::Pulse;
148
149 rect()
150 .layout(self.layout.clone())
151 .maybe(loading, |el| {
152 el.background(theme.background)
153 .corner_radius(theme.corner_radius)
154 .overflow(Overflow::Clip)
155 .maybe(is_pulse, |el| el.opacity(value))
156 .maybe(!is_pulse, |el| {
157 el.child(
158 rect()
159 .position(Position::new_absolute().left(value))
160 .width(Size::px(theme.shimmer_width))
161 .height(Size::fill())
162 .background_linear_gradient(
163 LinearGradient::new()
164 .angle(-90.)
165 .stop((theme.background, 0.))
166 .stop((theme.shimmer_color, 50.))
167 .stop((theme.background, 100.)),
168 ),
169 )
170 })
171 })
172 .maybe(!loading, |el| el.children(elements))
173 }
174
175 fn render_key(&self) -> DiffKey {
176 self.key.clone().or(self.default_key())
177 }
178}