diff --git a/Notui/NotuiElement.cs b/Notui/NotuiElement.cs index 6ea4d3f..cbddf10 100644 --- a/Notui/NotuiElement.cs +++ b/Notui/NotuiElement.cs @@ -7,8 +7,6 @@ using System.Reactive.Linq; using System.Windows; using System.Windows.Forms; -using System.Windows.Media; -using System.Windows.Media.Animation; using md.stdl.Interaction; using md.stdl.Interfaces; using md.stdl.Time; @@ -18,6 +16,28 @@ namespace Notui { + /// + /// Enum indicating the current visibility state of an element. You can combine flags regarding what state you're listening to. + /// + [Flags] + public enum ElementFadeState + { + /// Element invisible + Hidden = 1, + /// Element is waiting to be faded in or out respecting delays + Waiting = 16, + /// Element Fading in + FadingIn = 2, + /// Element is waiting to be faded in respecting delays + WaitingForFadeIn = Hidden | FadingIn | Waiting, + /// Element visible + Visible = 4, + /// Element Fading out + FadingOut = 8, + /// Element is waiting to be faded out respecting delays + WaitingForFadeOut = Visible | FadingOut | Waiting + } + /// /// /// Event args involving touches @@ -53,25 +73,15 @@ public class ChildrenUpdatedEventArgs : EventArgs /// public abstract class NotuiElement : DependencyObject, IElementCommon, ICloneable, IUpdateable, IMainlooping { - /// - public static readonly DependencyProperty RawFadeInProgressProperty = DependencyProperty.Register( - "RawFadeInProgress", - typeof(double), - typeof(NotuiElement) - ); - - /// - public static readonly DependencyProperty RawFadeOutProgressProperty = DependencyProperty.Register( - "RawFadeOutProgress", - typeof(double), - typeof(NotuiElement) - ); - private Matrix4x4 _displayMatrix; private Matrix4x4 _invDisplayMatrix; private bool _onFadedInInvoked; private SubContext _subContext; - + private double _rawFadeProgress; + private double _absFadeProgress; + private bool _fadingCancelled; + private bool _fadingCancelledFrame; + /// public string Name { get; set; } /// @@ -80,7 +90,6 @@ public abstract class NotuiElement : DependencyObject, IElementCommon, ICloneabl public float FadeOutTime { get; set; } /// public float FadeInTime { get; set; } - /// public float FadeOutDelay { get; set; } @@ -92,7 +101,7 @@ public abstract class NotuiElement : DependencyObject, IElementCommon, ICloneabl /// /// Time it takes to completely fade out the element /// - public float AbsoluteFadeOutTime => AbsoluteFadeOutDelay + FadeOutDelay; + public float AbsoluteFadeOutTime => AbsoluteFadeOutDelay + FadeOutTime; /// public float FadeInDelay { get; set; } @@ -105,7 +114,7 @@ public abstract class NotuiElement : DependencyObject, IElementCommon, ICloneabl /// /// Time it takes to completely fade in the element /// - public float AbsoluteFadeInTime => AbsoluteFadeInDelay + FadeInDelay; + public float AbsoluteFadeInTime => AbsoluteFadeInDelay + FadeInTime; /// public float TransformationFollowTime { get; set; } @@ -161,41 +170,6 @@ public abstract class NotuiElement : DependencyObject, IElementCommon, ICloneabl /// public double ElementFade { get; set; } - /// - /// 0 to 1 progress of Fadein including delays - /// - public double RawFadeInProgress - { - get => (double) GetValue(RawFadeInProgressProperty); - set => SetValue(RawFadeInProgressProperty, value); - } - - /// - /// 0 at element faded out, 1 at faded in, respecting delays - /// - public double FadeInProgress => VMath.Map(RawFadeInProgress * AbsoluteFadeInTime, AbsoluteFadeInDelay, AbsoluteFadeInTime, 0.0, 1.0, TMapMode.Clamp); - - /// - /// Animation driving the fadein; - /// - public DoubleAnimation FadeInAnimation { get; private set; } - - /// - /// 0 to 1 progress of Fadeout including delays - /// - public double RawFadeOutProgress - { - get => (double)GetValue(RawFadeOutProgressProperty); - set => SetValue(RawFadeOutProgressProperty, value); - } - - /// - /// 0 at element faded in, 1 at faded out, respecting delays - /// - public double FadeOutProgress => VMath.Map(RawFadeOutProgress * AbsoluteFadeOutTime, AbsoluteFadeOutDelay, AbsoluteFadeOutTime, 0.0, 1.0, TMapMode.Clamp); - - public DoubleAnimation FadeOutAnimation { get; private set; } - /// /// List of touches interacting with this element which is managed by this element /// @@ -235,9 +209,9 @@ public double RawFadeOutProgress public bool Dying { get; set; } /// - /// True while fading in. + /// Indicating current state of the element fading /// - public bool FadingIn { get; set; } + public ElementFadeState FadingState { get; set; } /// /// Element age since creation @@ -519,17 +493,9 @@ protected virtual void MainloopBeforeBehaviors() { } /// protected virtual void MainloopEnd() { } - /// - /// This is called every frame by the context - /// - /// - /// The context call this function of all flattened elements in parallel regardless of the element hierarchy, you should take this into account when overriding this function or developing behaviors. You MUST NOT call the Mainloop method of the children elements yourself because the context already does so (unless you are really desperate). - /// - public void Mainloop(float deltatime) + private void MainloopRemoveSuicidalChildren() { - OnMainLoopBegin?.Invoke(this, EventArgs.Empty); - - if(Children.Count > 0) + if (Children.Count > 0) { foreach (var child in Children.Values.ToArray()) { @@ -540,22 +506,21 @@ public void Mainloop(float deltatime) } } } - - DisplayTransformation.SubscribeToChange(Id, transformation => InvalidateMatrices()); - - MainloopBegin(); + } - var endtouches = ( from touch in Touching.Keys - where touch.ExpireFrames > Context.ConsiderReleasedAfter || !touch.Pressed - select touch + private void MainloopProcessTouches() + { + var endtouches = (from touch in Touching.Keys + where touch.ExpireFrames > Context.ConsiderReleasedAfter || !touch.Pressed + select touch ).ToArray(); foreach (var touch in endtouches) FireTouchEnd(touch); - var endhits = ( from touch in Hitting.Keys - where touch.ExpireFrames > Context.ConsiderReleasedAfter - select touch + var endhits = (from touch in Hitting.Keys + where touch.ExpireFrames > Context.ConsiderReleasedAfter + select touch ).ToArray(); foreach (var touch in endhits) @@ -579,20 +544,89 @@ select touch if (Touching.ContainsKey(touch)) Touching[touch] = inters; } + } - if (AbsoluteFadeInTime > 0 && AbsoluteFadeOutTime > 0) - { - ElementFade = VMath.Clamp(FadeInProgress - FadeOutProgress, 0.0, 1.0); - } - else if (AbsoluteFadeInTime > 0) + private void MainloopProcessFade(float deltatime) + { + if ((FadingState & ElementFadeState.FadingIn) > 0 && AbsoluteFadeInTime > 0) { - ElementFade = VMath.Clamp(FadeInProgress, 0.0, 1.0); + if (_fadingCancelled) + { + if (_fadingCancelledFrame) _rawFadeProgress = ElementFade; + var delta = deltatime / FadeInTime; + _rawFadeProgress += delta; + ElementFade = _rawFadeProgress; + } + else + { + var delta = deltatime / AbsoluteFadeInTime; + _rawFadeProgress += delta; + _absFadeProgress = _rawFadeProgress * AbsoluteFadeInTime - AbsoluteFadeInDelay; + + ElementFade = VMath.Map( + _absFadeProgress, + 0.0, FadeInTime, + 0.0, 1.0, + TMapMode.Clamp); + } + + FadingState = _absFadeProgress < 0 ? ElementFadeState.WaitingForFadeIn : ElementFadeState.FadingIn; + + if (_rawFadeProgress >= 1) + { + _rawFadeProgress = 1; + ElementFade = 1; + FadingState = ElementFadeState.Visible; + _fadingCancelled = false; + + Dying = false; + OnFadedIn?.Invoke(this, EventArgs.Empty); + } } - else if (AbsoluteFadeOutTime > 0) + + if ((FadingState & ElementFadeState.FadingOut) > 0 && AbsoluteFadeOutTime > 0) { - ElementFade = VMath.Clamp(1.0 - FadeOutProgress, 0.0, 1.0); + if (_fadingCancelled) + { + if(_fadingCancelledFrame) _rawFadeProgress = ElementFade; + var delta = deltatime / FadeOutTime; + _rawFadeProgress -= delta; + ElementFade = _rawFadeProgress; + } + else + { + var delta = deltatime / AbsoluteFadeOutTime; + _rawFadeProgress -= delta; + _absFadeProgress = _rawFadeProgress * AbsoluteFadeOutTime; + + ElementFade = VMath.Map( + _absFadeProgress, + 0.0, FadeOutTime, + 0.0, 1.0, + TMapMode.Clamp); + } + + FadingState = _absFadeProgress > FadeOutTime ? ElementFadeState.WaitingForFadeOut : ElementFadeState.FadingOut; + + if (_rawFadeProgress <= 0) + { + _rawFadeProgress = 0; + ElementFade = 0; + FadingState = ElementFadeState.Hidden; + _fadingCancelled = false; + + OnDeleting?.Invoke(this, EventArgs.Empty); + DeleteMe = true; + } } + _fadingCancelledFrame = false; + if (FadingState == ElementFadeState.Visible) ElementFade = 1; + if (FadingState == ElementFadeState.Hidden) ElementFade = 0; + } + + private void MainloopProcessMice() + { Mice = Touching.Keys.Where(t => t.AttachadMouse != null) .Concat(Hitting.Keys.Where(t => t.AttachadMouse != null)).Distinct().ToArray(); @@ -622,7 +656,7 @@ MouseInteractionEventArgs MakeMouseArgs(Touch touch) if (tmouse.MouseDelta.MouseClicks.Values.Any(mc => mc.ButtonDown)) buttonpressed = true; if (tmouse.MouseDelta.MouseClicks.Values.Any(mc => mc.ButtonUp)) buttonreleased = true; } - + if (vscrolled) { foreach (var touch in Mice.Where(mt => mt.MouseDelta.AccumulatedWheelDelta != 0)) @@ -664,6 +698,29 @@ MouseInteractionEventArgs MakeMouseArgs(Touch touch) } } } + } + + /// + /// This is called every frame by the context + /// + /// + /// The context call this function of all flattened elements in parallel regardless of the element hierarchy, you should take this into account when overriding this function or developing behaviors. You MUST NOT call the Mainloop method of the children elements yourself because the context already does so (unless you are really desperate). + /// + public void Mainloop(float deltatime) + { + OnMainLoopBegin?.Invoke(this, EventArgs.Empty); + + MainloopRemoveSuicidalChildren(); + + DisplayTransformation.SubscribeToChange(Id, transformation => InvalidateMatrices()); + + MainloopBegin(); + + MainloopProcessTouches(); + + MainloopProcessFade(deltatime); + + MainloopProcessMice(); if (TransformationFollowTime > 0) { @@ -714,13 +771,10 @@ public virtual void UpdateFrom(ElementPrototype other) if (Dying) { - var fadeoutcancelanim = new DoubleAnimation(0.0, new Duration(TimeSpan.FromSeconds(AbsoluteFadeInTime))); - fadeoutcancelanim.Completed += (sender, args) => - { - OnFadedIn?.Invoke(this, EventArgs.Empty); - }; + _fadingCancelled = true; + _fadingCancelledFrame = true; + FadingState = ElementFadeState.FadingIn; Dying = false; - fadeoutcancelanim.BeginAnimation(RawFadeOutProgressProperty, null, HandoffBehavior.SnapshotAndReplace); } UpdateChildren(true, other.Children.Values.ToArray()); @@ -749,8 +803,16 @@ protected NotuiElement(ElementPrototype prototype, NotuiContext context, NotuiEl Children.Add(child.Id, newchild); } - InitializeFadeInAnim(); - InitializeFadeOutAnim(); + if (AbsoluteFadeInTime > 0) + { + FadingState = ElementFadeState.FadingIn; + _rawFadeProgress = 0; + } + else + { + FadingState = ElementFadeState.Visible; + _rawFadeProgress = 1; + } Age.Start(); @@ -759,28 +821,7 @@ protected NotuiElement(ElementPrototype prototype, NotuiContext context, NotuiEl SubContext = new SubContext(this, prototype.SubContextOptions); } } - - private void InitializeFadeInAnim() - { - FadeInAnimation = new DoubleAnimation(1.0, new Duration(TimeSpan.FromSeconds(AbsoluteFadeInDelay + FadeInTime))); - FadeInAnimation.Completed += (sender, args) => - { - Dying = false; - DeleteMe = false; - OnFadedIn?.Invoke(this, EventArgs.Empty); - }; - FadeInAnimation.BeginAnimation(RawFadeInProgressProperty, null, HandoffBehavior.SnapshotAndReplace); - } - - private void InitializeFadeOutAnim() - { - FadeOutAnimation = new DoubleAnimation(1.0, new Duration(TimeSpan.FromSeconds(AbsoluteFadeOutDelay + FadeOutTime))); - FadeOutAnimation.Completed += (sender, args) => - { - OnDeleting?.Invoke(this, EventArgs.Empty); - if(Dying) DeleteMe = true; - }; - } + /// /// Override to modify the behavior how interaction should begin @@ -844,30 +885,23 @@ public void StartDeletion() /// /// Override to modify how element should be deleted /// - protected void Delete() + protected virtual void Delete() { if (AbsoluteFadeOutTime > 0) { - if (FadingIn) - { - var fadeincancelanim = new DoubleAnimation(0.0, new Duration(TimeSpan.FromSeconds(AbsoluteFadeOutDelay))); - fadeincancelanim.Completed += (sender, args) => - { - OnDeleting?.Invoke(this, EventArgs.Empty); - if (Dying) DeleteMe = true; - }; - fadeincancelanim.BeginAnimation(RawFadeInProgressProperty, null, HandoffBehavior.SnapshotAndReplace); - } - else + if ((FadingState & ElementFadeState.FadingIn) > 0) { - FadeOutAnimation.BeginAnimation(RawFadeOutProgressProperty, null, HandoffBehavior.SnapshotAndReplace); + _fadingCancelled = true; + _fadingCancelledFrame = true; } + FadingState = ElementFadeState.FadingOut; OnDeletionStarted?.Invoke(this, EventArgs.Empty); Dying = true; } else { OnDeleting?.Invoke(this, EventArgs.Empty); + FadingState = ElementFadeState.Hidden; DeleteMe = true; Dying = true; }