private static void RegisterElementEvents(FrameworkElement element, IAnimationSettings settings, bool useSecondarySettings = false) { var eventName = settings?.Event ?? nameof(FrameworkElement.Loaded); if (eventName.Equals(AnimationSettings.None, StringComparison.OrdinalIgnoreCase)) { // Do nothing for "None" } else if (eventName.Equals(nameof(FrameworkElement.Visibility), StringComparison.OrdinalIgnoreCase)) { element .Observe(FrameworkElement.VisibilityProperty) .Where(_ => element.Visibility == Visibility.Visible) .TakeUntil(element.Events().Unloaded) .Subscribe( _ => PrepareAnimations(element, useSecondaryAnimation: useSecondarySettings), ex => Logger?.LogError($"Error on subscription to the {nameof(FrameworkElement.Visibility)} changes of {nameof(FrameworkElement)}", ex), () => Cleanup(element) ); } else if (eventName.Equals(nameof(FrameworkElement.DataContextChanged), StringComparison.OrdinalIgnoreCase)) { element .Events() .DataContextChanged .DistinctUntilChanged(args => args.EventArgs.NewValue) .TakeUntil(element.Events().Unloaded) .Subscribe( args => PrepareAnimations(args.Sender as FrameworkElement, useSecondaryAnimation: useSecondarySettings), ex => Logger?.LogError($"Error on subscription to the {nameof(FrameworkElement.DataContextChanged)} event.", ex), () => Cleanup(element) ); } else { Observable.FromEventPattern(element, eventName) .TakeUntil(element.Events().Unloaded) .Subscribe( args => PrepareAnimations(args.Sender as FrameworkElement, useSecondaryAnimation: useSecondarySettings), ex => Logger?.LogError($"Error on subscription to the {eventName} event.", ex), () => Cleanup(element)); } }
static IObservable<bool> TryFocusImpl(FrameworkElement element, Func<FrameworkElement, bool> focusAction) { if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) { return Observable.Return(false); } var elementObs = element.IsLoaded ? Observable.Return(element) : element.Events().Loaded .FirstAsync() .Timeout(TimeSpan.FromSeconds(1), RxApp.MainThreadScheduler) .Select(_ => element); return elementObs .SelectMany(e => { var focusObs = Observable.Defer(() => { if (focusAction(element)) return Observable.Return(true); return Observable.Throw<bool>(new InvalidOperationException("Could not move focus")); }); // MoveFocus almost always requires its descendant elements to be fully loaded, we // have no way of knowing if they are so we'll try a few times before bailing out. return focusObs.RetryWithBackoffStrategy( retryCount: 5, strategy: i => TimeSpan.FromMilliseconds(i * 50), scheduler: RxApp.MainThreadScheduler ); }) .Catch(Observable.Return(false)) .FirstAsync(); }
private static void RegisterElementEvents(FrameworkElement element, IAnimationSettings settings, bool useSecondarySettings = false) { switch (settings?.Event ?? AnimationSettings.DEFAULT_EVENT) { case EventType.Loaded: { element .Events() .LoadedUntilUnloaded .Subscribe( args => PrepareAnimations(args.Sender as FrameworkElement, useSecondaryAnimation: useSecondarySettings), ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.Loaded)} event of {nameof(FrameworkElement)}", ex), () => Cleanup(element) ); break; } case EventType.Visibility: { element .Observe(FrameworkElement.VisibilityProperty) .Where(_ => element.Visibility == Visibility.Visible) .TakeUntil(element.Events().UnloadedMaterialized) .Subscribe( _ => PrepareAnimations(element, useSecondaryAnimation: useSecondarySettings), ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.Visibility)} changes of {nameof(FrameworkElement)}", ex), () => Cleanup(element) ); break; } case EventType.DataContextChanged: { element .Events() .DataContextChanged .TakeUntil(element.Events().UnloadedMaterialized) .Subscribe( args => PrepareAnimations(args.Sender as FrameworkElement, useSecondaryAnimation: useSecondarySettings), ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.DataContextChanged)} event of {nameof(FrameworkElement)}", ex), () => Cleanup(element) ); break; } case EventType.PointerOver: { element .Events() .PointerEntered .TakeUntil(element.Events().UnloadedMaterialized) .Subscribe( args => PrepareAnimations(args.Sender as FrameworkElement, useSecondaryAnimation: useSecondarySettings), #if __WPF__ ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.MouseEnter)} event of {nameof(FrameworkElement)}", ex), #else ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.PointerEntered)} event of {nameof(FrameworkElement)}", ex), #endif () => Cleanup(element) ); break; } case EventType.PointerExit: { element .Events() .PointerExited .TakeUntil(element.Events().UnloadedMaterialized) .Subscribe( args => PrepareAnimations(args.Sender as FrameworkElement, useSecondaryAnimation: useSecondarySettings), #if __WPF__ ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.MouseLeave)} event of {nameof(FrameworkElement)}", ex), #else ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.PointerExited)} event of {nameof(FrameworkElement)}", ex), #endif () => Cleanup(element) ); break; } case EventType.GotFocus: { element .Events() .GotFocus .TakeUntil(element.Events().UnloadedMaterialized) .Subscribe( args => PrepareAnimations(args.Sender as FrameworkElement, useSecondaryAnimation: useSecondarySettings), ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.GotFocus)} event of {nameof(FrameworkElement)}", ex), () => Cleanup(element) ); break; } case EventType.LostFocus: { element .Events() .LostFocus .TakeUntil(element.Events().UnloadedMaterialized) .Subscribe( args => PrepareAnimations(args.Sender as FrameworkElement, useSecondaryAnimation: useSecondarySettings), ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.LostFocus)} event of {nameof(FrameworkElement)}", ex), () => Cleanup(element) ); break; } } }
// This can be called from the three main entry-points (Primary, Secondary, and StartWith) private static void InitializeElement(FrameworkElement element) { if (GetIsInitialized(element)) { return; } // Set IsInitialized to true to only run this code once per element SetIsInitialized(element, true); #if __UWP__ // The new way of handling translate animations (see Translation property section): // https://blogs.windows.com/buildingapps/2017/06/22/sweet-ui-made-possible-easy-windows-ui-windows-10-creators-update/ ElementCompositionPreview.SetIsTranslationEnabled(element, true); #endif element .Events() .LoadedUntilUnloaded .Take(1) .Subscribe( args => { // Perform validations on element's attached properties Validate(element); var startSettings = element.GetSettings(SettingsTarget.StartWith, getStartWithFunc: GetStartWith); // If any StartWith settings were specified, apply them if (startSettings != null) { element.ApplyInitialSettings((AnimationSettings)startSettings); } }, ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.Loaded)} event of {nameof(FrameworkElement)}", ex) ); element .Observe(UIElement.VisibilityProperty) .TakeUntil(element.Events().UnloadedMaterialized) .Subscribe( _ => { var isVisible = element.Visibility == Visibility.Visible; var elementGuid = GetElementGuid(element); if (isVisible && _actives.GetNextIdleActiveTimeline(elementGuid)?.Timeline is Timeline idle) { RunNextAnimation(idle, element); } }, ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.Visibility)} changes of {nameof(FrameworkElement)}", ex) ); #if __UWP__ element .Events() .SizeChanged .TakeUntil(element.Events().UnloadedMaterialized) .Subscribe( args => { // If the element child is a SpriteVisual, maintain its size so update any effects applied if (args.Sender is FrameworkElement elem && ElementCompositionPreview.GetElementChildVisual(elem) is SpriteVisual sprite) { sprite.Size = new Vector2((float)elem.ActualWidth, (float)elem.ActualHeight); } }, ex => Logger.ErrorException($"Error on subscription to the {nameof(FrameworkElement.SizeChanged)} event of {nameof(FrameworkElement)}", ex) ); #endif }