SukiSideMenu.axaml.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Primitives;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using Avalonia.Interactivity;
  8. using Avalonia.Layout;
  9. using SukiUI.Enums;
  10. using Avalonia.Controls.Templates;
  11. namespace SukiUI.Controls;
  12. public class SukiSideMenu : SelectingItemsControl
  13. {
  14. public static readonly StyledProperty<string?> SearchTextProperty =
  15. AvaloniaProperty.Register<SukiSideMenu, string?>(nameof(SearchText));
  16. public static readonly StyledProperty<bool> IsSearchEnabledProperty =
  17. AvaloniaProperty.Register<SukiSideMenu, bool>(nameof(IsSearchEnabled), defaultValue: false);
  18. public static readonly StyledProperty<bool> SidebarToggleEnabledProperty =
  19. AvaloniaProperty.Register<SukiWindow, bool>(nameof(SidebarToggleEnabled), defaultValue: true);
  20. public bool SidebarToggleEnabled
  21. {
  22. get => GetValue(SidebarToggleEnabledProperty);
  23. set => SetValue(SidebarToggleEnabledProperty, value);
  24. }
  25. public string? SearchText
  26. {
  27. get => GetValue(SearchTextProperty);
  28. set => SetValue(SearchTextProperty, value);
  29. }
  30. public bool IsSearchEnabled
  31. {
  32. get => GetValue(IsSearchEnabledProperty);
  33. set => SetValue(IsSearchEnabledProperty, value);
  34. }
  35. public static readonly StyledProperty<bool> IsToggleButtonVisibleProperty =
  36. AvaloniaProperty.Register<SukiSideMenu, bool>(nameof(IsToggleButtonVisible), defaultValue: true);
  37. public bool IsToggleButtonVisible
  38. {
  39. get => GetValue(IsToggleButtonVisibleProperty);
  40. set => SetValue(IsToggleButtonVisibleProperty, value);
  41. }
  42. public static readonly StyledProperty<bool> IsMenuExpandedProperty =
  43. AvaloniaProperty.Register<SukiSideMenu, bool>(nameof(IsMenuExpanded), defaultValue: true);
  44. public bool IsMenuExpanded
  45. {
  46. get => GetValue(IsMenuExpandedProperty);
  47. set => SetValue(IsMenuExpandedProperty, value);
  48. }
  49. public static readonly StyledProperty<int> OpenPaneLengthProperty =
  50. AvaloniaProperty.Register<SukiSideMenu, int>(nameof(OpenPaneLength), defaultValue: 220);
  51. public int OpenPaneLength
  52. {
  53. get => GetValue(OpenPaneLengthProperty);
  54. set => SetValue(OpenPaneLengthProperty, value switch
  55. {
  56. >= 200 => value,
  57. _ => throw new ArgumentOutOfRangeException($"OpenPaneLength must be greater than or equal to 200, but was {value}")
  58. });
  59. }
  60. public static readonly StyledProperty<HorizontalAlignment> TogglePaneButtonPositionProperty =
  61. AvaloniaProperty.Register<SukiSideMenu, HorizontalAlignment>(nameof(TogglePaneButtonPosition), defaultValue: HorizontalAlignment.Right);
  62. public SideMenuTogglePaneButtonPositionOptions TogglePaneButtonPosition
  63. {
  64. get => GetValue(TogglePaneButtonPositionProperty) switch
  65. {
  66. HorizontalAlignment.Right => SideMenuTogglePaneButtonPositionOptions.Right,
  67. HorizontalAlignment.Left => SideMenuTogglePaneButtonPositionOptions.Left,
  68. _ => SideMenuTogglePaneButtonPositionOptions.Right
  69. };
  70. set => SetValue(TogglePaneButtonPositionProperty, value switch
  71. {
  72. SideMenuTogglePaneButtonPositionOptions.Right => HorizontalAlignment.Right,
  73. SideMenuTogglePaneButtonPositionOptions.Left => HorizontalAlignment.Left,
  74. _ => HorizontalAlignment.Right
  75. });
  76. }
  77. public static readonly StyledProperty<bool> IsSelectedItemContentMovableProperty =
  78. AvaloniaProperty.Register<SukiSideMenu, bool>(nameof(IsSelectedItemContentMovable), defaultValue: true);
  79. public bool IsSelectedItemContentMovable
  80. {
  81. get => GetValue(IsSelectedItemContentMovableProperty);
  82. set => SetValue(IsSelectedItemContentMovableProperty, value);
  83. }
  84. public static readonly StyledProperty<double> HeaderMinHeightProperty =
  85. AvaloniaProperty.Register<SukiSideMenu, double>(nameof(HeaderMinHeight));
  86. public double HeaderMinHeight
  87. {
  88. get => GetValue(HeaderMinHeightProperty);
  89. set => SetValue(HeaderMinHeightProperty, value);
  90. }
  91. public static readonly StyledProperty<object?> HeaderContentProperty =
  92. AvaloniaProperty.Register<SukiSideMenu, object?>(nameof(HeaderContent));
  93. public object? HeaderContent
  94. {
  95. get => GetValue(HeaderContentProperty);
  96. set => SetValue(HeaderContentProperty, value);
  97. }
  98. public static readonly StyledProperty<object?> FooterContentProperty =
  99. AvaloniaProperty.Register<SukiSideMenu, object?>(nameof(FooterContent));
  100. public object? FooterContent
  101. {
  102. get => GetValue(FooterContentProperty);
  103. set => SetValue(FooterContentProperty, value);
  104. }
  105. private bool IsSpacerVisible => !IsMenuExpanded;
  106. private SukiTransitioningContentControl? _contentControl;
  107. private Grid? _spacer;
  108. public SukiSideMenu()
  109. {
  110. SelectionMode = SelectionMode.Single | SelectionMode.AlwaysSelected;
  111. }
  112. private void MenuExpandedClicked()
  113. {
  114. IsMenuExpanded = !IsMenuExpanded;
  115. if(_sideMenuItems.Any())
  116. foreach (var item in _sideMenuItems)
  117. item.IsTopMenuExpanded = IsMenuExpanded;
  118. else if(Items.FirstOrDefault() is SukiSideMenuItem)
  119. foreach (SukiSideMenuItem? item in Items)
  120. item!.IsTopMenuExpanded = IsMenuExpanded;
  121. }
  122. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  123. {
  124. base.OnApplyTemplate(e);
  125. if (Items.Any())
  126. {
  127. SelectedItem = Items.First();
  128. }
  129. e.NameScope.Get<Button>("PART_SidebarToggleButton").Click += (_, _) => MenuExpandedClicked();
  130. _contentControl = e.NameScope.Get<SukiTransitioningContentControl>("PART_TransitioningContentControl");
  131. _spacer = e.NameScope.Get<Grid>("PART_Spacer");
  132. if(_spacer != null) _spacer.IsVisible = IsSpacerVisible;
  133. }
  134. protected override void OnLoaded(RoutedEventArgs e)
  135. {
  136. base.OnLoaded(e);
  137. SetContentControlContent(SelectedItem);
  138. }
  139. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  140. {
  141. base.OnPropertyChanged(change);
  142. if (change.Property == SearchTextProperty)
  143. {
  144. FilterItems(change.GetNewValue<string>());
  145. }
  146. if (change.Property == SelectedItemProperty && _contentControl != null)
  147. SetContentControlContent(change.NewValue);
  148. if (change.Property == IsMenuExpandedProperty && _spacer != null)
  149. _spacer.IsVisible = IsSpacerVisible;
  150. }
  151. private void FilterItems(string search)
  152. {
  153. search = search.ToLower();
  154. foreach (var item in _sideMenuItems)
  155. {
  156. var header = item.Header?.ToLower() ?? "";
  157. if (header.Contains(search))
  158. {
  159. item.Show();
  160. }
  161. else
  162. {
  163. item.Hide();
  164. }
  165. }
  166. }
  167. private void SetContentControlContent(object? newContent)
  168. {
  169. if (_contentControl == null) return;
  170. _contentControl.Content = newContent switch
  171. {
  172. SukiSideMenuItem { PageContent: { } sukiMenuPageContent } => sukiMenuPageContent,
  173. _ => newContent
  174. };
  175. }
  176. public bool UpdateSelectionFromPointerEvent(Control source) => UpdateSelectionFromEventSource(source);
  177. protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
  178. {
  179. var menuItem = (ItemTemplate != null && ItemTemplate.Match(item) &&
  180. ItemTemplate.Build(item) is SukiSideMenuItem sukiMenuItem)
  181. ? sukiMenuItem
  182. : new SukiSideMenuItem();
  183. menuItem.IsContentMovable = IsSelectedItemContentMovable;
  184. _sideMenuItems.Add(menuItem);
  185. return menuItem;
  186. }
  187. private readonly List<SukiSideMenuItem> _sideMenuItems = new();
  188. protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
  189. {
  190. return NeedsContainer<SukiSideMenuItem>(item, out recycleKey);
  191. }
  192. }