From 9cc366106b236cbe9dcf3546b36fe42389b15c1c Mon Sep 17 00:00:00 2001 From: David Date: Tue, 17 Aug 2021 15:33:18 -0400 Subject: [PATCH 1/7] fix(pointers): [skia] Fix captures atomatically released when leaving control --- .../UI/Xaml/UIElement.Pointers.Managed.cs | 34 ++++++++++++++++--- src/Uno.UI/UI/Xaml/UIElement.Pointers.cs | 27 ++++++++------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/UIElement.Pointers.Managed.cs b/src/Uno.UI/UI/Xaml/UIElement.Pointers.Managed.cs index 862fe17aa1fe..b7c0f518716a 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.Pointers.Managed.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.Pointers.Managed.cs @@ -8,6 +8,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Text; +using Windows.Devices.Input; using Microsoft.Extensions.Logging; using Uno.Disposables; using Uno.Extensions; @@ -137,6 +138,12 @@ private void CoreWindow_PointerExited(CoreWindow sender, PointerEventArgs args) var routedArgs = new PointerRoutedEventArgs(args, originalSource); Raise(Leave, overBranchLeaf, routedArgs); + if (!args.CurrentPoint.IsInContact && args.CurrentPoint.Pointer.Type == PointerDeviceType.Touch) + { + // We release the captures on exit when pointer if not pressed + // Note: for a "Tap" with a finger the sequence is Up / Exited / Lost, so the lost cannot be raised on Up + ReleaseCaptures(routedArgs); + } } private void CoreWindow_PointerPressed(CoreWindow sender, PointerEventArgs args) @@ -172,6 +179,7 @@ private void CoreWindow_PointerPressed(CoreWindow sender, PointerEventArgs args) private void CoreWindow_PointerReleased(CoreWindow sender, PointerEventArgs args) { var (originalSource, _) = VisualTreeHelper.HitTest(args.CurrentPoint.Position); + var isOutOfWindow = originalSource is null; // Even if impossible for the Release, we are fallbacking on the RootElement for safety // This is how UWP behaves: when out of the bounds of the Window, the root element is use. @@ -196,6 +204,12 @@ private void CoreWindow_PointerReleased(CoreWindow sender, PointerEventArgs args var routedArgs = new PointerRoutedEventArgs(args, originalSource); RaiseUsingCaptures(Released, originalSource, routedArgs); + if (isOutOfWindow || args.CurrentPoint.Pointer.Type != PointerDeviceType.Touch) + { + // We release the captures on up but only after the released event and processed the gesture + // Note: For a "Tap" with a finger the sequence is Up / Exited / Lost, so we let the Exit raise the capture lost + ReleaseCaptures(routedArgs); + } ClearPressedState(routedArgs); } @@ -266,9 +280,21 @@ private void CoreWindow_PointerCancelled(CoreWindow sender, PointerEventArgs arg var routedArgs = new PointerRoutedEventArgs(args, originalSource); RaiseUsingCaptures(Cancelled, originalSource, routedArgs); + // Note: No ReleaseCaptures(routedArgs);, the cancel automatically raise it ClearPressedState(routedArgs); } + private void ReleaseCaptures(PointerRoutedEventArgs routedArgs) + { + if (PointerCapture.TryGet(routedArgs.Pointer, out var capture)) + { + foreach (var target in capture.Targets) + { + target.Element.ReleasePointerCapture(capture.Pointer); + } + } + } + private void ClearPressedState(PointerRoutedEventArgs routedArgs) { if (_pressedElements.TryGetValue(routedArgs.Pointer, out var pressedLeaf)) @@ -285,7 +311,7 @@ private void ClearPressedState(PointerRoutedEventArgs routedArgs) } } -#region Helpers + #region Helpers private delegate void RaisePointerEventArgs(UIElement element, PointerRoutedEventArgs args, BubblingContext ctx); private static readonly RaisePointerEventArgs Wheel = (elt, args, ctx) => elt.OnPointerWheel(args, ctx); @@ -351,7 +377,7 @@ private static void RaiseUsingCaptures(RaisePointerEventArgs raise, UIElement or raise(originalSource, routedArgs, BubblingContext.Bubble); } } -#endregion + #endregion } // TODO Should be per CoreWindow @@ -365,7 +391,7 @@ partial void InitializePointersPartial() } } -#region HitTestVisibility + #region HitTestVisibility internal void UpdateHitTest() { this.CoerceValue(HitTestVisibilityProperty); @@ -434,7 +460,7 @@ internal void ClearHitTestVisibilityForRoot() this.ClearValue(HitTestVisibilityProperty); } -#endregion + #endregion partial void CapturePointerNative(Pointer pointer) => CoreWindow.GetForCurrentThread()!.SetPointerCapture(); diff --git a/src/Uno.UI/UI/Xaml/UIElement.Pointers.cs b/src/Uno.UI/UI/Xaml/UIElement.Pointers.cs index f14987777b9b..07cee003c7d2 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.Pointers.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.Pointers.cs @@ -857,7 +857,6 @@ private bool OnPointerUp(PointerRoutedEventArgs args, BubblingContext ctx = defa handledInManaged |= SetPressed(args, false, muteEvent: ctx.IsLocalOnly || !isOverOrCaptured); - // Note: We process the UpEvent between Release and Exited as the gestures like "Tap" // are fired between those events. if (_gestures.IsValueCreated) @@ -873,6 +872,7 @@ private bool OnPointerUp(PointerRoutedEventArgs args, BubblingContext ctx = defa } } +#if !UNO_HAS_MANAGED_POINTERS // Captures release are handled a root level // We release the captures on up but only after the released event and processed the gesture // Note: For a "Tap" with a finger the sequence is Up / Exited / Lost, so we let the Exit raise the capture lost // Note: If '!isOver', that means that 'IsCaptured == true' otherwise 'isOverOrCaptured' would have been false. @@ -880,6 +880,7 @@ private bool OnPointerUp(PointerRoutedEventArgs args, BubblingContext ctx = defa { handledInManaged |= SetNotCaptured(args); } +#endif return handledInManaged; } @@ -898,12 +899,14 @@ private bool OnPointerExited(PointerRoutedEventArgs args, BubblingContext ctx = global::Windows.UI.Xaml.Window.Current.DragDrop.ProcessMoved(args); } +#if !UNO_HAS_MANAGED_POINTERS // Captures release are handled a root level // We release the captures on exit when pointer if not pressed // Note: for a "Tap" with a finger the sequence is Up / Exited / Lost, so the lost cannot be raised on Up if (!IsPressed(args.Pointer)) { handledInManaged |= SetNotCaptured(args); } +#endif return handledInManaged; } @@ -990,9 +993,9 @@ private bool RaisePointerEvent(RoutedEvent evt, PointerRoutedEventArgs args, Bub _pendingRaisedEvent = (null, null, null); } } - #endregion +#endregion - #region Pointer over state (Updated by the partial API OnNative***, should not be updated externaly) +#region Pointer over state (Updated by the partial API OnNative***, should not be updated externaly) /// /// Indicates if a pointer (no matter the pointer) is currently over the element (i.e. OverState) /// WARNING: This might not be maintained for all controls, cf. remarks. @@ -1039,9 +1042,9 @@ private bool SetOver(PointerRoutedEventArgs args, bool isOver, bool muteEvent = return RaisePointerEvent(PointerExitedEvent, args); } } - #endregion +#endregion - #region Pointer pressed state (Updated by the partial API OnNative***, should not be updated externaly) +#region Pointer pressed state (Updated by the partial API OnNative***, should not be updated externaly) private readonly HashSet _pressedPointers = new HashSet(); /// @@ -1113,9 +1116,9 @@ private bool SetPressed(PointerRoutedEventArgs args, bool isPressed, bool muteEv } private void ClearPressed() => _pressedPointers.Clear(); - #endregion +#endregion - #region Pointer capture state (Updated by the partial API OnNative***, should not be updated externaly) +#region Pointer capture state (Updated by the partial API OnNative***, should not be updated externaly) /* * About pointer capture * @@ -1129,7 +1132,7 @@ private bool SetPressed(PointerRoutedEventArgs args, bool isPressed, bool muteEv private List _localExplicitCaptures; - #region Capture public (and internal) API ==> This manages only Explicit captures +#region Capture public (and internal) API ==> This manages only Explicit captures public static DependencyProperty PointerCapturesProperty { get; } = DependencyProperty.Register( "PointerCaptures", typeof(IReadOnlyList), @@ -1196,7 +1199,7 @@ public void ReleasePointerCaptures() Release(PointerCaptureKind.Explicit); } - #endregion +#endregion partial void CapturePointerNative(Pointer pointer); partial void ReleasePointerNative(Pointer pointer); @@ -1295,9 +1298,9 @@ private bool Release(PointerCapture capture, PointerCaptureKind kinds, PointerRo relatedArgs.Handled = false; return RaisePointerEvent(PointerCaptureLostEvent, relatedArgs); } - #endregion +#endregion - #region Drag state (Updated by the RaiseDrag***, should not be updated externaly) +#region Drag state (Updated by the RaiseDrag***, should not be updated externaly) private HashSet _draggingOver; /// @@ -1329,6 +1332,6 @@ private void ClearDragOver() { _draggingOver?.Clear(); } - #endregion +#endregion } } From 144348cd26e1572c516f2f89fea7ed6030bf80c5 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 17 Aug 2021 15:41:15 -0400 Subject: [PATCH 2/7] fix(animation): Fix transition animation not cancelled BREAKING CHANGE: `VisualState.Setters` are now applied after `VisualTransition.Storyboard` --- src/Uno.UI/FeatureConfiguration.cs | 10 + .../UI/Xaml/Media/Animation/Storyboard.cs | 1 + .../UI/Xaml/Media/Animation/Timeline.cs | 4 +- src/Uno.UI/UI/Xaml/VisualStateGroup.cs | 258 ++++++++++-------- src/Uno.UI/UI/Xaml/VisualStateManager.cs | 95 ++++--- 5 files changed, 205 insertions(+), 163 deletions(-) diff --git a/src/Uno.UI/FeatureConfiguration.cs b/src/Uno.UI/FeatureConfiguration.cs index 78bb824b5d20..76fbdf4cd5c2 100644 --- a/src/Uno.UI/FeatureConfiguration.cs +++ b/src/Uno.UI/FeatureConfiguration.cs @@ -493,6 +493,16 @@ public static class UIElement #endif } + public static class VisualState + { + /// + /// When this is set, the will be applied synchronously when changing state, + /// unlike UWP which waits the for the end of the (if any) to apply them. + /// + /// This flag is for backward compatibility with old versions of uno and should not be turned on. + public static bool ApplySettersBeforeTransition { get; set; } = false; + } + public static class WebView { #if __ANDROID__ diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/Storyboard.cs b/src/Uno.UI/UI/Xaml/Media/Animation/Storyboard.cs index 479f473a279f..bddfc74bf732 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/Storyboard.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/Storyboard.cs @@ -287,6 +287,7 @@ internal void TurnOverAnimationsTo(Storyboard storyboard) ((ITimeline)child).Stop(); } } + State = TimelineState.Stopped; } diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/Timeline.cs b/src/Uno.UI/UI/Xaml/Media/Animation/Timeline.cs index 08ae4a815a63..3de91c724dd1 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/Timeline.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/Timeline.cs @@ -24,7 +24,7 @@ public Timeline() State = TimelineState.Stopped; } - protected enum TimelineState + protected internal enum TimelineState { Active, Filling, @@ -46,7 +46,7 @@ protected string[] GetTraceProperties() /// An internally-used property which is essentially equivalent to , except that it /// distinguishes from . /// - protected TimelineState State { get; set; } + protected internal TimelineState State { get; set; } public TimeSpan? BeginTime { diff --git a/src/Uno.UI/UI/Xaml/VisualStateGroup.cs b/src/Uno.UI/UI/Xaml/VisualStateGroup.cs index ee06acd3b2ed..4b0266ac803f 100644 --- a/src/Uno.UI/UI/Xaml/VisualStateGroup.cs +++ b/src/Uno.UI/UI/Xaml/VisualStateGroup.cs @@ -30,6 +30,11 @@ public sealed partial class VisualStateGroup : DependencyObject /// The xaml scope in force at the time the VisualStateGroup was created. /// private readonly XamlScope _xamlScope; + private (VisualState state, VisualTransition transition) _current; + + public event VisualStateChangedEventHandler CurrentStateChanging; + + public event VisualStateChangedEventHandler CurrentStateChanged; public VisualStateGroup() { @@ -41,6 +46,8 @@ public VisualStateGroup() this.RegisterParentChangedCallback(this, OnParentChanged); } + public VisualState CurrentState => _current.state; + public string Name { get; set; } #region States Dependency Property @@ -67,7 +74,6 @@ public IList States internal set { this.SetValue(StatesProperty, value); } } - // Using a DependencyProperty as the backing store for States. This enables animation, styling, binding, etc... public static DependencyProperty StatesProperty { get; } = DependencyProperty.Register( "States", @@ -101,16 +107,13 @@ public IList Transitions internal set { this.SetValue(TransitionsProperty, value); } } - // Using a DependencyProperty as the backing store for Transitions. This enables animation, styling, binding, etc... public static DependencyProperty TransitionsProperty { get; } = DependencyProperty.Register( "Transitions", typeof(IList), typeof(VisualStateGroup), new FrameworkPropertyMetadata( - defaultValue: null, - propertyChangedCallback: (s, e) => ((VisualStateGroup)s)?.OnTransitionsChanged(e) - ) + defaultValue: null) ); #endregion @@ -137,17 +140,11 @@ private void VisualStateChanged(object sender, IVectorChangedEventArgs e) RefreshStateTriggers(); } - //Adds Event Handlers when collections changed - private void OnTransitionsChanged(DependencyPropertyChangedEventArgs e) + private void OnParentChanged(object instance, object key, DependencyObjectParentChangedEventArgs args) { + RefreshStateTriggers(force: true); } - public VisualState CurrentState { get; internal set; } - - public event VisualStateChangedEventHandler CurrentStateChanging; - - public event VisualStateChangedEventHandler CurrentStateChanged; - internal void RaiseCurrentStateChanging(VisualState oldState, VisualState newState) { if (this.CurrentStateChanging == null) @@ -175,80 +172,60 @@ private Control FindFirstAncestorControl() return (this.GetParent() as FrameworkElement)?.FindFirstParent(); } - internal void GoToState(IFrameworkElement element, VisualState state, VisualState originalState, bool useTransitions, Action onStateChanged) + internal void GoToState( + IFrameworkElement element, + VisualState state, + bool useTransitions, + Action onStateChanged) { + global::System.Diagnostics.Debug.Assert(state is null || States.Contains(state)); + if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { this.Log().DebugFormat("Go to state [{0}/{1}] on [{2}]", Name, state?.Name, element); } - var transition = FindTransition(originalState?.Name, state?.Name); - - EventHandler onComplete = null; - - onComplete = (s, a) => + var current = _current; + var target = (state, transition: FindTransition(current.state?.Name, state?.Name)); + + // Stops running animations (transition or state's storyboard) + // Note about animations (as of 2021-08-16 win 19043): + // Any "running animation", either from the current transition or the current state's storyboard, + // is being "paused" for properties that are going to be animated by the "next animation" (again transition or target state's storyboard), + // and rollbacked for properties that won't be animated anymore. + var runningAnimation = current.transition?.Storyboard is { } currentTransition + && currentTransition.State != Timeline.TimelineState.Stopped + ? currentTransition + : current.state?.Storyboard; + var nextAnimation = target.transition?.Storyboard ?? target.state?.Storyboard; + if (runningAnimation != null) { - onStateChanged(); - - if (state?.Storyboard == null) + if(nextAnimation is null) { - return; + runningAnimation.Stop(); } - - state.Storyboard.Completed -= onComplete; - }; - - EventHandler onTransitionComplete = null; - - onTransitionComplete = (s, a) => - { - if (transition?.Storyboard != null && useTransitions) - { - transition.Storyboard.Completed -= onTransitionComplete; - - if (state?.Storyboard != null) - { - transition.Storyboard.TurnOverAnimationsTo(state.Storyboard); - } - } - - //Starts Storyboard Animation - if (state?.Storyboard == null) - { - onComplete(this, null); - } - else if (state != null) + else { - state.Storyboard.Completed += onComplete; - state.Storyboard.Begin(); + runningAnimation.TurnOverAnimationsTo(nextAnimation); } - }; + } - //Stops Previous Storyboard Animation - if (originalState != null) + // Rollback setters that won't be re-set by the target state setters + // Note about setters and transition (as of 2021-08-16 win 19043): + // * if current and target state have setters for the same property, + // the value of the current is kept as is and updated only once at the end of the transition + // * if current has a setter which is not updated by the target state + // the value is rollbacked before the transition + // * if the target has a setter for a property that was not affected by the current + // the value is applied only at the end of the transition + if (current.state is {} currentState) { - if (originalState.Storyboard != null) - { - if (transition?.Storyboard != null) - { - originalState.Storyboard.TurnOverAnimationsTo(transition.Storyboard); - } - else if (state?.Storyboard != null) - { - originalState.Storyboard.TurnOverAnimationsTo(state.Storyboard); - } - else - { - originalState.Storyboard.Stop(); - } - } - - foreach (var setter in this.CurrentState.Setters.OfType()) + foreach (var setter in currentState.Setters.OfType()) { - if (element != null && (state?.Setters.OfType().Any(o => o.HasSameTarget(setter, DependencyPropertyValuePrecedences.Animations, element)) ?? false)) + if (element != null && (target.state?.Setters.OfType().Any(o => o.HasSameTarget(setter, DependencyPropertyValuePrecedences.Animations, element)) ?? false)) { - // PERF: We clear the value of the current setter only if there isn't any setter in the target state - // which changes the same target property. + // We clear the value of the current setter only if there isn't any setter in the target state + // which changes the same target property (for perf ... and UWP behavior support regarding transition animation). if (this.Log().IsEnabled(LogLevel.Debug)) { @@ -262,78 +239,136 @@ internal void GoToState(IFrameworkElement element, VisualState state, VisualStat } } -#if !HAS_EXPENSIVE_TRYFINALLY - try -#endif + _current = target; + + // For backward compatibility, we may apply the setters before the end of the transition. + if (FeatureConfiguration.VisualState.ApplySettersBeforeTransition) + { + ApplyTargetStateSetters(); + } + + // Finally effectively apply the target state! + if (useTransitions && target.transition?.Storyboard is { } transitionAnimation) { - ResourceResolver.PushNewScope(_xamlScope); + // Note: As of 2021-08-16 win 19043, if the transitionAnimation is Repeat=Forever, we actually never apply the state! - this.CurrentState = state; - if (this.CurrentState != null && element != null) + transitionAnimation.Completed += OnTransitionCompleted; + transitionAnimation.Begin(); + + void OnTransitionCompleted(object s, object a) { - foreach (var setter in this.CurrentState.Setters.OfType()) + transitionAnimation.Completed -= OnTransitionCompleted; + + if (target.state?.Storyboard is { } stateAnimation) { - setter.ApplyValue(DependencyPropertyValuePrecedences.Animations, element); + transitionAnimation.TurnOverAnimationsTo(stateAnimation); } + + ApplyTargetState(); } + } + else + { + ApplyTargetState(); + } - if (transition?.Storyboard == null || !useTransitions) + void ApplyTargetState() + { + // Apply target state setters (the right time to do it!) + if (!FeatureConfiguration.VisualState.ApplySettersBeforeTransition) { - onTransitionComplete(this, null); + ApplyTargetStateSetters(); + } + + // Starts target state animation + if (target.state?.Storyboard is { } stateAnimation) + { + stateAnimation.Completed += OnStateStoryboardCompleted; + stateAnimation.Begin(); + + void OnStateStoryboardCompleted(object s, object a) + { + state.Storyboard.Completed -= OnStateStoryboardCompleted; + onStateChanged(); + } } else { - transition.Storyboard.Completed += onTransitionComplete; - transition.Storyboard.Begin(); + onStateChanged(); } } + + void ApplyTargetStateSetters() + { + if (target.state is null || element is null) + { + return; + } + #if !HAS_EXPENSIVE_TRYFINALLY - finally + try #endif - { - ResourceResolver.PopScope(); + { + ResourceResolver.PushNewScope(_xamlScope); + + foreach (var setter in target.state.Setters.OfType()) + { + setter.ApplyValue(DependencyPropertyValuePrecedences.Animations, element); + } + } +#if !HAS_EXPENSIVE_TRYFINALLY + finally +#endif + { + ResourceResolver.PopScope(); + } } } private VisualTransition FindTransition(string oldStateName, string newStateName) { - if (oldStateName.IsNullOrEmpty() || newStateName.IsNullOrEmpty()) - { - return null; - } - - var perfectMatch = Transitions.FirstOrDefault(vt => - string.Equals(vt.From, oldStateName) && - string.Equals(vt.To, newStateName)); + var hasOld = oldStateName.HasValue(); + var hasNew = newStateName.HasValue(); - if (perfectMatch != null) + if (hasOld && hasNew && Transitions.FirstOrDefault(Match(oldStateName, newStateName)) is { } perfectMatch) { return perfectMatch; } - var fromMatch = Transitions.FirstOrDefault(vt => - string.Equals(vt.From, oldStateName) && - vt.To == null); - - if (fromMatch != null) + if (hasOld && Transitions.FirstOrDefault(Match(oldStateName, null)) is { } fromMatch) { return fromMatch; } - var toMatch = Transitions.FirstOrDefault(vt => - vt.From == null && - string.Equals(vt.To, newStateName)); + if (hasNew && Transitions.FirstOrDefault(Match(null, newStateName)) is { } newMatch) + { + return newMatch; + } + + return default; - return toMatch; + Func Match(string from, string to) + => tr => string.Equals(tr.From, oldStateName) && string.Equals(tr.To, newStateName); } internal void RefreshStateTriggers(bool force = false) { var newState = GetActiveTrigger(); var oldState = CurrentState; - if (!force && newState == oldState) + if (newState == oldState) { - return; + if (!force) + { + return; + } + else if (newState is null) + { + // The 'force' has no effect is both old and new states are 'null' + // (setting the parent for the first time in control's init) + // we however raise the state changed for backward compatibility. + OnStateChanged(); + return; + } } if (this.Log().IsEnabled(LogLevel.Debug)) @@ -347,13 +382,9 @@ void OnStateChanged() } var parent = this.GetParent() as IFrameworkElement; - GoToState(parent, newState, CurrentState, false, OnStateChanged); + GoToState(parent, newState, false, OnStateChanged); } - private void OnParentChanged(object instance, object key, DependencyObjectParentChangedEventArgs args) - { - RefreshStateTriggers(force: true); - } /// /// This method is not using LINQ for performance considerations. @@ -416,6 +447,7 @@ private VisualState GetActiveTrigger() return winnerState; } - public override string ToString() => Name ?? $""; + public override string ToString() + => Name ?? $""; } } diff --git a/src/Uno.UI/UI/Xaml/VisualStateManager.cs b/src/Uno.UI/UI/Xaml/VisualStateManager.cs index be9d4d50c4a4..18ef45317631 100644 --- a/src/Uno.UI/UI/Xaml/VisualStateManager.cs +++ b/src/Uno.UI/UI/Xaml/VisualStateManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Microsoft.Extensions.Logging; using Uno.Extensions; using Uno.Logging; using Uno.Diagnostics.Eventing; @@ -11,11 +12,12 @@ namespace Windows.UI.Xaml { public partial class VisualStateManager : DependencyObject { - private readonly static IEventProvider _trace = Tracing.Get(TraceProvider.Id); + private static readonly IEventProvider _trace = Tracing.Get(TraceProvider.Id); + private static readonly ILogger _log = typeof(VisualStateManager).Log(); public static class TraceProvider { - public readonly static Guid Id = Guid.Parse("{2F38E5F4-90A2-4872-BD49-3696F897BAD1}"); + public static readonly Guid Id = Guid.Parse("{2F38E5F4-90A2-4872-BD49-3696F897BAD1}"); public const int StoryBoard_GoToState = 1; } @@ -37,8 +39,7 @@ public static void SetVisualStateGroups(FrameworkElement obj, IList), @@ -92,7 +93,7 @@ internal static void SetVisualStateManager(IFrameworkElement obj, VisualStateMan obj.SetValue(VisualStateManagerProperty, value); } - internal static DependencyProperty VisualStateManagerProperty { get ; } = + internal static DependencyProperty VisualStateManagerProperty { get; } = DependencyProperty.RegisterAttached("VisualStateManager", typeof(VisualStateManager), typeof(VisualStateManager), new FrameworkPropertyMetadata(null)); #endregion @@ -100,12 +101,11 @@ internal static void SetVisualStateManager(IFrameworkElement obj, VisualStateMan public static bool GoToState(Control control, string stateName, bool useTransitions) { var templateRoot = control.GetTemplateRoot(); - - if (templateRoot == null) + if (templateRoot is null) { - if (typeof(VisualStateManager).Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_log.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { - typeof(VisualStateManager).Log().DebugFormat("Failed to set state [{0}], unable to find template root on [{1}]", stateName, control); + _log.DebugFormat("Failed to set state [{0}], unable to find template root on [{1}]", stateName, control); } return false; @@ -115,9 +115,9 @@ public static bool GoToState(Control control, string stateName, bool useTransiti { if (fe.GoToElementState(stateName, useTransitions)) { - if (typeof(VisualStateManager).Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_log.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { - typeof(VisualStateManager).Log().DebugFormat($"GoToElementStateCore({stateName}) override on [{control}]"); + _log.DebugFormat($"GoToElementStateCore({stateName}) override on [{control}]"); } return true; @@ -125,12 +125,11 @@ public static bool GoToState(Control control, string stateName, bool useTransiti } var groups = GetVisualStateGroups(templateRoot); - - if (groups == null) + if (groups is null) { - if (typeof(VisualStateManager).Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_log.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { - typeof(VisualStateManager).Log().DebugFormat("Failed to set state [{0}], no visual state group on [{1}]", stateName, control); + _log.DebugFormat("Failed to set state [{0}], no visual state group on [{1}]", stateName, control); } return false; @@ -138,54 +137,42 @@ public static bool GoToState(Control control, string stateName, bool useTransiti // Get all the groups with a state that matches the state name var (group, state) = GetValidGroupAndState(stateName, groups); - - if (group == null) + if (group is null) { - if (typeof(VisualStateManager).Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_log.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { - typeof(VisualStateManager).Log().DebugFormat("Failed to set state [{0}], there are no matching groups on [{1}]", stateName, control); + _log.DebugFormat("Failed to set state [{0}], there are no matching groups on [{1}]", stateName, control); } return false; } var vsm = GetVisualStateManager(control); - - if (vsm == null) + if (vsm is null) { - if (typeof(VisualStateManager).Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) + if (_log.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { - typeof(VisualStateManager).Log().DebugFormat("Failed to set state [{0}], there is no VisualStateManagr on [{1}]", stateName, control); + _log.DebugFormat("Failed to set state [{0}], there is no VisualStateManagr on [{1}]", stateName, control); } return false; } - var output = vsm.GoToStateCore(control, templateRoot, stateName, group, state, useTransitions); + + var output = templateRoot is FrameworkElement fwRoot + ? vsm.GoToStateCore(control, fwRoot, stateName, group, state, useTransitions) + : vsm.GoToStateCorePrivateBaseImplementation(control, group, state, useTransitions); // For backward compatibility! + #if __WASM__ TryAssignDOMVisualStates(groups, templateRoot); #endif return output; } - private static (VisualStateGroup, VisualState) GetValidGroupAndState(string stateName, IList groups) - { - foreach (var group in groups) - { - foreach (var state in group.States) - { - if (state.Name?.Equals(stateName) ?? false) - { - return (group, state); - } - } - } + protected virtual bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions) + => GoToStateCorePrivateBaseImplementation(control, group, state, useTransitions); - return (null, null); - } - - protected virtual bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions) => GoToStateCore(control, (IFrameworkElement) templateRoot, stateName, group, state, useTransitions); - private bool GoToStateCore(Control control, IFrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions) + private bool GoToStateCorePrivateBaseImplementation(Control control, VisualStateGroup group, VisualState state, bool useTransitions) { #if IS_UNO if (_trace.IsEnabled) @@ -196,7 +183,7 @@ private bool GoToStateCore(Control control, IFrameworkElement templateRoot, stri new[] { control.GetType()?.ToString(), control?.GetDependencyObjectId().ToString(), - stateName, + state.Name, useTransitions ? "UseTransitions" : "NoTransitions" } ); @@ -204,8 +191,7 @@ private bool GoToStateCore(Control control, IFrameworkElement templateRoot, stri #endif var originalState = group.CurrentState; - - if (VisualState.Equals(originalState, state)) + if (object.Equals(originalState, state)) { // Already in the right state return true; @@ -220,13 +206,10 @@ private bool GoToStateCore(Control control, IFrameworkElement templateRoot, stri group.GoToState( control, state, - originalState, useTransitions, () => { - var innerControl = wr?.Target as Control; - - if (innerControl != null) + if (wr?.Target is Control) { RaiseCurrentStateChanged(group, originalState, state); } @@ -270,5 +253,21 @@ internal static VisualState GetCurrentState(Control control, string groupName) return group?.CurrentState; } + + private static (VisualStateGroup, VisualState) GetValidGroupAndState(string stateName, IList groups) + { + foreach (var group in groups) + { + foreach (var state in group.States) + { + if (state.Name?.Equals(stateName) ?? false) + { + return (group, state); + } + } + } + + return (null, null); + } } } From 1af267b8e6341e4b7bf5fc3f146a3d59f16311ce Mon Sep 17 00:00:00 2001 From: David Date: Tue, 17 Aug 2021 15:41:59 -0400 Subject: [PATCH 3/7] fix(animation): Fix DAUKF not cancelling its frames when stopped --- .../UI/Xaml/Media/Animation/ObjectAnimationUsingKeyFrames.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/ObjectAnimationUsingKeyFrames.cs b/src/Uno.UI/UI/Xaml/Media/Animation/ObjectAnimationUsingKeyFrames.cs index 8055efaa9602..afca29af5c0b 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/ObjectAnimationUsingKeyFrames.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/ObjectAnimationUsingKeyFrames.cs @@ -251,9 +251,9 @@ private void Play() _scheduledFrames.Add( CoreDispatcher.Main.RunAsync( CoreDispatcherPriority.Normal, - async () => + async ct => { - await Task.Delay(dueTime); + await Task.Delay(dueTime, ct); update(); } ) From c7fba6ad970e6d62047ad9799c65c35b75f0bad9 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 18 Aug 2021 08:50:45 -0400 Subject: [PATCH 4/7] chore: Config diff ignore --- build/PackageDiffIgnore.xml | 197 +++++++++--------- .../UI/Xaml/Media/Animation/Timeline.cs | 4 +- 2 files changed, 103 insertions(+), 98 deletions(-) diff --git a/build/PackageDiffIgnore.xml b/build/PackageDiffIgnore.xml index ca9b3833724b..d6a3f379ecc0 100644 --- a/build/PackageDiffIgnore.xml +++ b/build/PackageDiffIgnore.xml @@ -3832,105 +3832,110 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + - - + +