1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-05-04 17:07:07 +02:00

Added UiAnimation system

This commit is contained in:
Ell 2023-06-14 10:21:32 +02:00
parent 985dc74376
commit d48b7e2e71
4 changed files with 150 additions and 14 deletions

View file

@ -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

View file

@ -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) {

View file

@ -379,6 +379,14 @@ namespace MLEM.Ui.Elements {
this.SetAreaDirty();
}
}
/// <summary>
/// A <see cref="UiAnimation"/> that is played when the mouse enters this element, in <see cref="OnMouseEnter"/>.
/// </summary>
public StyleProp<UiAnimation> MouseEnterAnimation;
/// <summary>
/// A <see cref="UiAnimation"/> that is played when the mouse exits this element, in <see cref="OnMouseExit"/>.
/// </summary>
public StyleProp<UiAnimation> MouseExitAnimation;
/// <summary>
/// Event that is called after this element is drawn, but before its children are drawn
@ -490,6 +498,12 @@ namespace MLEM.Ui.Elements {
/// Use <see cref="AddChild{T}"/> or <see cref="RemoveChild"/> to manipulate this list while calling all of the necessary callbacks.
/// </summary>
protected readonly IList<Element> Children;
/// <summary>
/// A list of all of the <see cref="UiAnimation"/> instances that are currently playing.
/// You can modify this collection through <see cref="PlayAnimation"/> and <see cref="StopAnimation"/>.
/// </summary>
protected readonly List<UiAnimation> PlayingAnimations = new List<UiAnimation>();
/// <summary>
/// A sorted version of <see cref="Children"/>. The children are sorted by their <see cref="Priority"/>.
/// </summary>
@ -541,8 +555,16 @@ namespace MLEM.Ui.Elements {
this.size = size;
this.Children = new ReadOnlyCollection<Element>(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;
}
/// <summary>
/// Plays the given <see cref="UiAnimation"/> on this element, causing it to be added to the <see cref="PlayingAnimations"/> and updated in <see cref="Update"/>.
/// If the given <paramref name="animation"/> is already playing on this element, it will be restarted.
/// </summary>
/// <param name="animation">The animation to play.</param>
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);
}
}
/// <summary>
/// Stops the given <see cref="UiAnimation"/> on this element, causing it to be removed from the <see cref="PlayingAnimations"/> and <see cref="UiAnimation.OnFinished"/> to be invoked.
/// </summary>
/// <param name="animation">The animation to stop.</param>
/// <returns>Whether the animation was present in this element's <see cref="PlayingAnimations"/>.</returns>
public virtual bool StopAnimation(UiAnimation animation) {
if (this.PlayingAnimations.Remove(animation)) {
animation.OnFinished(this);
return true;
}
return false;
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
[Obsolete("Dispose will be removed in a future update. To unregister custom event handlers, use OnRemovedFromUi instead.")]
public virtual void Dispose() {

86
MLEM.Ui/UiAnimation.cs Normal file
View file

@ -0,0 +1,86 @@
using System;
using Microsoft.Xna.Framework;
using MLEM.Misc;
using MLEM.Ui.Elements;
namespace MLEM.Ui {
/// <summary>
/// A ui animation is a simple timed event that an <see cref="Element"/> in a <see cref="UiSystem"/> can use to play a visual or other type of animation.
/// To use ui animations, you can use <see cref="Element.PlayAnimation"/>, or one of the built-in style properties like <see cref="Element.MouseEnterAnimation"/> or <see cref="Element.MouseExitAnimation"/>.
/// </summary>
public class UiAnimation : GenericDataHolder {
/// <summary>
/// The total time that this ui animation plays for.
/// </summary>
public readonly TimeSpan TotalTime;
/// <summary>
/// The <see cref="AnimationFunction"/> that is invoked every <see cref="Update"/>.
/// </summary>
public readonly AnimationFunction Function;
/// <summary>
/// An event that is raised when this ui animation is (re)started in <see cref="Update"/>.
/// </summary>
public Action<UiAnimation, Element> Started;
/// <summary>
/// An event that is raised when this ui animation is stopped or finished through <see cref="OnFinished"/>.
/// </summary>
public Action<UiAnimation, Element> Finished;
/// <summary>
/// The current time that this ui animation has been playing for, out of the <see cref="TotalTime"/>.
/// </summary>
public TimeSpan CurrentTime { get; private set; }
/// <summary>
/// Creates a new ui animation with the given settings.
/// </summary>
/// <param name="seconds">The amount of seconds that this ui animation should play for.</param>
/// <param name="function">The <see cref="AnimationFunction"/> that is invoked every <see cref="Update"/>.</param>
public UiAnimation(double seconds, AnimationFunction function) : this(TimeSpan.FromSeconds(seconds), function) {}
/// <summary>
/// Creates a new ui animation with the given settings.
/// </summary>
/// <param name="totalTime">The <see cref="TotalTime"/> that this ui animation should play for.</param>
/// <param name="function">The <see cref="AnimationFunction"/> that is invoked every <see cref="Update"/>.</param>
public UiAnimation(TimeSpan totalTime, AnimationFunction function) {
this.TotalTime = totalTime;
this.Function = function;
}
/// <summary>
/// Updates this ui animation, invoking its <see cref="Started"/> event if necessary, increasing its <see cref="CurrentTime"/> and invoking its <see cref="Function"/>.
/// This method is called by an <see cref="Element"/> in <see cref="Element.Update"/>.
/// </summary>
/// <param name="element">The element that this ui animation is attached to.</param>
/// <param name="time">The game's current time.</param>
/// <returns>Whether this animation is ready to finish, that is, if its <see cref="CurrentTime"/> is greater than or equal to its <see cref="TotalTime"/>.</returns>
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;
}
/// <summary>
/// Causes this ui animation's <see cref="Finished"/> event to be raised, and sets the <see cref="CurrentTime"/> to <see cref="TimeSpan.Zero"/>.
/// This allows the animation to play from the start again.
/// This method is invoked automatically when <see cref="Update"/> returns <see langword="true"/> in <see cref="Element.Update"/>, as well as in <see cref="Element.StopAnimation"/>.
/// </summary>
/// <param name="element"></param>
public virtual void OnFinished(Element element) {
this.Finished?.Invoke(this, element);
this.CurrentTime = TimeSpan.Zero;
}
/// <summary>
/// A delegate method used by <see cref="UiAnimation.Function"/>.
/// </summary>
public delegate void AnimationFunction(UiAnimation animation, Element element, float timePercentage);
}
}