Skip to main content

fyrox_ui/
animation.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Animation player is a node that contains multiple animations. It updates and plays all the animations.
22//! See [`AnimationPlayer`] docs for more info.
23
24use crate::message::MessageData;
25use crate::{
26    core::{
27        log::{Log, MessageKind},
28        pool::Handle,
29        reflect::prelude::*,
30        type_traits::prelude::*,
31        variable::InheritableVariable,
32        visitor::prelude::*,
33    },
34    define_widget_deref,
35    generic_animation::value::{BoundValueCollection, TrackValue, ValueBinding},
36    message::UiMessage,
37    widget::{Widget, WidgetBuilder},
38    BuildContext, Control, UiNode, UserInterface,
39};
40use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
41
42#[derive(Debug, Clone, PartialEq)]
43pub enum AnimationPlayerMessage {
44    EnableAnimation { animation: String, enabled: bool },
45    RewindAnimation { animation: String },
46    TimePosition { animation: String, time: f32 },
47}
48impl MessageData for AnimationPlayerMessage {}
49
50/// UI-specific animation.
51pub type Animation = crate::generic_animation::Animation<Handle<UiNode>>;
52/// UI-specific animation track.
53pub type Track = crate::generic_animation::track::Track;
54/// UI-specific animation container.
55pub type AnimationContainer = crate::generic_animation::AnimationContainer<Handle<UiNode>>;
56/// UI-specific animation pose.
57pub type AnimationPose = crate::generic_animation::AnimationPose<Handle<UiNode>>;
58/// UI-specific animation node pose.
59pub type NodePose = crate::generic_animation::NodePose<Handle<UiNode>>;
60
61/// Standard prelude for animations, that contains all the most commonly used types and traits.
62pub mod prelude {
63    pub use super::{
64        Animation, AnimationContainer, AnimationContainerExt, AnimationPlayer,
65        AnimationPlayerBuilder, AnimationPose, AnimationPoseExt, BoundValueCollectionExt, NodePose,
66        Track,
67    };
68    pub use crate::generic_animation::{
69        container::{TrackDataContainer, TrackValueKind},
70        signal::AnimationSignal,
71        value::{BoundValueCollection, TrackValue, ValueBinding, ValueType},
72        AnimationEvent,
73    };
74}
75
76/// Extension trait for [`AnimationContainer`].
77pub trait AnimationContainerExt {
78    /// Updates all animations in the container and applies their poses to respective nodes. This method is intended to
79    /// be used only by the internals of the engine!
80    fn update_animations(&mut self, nodes: &mut UserInterface, dt: f32);
81}
82
83impl AnimationContainerExt for AnimationContainer {
84    fn update_animations(&mut self, ui: &mut UserInterface, dt: f32) {
85        for animation in self.iter_mut().filter(|anim| anim.is_enabled()) {
86            animation.tick(dt);
87            animation.pose().apply(ui);
88        }
89    }
90}
91
92/// Extension trait for [`AnimationPose`].
93pub trait AnimationPoseExt {
94    /// Tries to set each value to the property from the animation pose to the respective widgets.
95    fn apply(&self, ui: &mut UserInterface);
96}
97
98impl AnimationPoseExt for AnimationPose {
99    fn apply(&self, ui: &mut UserInterface) {
100        for (node, local_pose) in self.poses() {
101            if node.is_none() {
102                Log::writeln(MessageKind::Error, "Invalid node handle found for animation pose, most likely it means that animation retargeting failed!");
103            } else if let Ok(node) = ui.try_get_node_mut(*node) {
104                node.invalidate_layout();
105
106                local_pose.values.apply(node);
107            }
108        }
109    }
110}
111
112/// Extension trait for [`BoundValueCollection`].
113pub trait BoundValueCollectionExt {
114    /// Tries to set each value from the collection to the respective property (by binding) of the
115    /// given widget.
116    fn apply(&self, node_ref: &mut UiNode);
117}
118
119impl BoundValueCollectionExt for BoundValueCollection {
120    fn apply(&self, node_ref: &mut UiNode) {
121        for bound_value in self.values.iter() {
122            match bound_value.binding {
123                ValueBinding::Position => {
124                    if let TrackValue::Vector2(v) = bound_value.value {
125                        node_ref.set_desired_local_position(v);
126                    } else {
127                        Log::err(
128                            "Unable to apply position, because underlying type is not Vector2!",
129                        )
130                    }
131                }
132                ValueBinding::Scale => Log::warn("Implement me!"),
133                ValueBinding::Rotation => Log::warn("Implement me!"),
134                ValueBinding::Property {
135                    name: ref property_name,
136                    value_type,
137                } => bound_value.apply_to_object(node_ref, property_name, value_type),
138            }
139        }
140    }
141}
142
143/// Animation player is a node that contains multiple animations. It updates and plays all the animations.
144/// The node could be a source of animations for animation blending state machines. To learn more about
145/// animations, see [`Animation`] docs.
146#[derive(Visit, Reflect, Clone, Debug, ComponentProvider)]
147#[reflect(derived_type = "UiNode")]
148pub struct AnimationPlayer {
149    widget: Widget,
150    #[component(include)]
151    pub(crate) animations: InheritableVariable<AnimationContainer>,
152    #[component(include)]
153    auto_apply: bool,
154}
155
156impl ConstructorProvider<UiNode, UserInterface> for AnimationPlayer {
157    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
158        GraphNodeConstructor::new::<Self>()
159            .with_variant("Animation Player", |ui| {
160                AnimationPlayerBuilder::new(WidgetBuilder::new().with_name("Animation Player"))
161                    .build(&mut ui.build_ctx())
162                    .to_base()
163                    .into()
164            })
165            .with_group("Animation")
166    }
167}
168
169impl Default for AnimationPlayer {
170    fn default() -> Self {
171        Self {
172            widget: Default::default(),
173            animations: Default::default(),
174            auto_apply: true,
175        }
176    }
177}
178
179impl AnimationPlayer {
180    /// Enables or disables automatic animations update and pose applying. If auto applying is enabled,
181    /// then every animation in this node is updated first, and then their output pose could be applied
182    /// to the graph, so the animation takes effect. Automatic applying is useful when you need your
183    /// animations to be applied immediately to the graph, but in some cases (if you're using animation
184    /// blending state machines, for example), this functionality is undesired.
185    ///
186    /// Animation blending machines hijacks control over the animation container and updates only
187    /// active animations, instead of all available. This is much better for performance than updating
188    /// all at once.
189    pub fn set_auto_apply(&mut self, auto_apply: bool) {
190        self.auto_apply = auto_apply;
191    }
192
193    /// Returns `true` if the node is automatically applying output poses of animations to the graph, `false` -
194    /// otherwise.
195    pub fn is_auto_apply(&self) -> bool {
196        self.auto_apply
197    }
198
199    /// Returns a reference to internal animations' container.
200    pub fn animations(&self) -> &InheritableVariable<AnimationContainer> {
201        &self.animations
202    }
203
204    /// Returns a reference to internal animations' container. Keep in mind that mutable access to [`InheritableVariable`]
205    /// may have side effects if used inappropriately. Check docs for [`InheritableVariable`] for more info.
206    pub fn animations_mut(&mut self) -> &mut InheritableVariable<AnimationContainer> {
207        &mut self.animations
208    }
209
210    /// Sets new animations container of the animation player.
211    pub fn set_animations(&mut self, animations: AnimationContainer) {
212        self.animations.set_value_and_mark_modified(animations);
213    }
214
215    fn find_animation(&mut self, name: &str) -> Option<&mut Animation> {
216        self.animations.find_by_name_mut(name).map(|(_, a)| a)
217    }
218}
219
220impl TypeUuidProvider for AnimationPlayer {
221    fn type_uuid() -> Uuid {
222        uuid!("44d1c94e-354f-4f9a-b918-9d31c28aa16a")
223    }
224}
225
226define_widget_deref!(AnimationPlayer);
227
228impl Control for AnimationPlayer {
229    fn update(&mut self, dt: f32, ui: &mut UserInterface) {
230        if self.auto_apply {
231            self.animations
232                .get_value_mut_silent()
233                .update_animations(ui, dt);
234        }
235    }
236
237    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
238        self.widget.handle_routed_message(ui, message);
239
240        if let Some(msg) = message.data::<AnimationPlayerMessage>() {
241            match msg {
242                AnimationPlayerMessage::EnableAnimation { animation, enabled } => {
243                    if let Some(animation) = self.find_animation(animation) {
244                        animation.set_enabled(*enabled);
245                    }
246                }
247                AnimationPlayerMessage::RewindAnimation { animation } => {
248                    if let Some(animation) = self.find_animation(animation) {
249                        animation.rewind();
250                    }
251                }
252                AnimationPlayerMessage::TimePosition { animation, time } => {
253                    if let Some(animation) = self.find_animation(animation) {
254                        animation.set_time_position(*time);
255                    }
256                }
257            }
258        }
259    }
260}
261
262/// A builder for [`AnimationPlayer`] node.
263pub struct AnimationPlayerBuilder {
264    widget_builder: WidgetBuilder,
265    animations: AnimationContainer,
266    auto_apply: bool,
267}
268
269impl AnimationPlayerBuilder {
270    /// Creates new builder instance.
271    pub fn new(widget_builder: WidgetBuilder) -> Self {
272        Self {
273            widget_builder,
274            animations: AnimationContainer::new(),
275            auto_apply: true,
276        }
277    }
278
279    /// Sets a container with desired animations.
280    pub fn with_animations(mut self, animations: AnimationContainer) -> Self {
281        self.animations = animations;
282        self
283    }
284
285    /// Enables or disables automatic pose applying. See [`AnimationPlayer::set_auto_apply`] docs for more info.
286    pub fn with_auto_apply(mut self, auto_apply: bool) -> Self {
287        self.auto_apply = auto_apply;
288        self
289    }
290
291    /// Creates an instance of [`AnimationPlayer`] node.
292    pub fn build_animation_player(self, ctx: &BuildContext) -> AnimationPlayer {
293        AnimationPlayer {
294            widget: self.widget_builder.with_need_update(true).build(ctx),
295            animations: self.animations.into(),
296            auto_apply: self.auto_apply,
297        }
298    }
299
300    /// Creates an instance of [`AnimationPlayer`] node.
301    pub fn build_node(self, ctx: &BuildContext) -> UiNode {
302        UiNode::new(self.build_animation_player(ctx))
303    }
304
305    /// Creates an instance of [`AnimationPlayer`] node and adds it to the given user interface.
306    pub fn build(self, ctx: &mut BuildContext) -> Handle<AnimationPlayer> {
307        ctx.add(self.build_animation_player(ctx))
308    }
309}
310
311#[cfg(test)]
312mod test {
313    use crate::animation::AnimationPlayerBuilder;
314    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
315
316    #[test]
317    fn test_deletion() {
318        test_widget_deletion(|ctx| AnimationPlayerBuilder::new(WidgetBuilder::new()).build(ctx));
319    }
320}