SukiStackPage.axaml.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Primitives;
  4. using Avalonia.Interactivity;
  5. using Avalonia.Markup.Xaml.MarkupExtensions;
  6. using Avalonia.Media;
  7. using SukiUI.Content;
  8. using System;
  9. using System.Collections.Generic;
  10. namespace SukiUI.Controls
  11. {
  12. public class SukiStackPage : TemplatedControl
  13. {
  14. public static readonly StyledProperty<object> ContentProperty =
  15. AvaloniaProperty.Register<SukiStackPage, object>(nameof(Content));
  16. public object Content
  17. {
  18. get => GetValue(ContentProperty);
  19. set => SetValue(ContentProperty, value);
  20. }
  21. public static readonly StyledProperty<int> LimitProperty =
  22. AvaloniaProperty.Register<SukiStackPage, int>(nameof(Limit), defaultValue: 5);
  23. public int Limit
  24. {
  25. get => GetValue(LimitProperty);
  26. set => SetValue(LimitProperty, value);
  27. }
  28. private static readonly DynamicResourceExtension ColorResource = new("SukiLowText");
  29. private StackPageModel?[]? _stackPages;
  30. private int _index = -1;
  31. private StackPanel? _stackHeaders;
  32. private readonly Stack<IDisposable> _disposables = new();
  33. public SukiStackPage()
  34. {
  35. _stackPages = new StackPageModel?[Limit];
  36. }
  37. protected override void OnLoaded(RoutedEventArgs e)
  38. {
  39. base.OnLoaded(e);
  40. UpdateHeaders();
  41. }
  42. protected override void OnUnloaded(RoutedEventArgs e)
  43. {
  44. base.OnUnloaded(e);
  45. while (_disposables.Count > 0)
  46. _disposables.Pop().Dispose();
  47. }
  48. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  49. {
  50. base.OnApplyTemplate(e);
  51. if (e.NameScope.Get<StackPanel>("StackHeader") is { } stackHeaders)
  52. _stackHeaders = stackHeaders;
  53. }
  54. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  55. {
  56. base.OnPropertyChanged(change);
  57. if (change.Property == ContentProperty)
  58. UpdateContentChanged(change.NewValue);
  59. else if (change.Property == LimitProperty && change.NewValue is int newLimit)
  60. {
  61. if (newLimit < 2)
  62. {
  63. Limit = 2;
  64. return;
  65. }
  66. UpdateStackArray(newLimit);
  67. }
  68. }
  69. private void UpdateStackArray(int newLimit)
  70. {
  71. var newArr = new StackPageModel?[newLimit];
  72. Array.Copy(_stackPages!, 0, newArr, 0, Math.Min(newArr.Length, _stackPages!.Length));
  73. _stackPages = newArr;
  74. var indexOfLastNotNull = Array.FindIndex(_stackPages, x => x is null) - 1;
  75. _index = indexOfLastNotNull > -1 ? indexOfLastNotNull : _stackPages.Length - 1;
  76. Content = _stackPages[_index]!.Content;
  77. }
  78. private void UpdateContentChanged(object? newVal)
  79. {
  80. if (newVal is null) return;
  81. if (_stackPages is null) return;
  82. var indexOfExists = Array.FindIndex(_stackPages, x => x is not null && x.Content == newVal);
  83. if (indexOfExists >= 0)
  84. {
  85. UnwindToIndex(indexOfExists);
  86. UpdateHeaders();
  87. return;
  88. }
  89. StackPageModel model;
  90. if (newVal is ISukiStackPageTitleProvider stackPageVm)
  91. {
  92. model = new StackPageModel(stackPageVm.Title, stackPageVm);
  93. }
  94. else if (newVal is Control c)
  95. {
  96. if (c.Name is not null)
  97. model = new StackPageModel(c.Name, newVal);
  98. else
  99. {
  100. model = new StackPageModel(c.Name, newVal);
  101. c.AttachedToVisualTree += (sender, args) => model.Title = c.Name;
  102. }
  103. }
  104. else
  105. model = new StackPageModel(newVal.GetType().Name, newVal);
  106. if (_index >= _stackPages.Length - 1)
  107. ShiftLeft();
  108. _index++;
  109. _stackPages[_index] = model;
  110. UpdateHeaders();
  111. }
  112. private void UnwindToIndex(int index)
  113. {
  114. Array.Copy(_stackPages, 0, _stackPages, 0, index + 1);
  115. _index = index;
  116. }
  117. private void ShiftLeft()
  118. {
  119. Array.Copy(_stackPages, 1, _stackPages, 0, _stackPages.Length - 1);
  120. _index = _stackPages.Length - 2;
  121. }
  122. private void UpdateHeaders()
  123. {
  124. if (_stackHeaders is null) return;
  125. _stackHeaders.Children.Clear();
  126. if (_index == -1 || _stackPages?[0] == null) return;
  127. for (var i = 0; i < _index; i++)
  128. {
  129. AddLowHeader(_stackPages[i]!);
  130. AddChevron();
  131. }
  132. AddStrongHeader(_stackPages[_index]!.Title);
  133. }
  134. private void AddChevron()
  135. {
  136. var pathIcon = new PathIcon
  137. {
  138. Data = Icons.ChevronRight,
  139. Height = 15,
  140. Width = 15,
  141. Margin = new Thickness(15, -3, 15, 0),
  142. HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center,
  143. VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
  144. Classes = { "Flippable" }
  145. };
  146. _disposables.Push(pathIcon.Bind(ForegroundProperty, ColorResource));
  147. _stackHeaders!.Children.Add(pathIcon);
  148. }
  149. private void AddLowHeader(StackPageModel model)
  150. {
  151. var button = new TextBlock()
  152. {
  153. Classes = { "h2" },
  154. Text = model.Title,
  155. };
  156. _disposables.Push(button.Bind(ForegroundProperty, ColorResource));
  157. button.PointerReleased += (_, _) =>
  158. Content = model.Content;
  159. button.PointerEntered += (_, _) =>
  160. button.RenderTransform = new ScaleTransform() { ScaleX = 1.02, ScaleY = 1.02 };
  161. button.PointerExited += (_, _) =>
  162. button.RenderTransform = new ScaleTransform() { ScaleX = 1, ScaleY = 1 };
  163. _stackHeaders!.Children.Add(button);
  164. }
  165. private void AddStrongHeader(string s)
  166. {
  167. _stackHeaders!.Children.Add(new TextBlock()
  168. {
  169. Classes = { "h2" },
  170. Text = s
  171. });
  172. }
  173. }
  174. internal record StackPageModel(string Title, object Content)
  175. {
  176. public string Title { get; set; } = Title;
  177. public object Content { get; } = Content;
  178. }
  179. public interface ISukiStackPageTitleProvider
  180. {
  181. public string Title { get; }
  182. }
  183. }