SukiTransitioningContentControl.axaml.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. using System;
  2. using System.Threading;
  3. using Avalonia;
  4. using Avalonia.Animation;
  5. using Avalonia.Controls;
  6. using Avalonia.Controls.Presenters;
  7. using Avalonia.Controls.Primitives;
  8. using Avalonia.Interactivity;
  9. using Avalonia.Media;
  10. using Avalonia.Styling;
  11. using Avalonia.Threading;
  12. namespace SukiUI.Controls
  13. {
  14. // TODO: This needs fairly significant work to make a bit more bomb proof
  15. // There are probably some more gains that can be made in terms of performance.
  16. // Unfortunately we're still bound by the arrange of controls having to happen on the main thread.
  17. public class SukiTransitioningContentControl : TemplatedControl
  18. {
  19. internal static readonly StyledProperty<object?> FirstBufferProperty =
  20. AvaloniaProperty.Register<SukiTransitioningContentControl, object?>(nameof(FirstBuffer));
  21. internal object? FirstBuffer
  22. {
  23. get => GetValue(FirstBufferProperty);
  24. set => SetValue(FirstBufferProperty, value);
  25. }
  26. internal static readonly StyledProperty<object?> SecondBufferProperty =
  27. AvaloniaProperty.Register<SukiTransitioningContentControl, object?>(nameof(SecondBuffer));
  28. internal object? SecondBuffer
  29. {
  30. get => GetValue(SecondBufferProperty);
  31. set => SetValue(SecondBufferProperty, value);
  32. }
  33. public static readonly StyledProperty<object?> ContentProperty = AvaloniaProperty.Register<SukiTransitioningContentControl, object?>(nameof(Content));
  34. public object? Content
  35. {
  36. get => GetValue(ContentProperty);
  37. set => SetValue(ContentProperty, value);
  38. }
  39. private bool _isFirstBufferActive;
  40. private ContentPresenter? _firstBuffer = null;
  41. private ContentPresenter? _secondBuffer = null;
  42. private static readonly Animation FadeIn;
  43. private static readonly Animation FadeOut;
  44. private ContentPresenter? To => _isFirstBufferActive ? _firstBuffer : _secondBuffer;
  45. private ContentPresenter? From => _isFirstBufferActive ? _secondBuffer : _firstBuffer;
  46. private object? _contentBeforeApplied;
  47. static SukiTransitioningContentControl()
  48. {
  49. FadeIn = new Animation
  50. {
  51. Duration = TimeSpan.FromMilliseconds(400),
  52. Children =
  53. {
  54. new KeyFrame()
  55. {
  56. Setters =
  57. {
  58. new Setter
  59. {
  60. Property = OpacityProperty,
  61. Value = 0d
  62. }
  63. },
  64. Cue = new Cue(0d)
  65. },
  66. new KeyFrame()
  67. {
  68. Setters =
  69. {
  70. new Setter
  71. {
  72. Property = OpacityProperty,
  73. Value = 1d
  74. }
  75. },
  76. Cue = new Cue(1d)
  77. }
  78. },
  79. FillMode = FillMode.Forward
  80. };
  81. FadeOut = new Animation
  82. {
  83. Duration = TimeSpan.FromMilliseconds(400),
  84. Children =
  85. {
  86. new KeyFrame()
  87. {
  88. Setters =
  89. {
  90. new Setter
  91. {
  92. Property = OpacityProperty,
  93. Value = 1d
  94. }
  95. },
  96. Cue = new Cue(0d)
  97. },
  98. new KeyFrame()
  99. {
  100. Setters =
  101. {
  102. new Setter
  103. {
  104. Property = OpacityProperty,
  105. Value = 0d
  106. }
  107. },
  108. Cue = new Cue(1d)
  109. }
  110. },
  111. FillMode = FillMode.Forward
  112. };
  113. FadeIn.Duration = FadeOut.Duration = TimeSpan.FromMilliseconds(250);
  114. }
  115. private CancellationTokenSource _animCancellationToken = new();
  116. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  117. {
  118. base.OnPropertyChanged(change);
  119. if(change.Property == ContentProperty)
  120. PushContent(change.NewValue);
  121. }
  122. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  123. {
  124. base.OnApplyTemplate(e);
  125. if (e.NameScope.Get<ContentPresenter>("PART_FirstBufferControl") is { } fBuff)
  126. _firstBuffer = fBuff;
  127. if (e.NameScope.Get<ContentPresenter>("PART_SecondBufferControl") is { } sBuff)
  128. _secondBuffer = sBuff;
  129. if (_contentBeforeApplied != null)
  130. {
  131. PushContent(_contentBeforeApplied);
  132. _contentBeforeApplied = null;
  133. }
  134. }
  135. private void PushContent(object? content)
  136. {
  137. if (To is null || From is null)
  138. {
  139. _contentBeforeApplied = content;
  140. return;
  141. }
  142. try
  143. {
  144. _animCancellationToken.Cancel();
  145. _animCancellationToken.Dispose();
  146. }
  147. catch
  148. {
  149. }
  150. _animCancellationToken = new CancellationTokenSource();
  151. if (_isFirstBufferActive) SecondBuffer = content;
  152. else FirstBuffer = content;
  153. _isFirstBufferActive = !_isFirstBufferActive;
  154. try
  155. {
  156. FadeOut.RunAsync(From, _animCancellationToken.Token).ContinueWith(_ =>
  157. {
  158. Dispatcher.UIThread.Invoke(() =>
  159. {
  160. From.IsHitTestVisible = false;
  161. if (_isFirstBufferActive) SecondBuffer = null;
  162. else FirstBuffer = null;
  163. });
  164. });
  165. FadeIn.RunAsync(To, _animCancellationToken.Token).ContinueWith(_ =>
  166. Dispatcher.UIThread.Invoke(() => To.IsHitTestVisible = true));
  167. }
  168. catch
  169. {
  170. // ignored
  171. }
  172. }
  173. protected override void OnUnloaded(RoutedEventArgs e)
  174. {
  175. base.OnUnloaded(e);
  176. _animCancellationToken.Dispose();
  177. }
  178. }
  179. }