diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03ad1af..fdd8d35 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ Removals
### MLEM.Ui
Additions
- Added AutoInlineCenter and AutoInlineBottom anchors
+- Added UiAnimation system
Improvements
- Increased Element area calculation recursion limit to 64
diff --git a/Demos/UiDemo.cs b/Demos/UiDemo.cs
index 5fb7503..a3381c0 100644
--- a/Demos/UiDemo.cs
+++ b/Demos/UiDemo.cs
@@ -164,21 +164,13 @@ namespace Demos {
PositionOffset = new Vector2(0, 1)
});
// Another button that shows animations!
- var fancyHoverTimer = 0D;
- var fancyButton = this.root.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, 10), "Fancy Hover") {
+ this.root.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, 10), "Fancy Hover") {
PositionOffset = new Vector2(0, 1),
- OnUpdated = (e, time) => {
- if (e.IsMouseOver && fancyHoverTimer <= 0.5F)
- return;
- if (fancyHoverTimer > 0) {
- fancyHoverTimer -= time.ElapsedGameTime.TotalSeconds * 3;
- e.ScaleTransform(1 + (float) Math.Sin(fancyHoverTimer * MathHelper.Pi) * 0.05F);
- } else {
- e.Transform = Matrix.Identity;
- }
+ MouseEnterAnimation = new UiAnimation(0.15, (a, e, p) => e.ScaleTransform(1 + Easings.OutSine(p) * 0.05F)),
+ MouseExitAnimation = new UiAnimation(0.15, (a, e, p) => e.ScaleTransform(1 + Easings.OutSine.ReverseOutput()(p) * 0.05F)) {
+ Finished = (a, e) => e.Transform = Matrix.Identity
}
});
- fancyButton.OnMouseEnter += e => fancyHoverTimer = 1;
this.root.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.5F, 10), "Transform Ui", "This button causes the entire ui to be transformed (both in positioning, rotation and scale)") {
OnPressed = element => {
if (element.Root.Transform == Matrix.Identity) {
diff --git a/MLEM.Ui/Elements/Element.cs b/MLEM.Ui/Elements/Element.cs
index 4b5192c..e97b1c7 100644
--- a/MLEM.Ui/Elements/Element.cs
+++ b/MLEM.Ui/Elements/Element.cs
@@ -379,6 +379,14 @@ namespace MLEM.Ui.Elements {
this.SetAreaDirty();
}
}
+ ///
+ /// A that is played when the mouse enters this element, in .
+ ///
+ public StyleProp MouseEnterAnimation;
+ ///
+ /// A that is played when the mouse exits this element, in .
+ ///
+ public StyleProp MouseExitAnimation;
///
/// Event that is called after this element is drawn, but before its children are drawn
@@ -490,6 +498,12 @@ namespace MLEM.Ui.Elements {
/// Use or to manipulate this list while calling all of the necessary callbacks.
///
protected readonly IList Children;
+ ///
+ /// A list of all of the instances that are currently playing.
+ /// You can modify this collection through and .
+ ///
+ protected readonly List PlayingAnimations = new List();
+
///
/// A sorted version of . The children are sorted by their .
///
@@ -541,8 +555,16 @@ namespace MLEM.Ui.Elements {
this.size = size;
this.Children = new ReadOnlyCollection(this.children);
- this.GetTabNextElement = (backward, next) => next;
- this.GetGamepadNextElement = (dir, next) => next;
+ this.GetTabNextElement += (backward, next) => next;
+ this.GetGamepadNextElement += (dir, next) => next;
+ this.OnMouseEnter += e => {
+ if (e.MouseEnterAnimation.HasValue())
+ e.PlayAnimation(e.MouseEnterAnimation);
+ };
+ this.OnMouseExit += e => {
+ if (e.MouseExitAnimation.HasValue())
+ e.PlayAnimation(e.MouseExitAnimation);
+ };
this.SetAreaDirty();
this.SetSortedChildrenDirty();
@@ -1028,6 +1050,14 @@ namespace MLEM.Ui.Elements {
public virtual void Update(GameTime time) {
this.System.InvokeOnElementUpdated(this, time);
+ for (var i = this.PlayingAnimations.Count - 1; i >= 0; i--) {
+ var anim = this.PlayingAnimations[i];
+ if (anim.Update(this, time)) {
+ anim.OnFinished(this);
+ this.PlayingAnimations.RemoveAt(i);
+ }
+ }
+
// update all sorted children, not just relevant ones, because they might become relevant or irrelevant through updates
foreach (var child in this.SortedChildren) {
if (child.System != null)
@@ -1170,6 +1200,33 @@ namespace MLEM.Ui.Elements {
return this.CanBeMoused && this.DisplayArea.Contains(position) ? this : null;
}
+ ///
+ /// Plays the given on this element, causing it to be added to the and updated in .
+ /// If the given is already playing on this element, it will be restarted.
+ ///
+ /// The animation to play.
+ public virtual void PlayAnimation(UiAnimation animation) {
+ if (this.PlayingAnimations.Contains(animation)) {
+ // if we're already playing this animation, just restart it
+ animation.OnFinished(this);
+ } else {
+ this.PlayingAnimations.Add(animation);
+ }
+ }
+
+ ///
+ /// Stops the given on this element, causing it to be removed from the and to be invoked.
+ ///
+ /// The animation to stop.
+ /// Whether the animation was present in this element's .
+ public virtual bool StopAnimation(UiAnimation animation) {
+ if (this.PlayingAnimations.Remove(animation)) {
+ animation.OnFinished(this);
+ return true;
+ }
+ return false;
+ }
+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
[Obsolete("Dispose will be removed in a future update. To unregister custom event handlers, use OnRemovedFromUi instead.")]
public virtual void Dispose() {
diff --git a/MLEM.Ui/UiAnimation.cs b/MLEM.Ui/UiAnimation.cs
new file mode 100644
index 0000000..61a9a3c
--- /dev/null
+++ b/MLEM.Ui/UiAnimation.cs
@@ -0,0 +1,86 @@
+using System;
+using Microsoft.Xna.Framework;
+using MLEM.Misc;
+using MLEM.Ui.Elements;
+
+namespace MLEM.Ui {
+ ///
+ /// A ui animation is a simple timed event that an in a can use to play a visual or other type of animation.
+ /// To use ui animations, you can use , or one of the built-in style properties like or .
+ ///
+ public class UiAnimation : GenericDataHolder {
+
+ ///
+ /// The total time that this ui animation plays for.
+ ///
+ public readonly TimeSpan TotalTime;
+ ///
+ /// The that is invoked every .
+ ///
+ public readonly AnimationFunction Function;
+
+ ///
+ /// An event that is raised when this ui animation is (re)started in .
+ ///
+ public Action Started;
+ ///
+ /// An event that is raised when this ui animation is stopped or finished through .
+ ///
+ public Action Finished;
+ ///
+ /// The current time that this ui animation has been playing for, out of the .
+ ///
+ public TimeSpan CurrentTime { get; private set; }
+
+ ///
+ /// Creates a new ui animation with the given settings.
+ ///
+ /// The amount of seconds that this ui animation should play for.
+ /// The that is invoked every .
+ public UiAnimation(double seconds, AnimationFunction function) : this(TimeSpan.FromSeconds(seconds), function) {}
+
+ ///
+ /// Creates a new ui animation with the given settings.
+ ///
+ /// The that this ui animation should play for.
+ /// The that is invoked every .
+ public UiAnimation(TimeSpan totalTime, AnimationFunction function) {
+ this.TotalTime = totalTime;
+ this.Function = function;
+ }
+
+ ///
+ /// Updates this ui animation, invoking its event if necessary, increasing its and invoking its .
+ /// This method is called by an in .
+ ///
+ /// The element that this ui animation is attached to.
+ /// The game's current time.
+ /// Whether this animation is ready to finish, that is, if its is greater than or equal to its .
+ public virtual bool Update(Element element, GameTime time) {
+ if (this.CurrentTime <= TimeSpan.Zero)
+ this.Started?.Invoke(this, element);
+
+ this.CurrentTime += time.ElapsedGameTime;
+ this.Function?.Invoke(this, element, this.CurrentTime.Ticks / (float) this.TotalTime.Ticks);
+
+ return this.CurrentTime >= this.TotalTime;
+ }
+
+ ///
+ /// Causes this ui animation's event to be raised, and sets the to .
+ /// This allows the animation to play from the start again.
+ /// This method is invoked automatically when returns in , as well as in .
+ ///
+ ///
+ public virtual void OnFinished(Element element) {
+ this.Finished?.Invoke(this, element);
+ this.CurrentTime = TimeSpan.Zero;
+ }
+
+ ///
+ /// A delegate method used by .
+ ///
+ public delegate void AnimationFunction(UiAnimation animation, Element element, float timePercentage);
+
+ }
+}