animated_ui/
animated_ui.rs1use bevy::{
4 animation::{
5 animated_field, AnimationEntityMut, AnimationEvaluationError, AnimationTarget,
6 AnimationTargetId,
7 },
8 prelude::*,
9};
10use std::any::TypeId;
11
12struct AnimationInfo {
14 target_name: Name,
16 target_id: AnimationTargetId,
18 graph: Handle<AnimationGraph>,
20 node_index: AnimationNodeIndex,
22}
23
24fn main() {
26 App::new()
27 .add_plugins(DefaultPlugins)
28 .add_systems(Startup, setup)
31 .run();
32}
33
34impl AnimationInfo {
35 fn create(
37 animation_graphs: &mut Assets<AnimationGraph>,
38 animation_clips: &mut Assets<AnimationClip>,
39 ) -> AnimationInfo {
40 let animation_target_name = Name::new("Text");
42 let animation_target_id = AnimationTargetId::from_name(&animation_target_name);
43
44 let mut animation_clip = AnimationClip::default();
46
47 animation_clip.add_curve_to_target(
49 animation_target_id,
50 AnimatableCurve::new(
51 animated_field!(TextFont::font_size),
52 AnimatableKeyframeCurve::new(
53 [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
54 .into_iter()
55 .zip([24.0, 80.0, 24.0, 80.0, 24.0, 80.0, 24.0]),
56 )
57 .expect(
58 "should be able to build translation curve because we pass in valid samples",
59 ),
60 ),
61 );
62
63 animation_clip.add_curve_to_target(
69 animation_target_id,
70 AnimatableCurve::new(
71 TextColorProperty,
72 AnimatableKeyframeCurve::new([0.0, 1.0, 2.0, 3.0].into_iter().zip([
73 Srgba::RED,
74 Srgba::GREEN,
75 Srgba::BLUE,
76 Srgba::RED,
77 ]))
78 .expect(
79 "should be able to build translation curve because we pass in valid samples",
80 ),
81 ),
82 );
83
84 let animation_clip_handle = animation_clips.add(animation_clip);
86
87 let (animation_graph, animation_node_index) =
89 AnimationGraph::from_clip(animation_clip_handle);
90 let animation_graph_handle = animation_graphs.add(animation_graph);
91
92 AnimationInfo {
93 target_name: animation_target_name,
94 target_id: animation_target_id,
95 graph: animation_graph_handle,
96 node_index: animation_node_index,
97 }
98 }
99}
100
101fn setup(
103 mut commands: Commands,
104 asset_server: Res<AssetServer>,
105 mut animation_graphs: ResMut<Assets<AnimationGraph>>,
106 mut animation_clips: ResMut<Assets<AnimationClip>>,
107) {
108 let AnimationInfo {
110 target_name: animation_target_name,
111 target_id: animation_target_id,
112 graph: animation_graph,
113 node_index: animation_node_index,
114 } = AnimationInfo::create(&mut animation_graphs, &mut animation_clips);
115
116 let mut animation_player = AnimationPlayer::default();
118 animation_player.play(animation_node_index).repeat();
119
120 commands.spawn(Camera2d);
122
123 let mut entity = commands.spawn((
127 Node {
129 position_type: PositionType::Absolute,
130 top: px(0),
131 left: px(0),
132 right: px(0),
133 bottom: px(0),
134 justify_content: JustifyContent::Center,
135 align_items: AlignItems::Center,
136 ..default()
137 },
138 animation_player,
139 AnimationGraphHandle(animation_graph),
140 ));
141
142 let player = entity.id();
143 entity.insert(children![(
144 Text::new("Bevy"),
145 TextFont {
146 font: asset_server.load("fonts/FiraSans-Bold.ttf"),
147 font_size: 24.0,
148 ..default()
149 },
150 TextColor(Color::Srgba(Srgba::RED)),
151 TextLayout::new_with_justify(Justify::Center),
152 AnimationTarget {
153 id: animation_target_id,
154 player,
155 },
156 animation_target_name,
157 )]);
158}
159
160#[derive(Clone)]
164struct TextColorProperty;
165
166impl AnimatableProperty for TextColorProperty {
167 type Property = Srgba;
168
169 fn evaluator_id(&self) -> EvaluatorId<'_> {
170 EvaluatorId::Type(TypeId::of::<Self>())
171 }
172
173 fn get_mut<'a>(
174 &self,
175 entity: &'a mut AnimationEntityMut,
176 ) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
177 let text_color = entity
178 .get_mut::<TextColor>()
179 .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
180 TextColor,
181 >(
182 )))?
183 .into_inner();
184 match text_color.0 {
185 Color::Srgba(ref mut color) => Ok(color),
186 _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
187 Srgba,
188 >(
189 ))),
190 }
191 }
192}