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 ContainerWithContentExt for Skeleton {}
101
102impl Default for Skeleton {
103 fn default() -> Self {
104 Self::new(false)
105 }
106}
107
108impl Skeleton {
109 pub fn new(loading: bool) -> Self {
110 Self {
111 theme: None,
112 loading,
113 elements: Vec::new(),
114 layout: LayoutData::default(),
115 key: DiffKey::None,
116 }
117 }
118
119 pub fn theme(mut self, theme: SkeletonThemePartial) -> Self {
121 self.theme = Some(theme);
122 self
123 }
124}
125
126impl Component for Skeleton {
127 fn render(&self) -> impl IntoElement {
128 let loading = self.loading;
129 let elements = self.elements.clone();
130
131 let theme = get_theme!(&self.theme, SkeletonThemePreference, "skeleton");
132
133 let animation = use_animation_with_dependencies(&theme, |conf, theme| {
134 conf.on_creation(OnCreation::Run);
135 conf.on_change(OnChange::Rerun);
136 match theme.animation {
137 SkeletonAnimation::Pulse => {
138 conf.on_finish(OnFinish::reverse());
139 AnimNum::new(0.4, 1.0).duration(theme.duration)
140 }
141 SkeletonAnimation::Shimmer => {
142 conf.on_finish(OnFinish::restart());
143 AnimNum::new(theme.shimmer_from, theme.shimmer_to).duration(theme.duration)
144 }
145 }
146 });
147
148 let value = animation.get().value();
149 let is_pulse = theme.animation == SkeletonAnimation::Pulse;
150
151 rect()
152 .layout(self.layout.clone())
153 .maybe(loading, |el| {
154 el.background(theme.background)
155 .corner_radius(theme.corner_radius)
156 .overflow(Overflow::Clip)
157 .maybe(is_pulse, |el| el.opacity(value))
158 .maybe(!is_pulse, |el| {
159 el.child(
160 rect()
161 .position(Position::new_absolute().left(value))
162 .width(Size::px(theme.shimmer_width))
163 .height(Size::fill())
164 .background(
165 LinearGradient::new()
166 .angle(-90.)
167 .stop((theme.background, 0.))
168 .stop((theme.shimmer_color, 50.))
169 .stop((theme.background, 100.)),
170 ),
171 )
172 })
173 })
174 .maybe(!loading, |el| el.children(elements))
175 }
176
177 fn render_key(&self) -> DiffKey {
178 self.key.clone().or(self.default_key())
179 }
180}