ButtonStrip.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Metadata;
  4. using Avalonia.Controls.Primitives;
  5. using Avalonia.LogicalTree;
  6. using Avalonia.Media;
  7. using Avalonia.Metadata;
  8. using Avalonia.Threading;
  9. using CommunityToolkit.Mvvm.Input;
  10. using System;
  11. using System.Collections;
  12. using System.Collections.Generic;
  13. using System.Collections.ObjectModel;
  14. using System.Collections.Specialized;
  15. using System.Linq;
  16. using System.Text;
  17. using System.Threading.Tasks;
  18. using System.Windows.Input;
  19. namespace InABox.Avalonia.Components;
  20. public partial class ButtonStrip : TemplatedControl
  21. {
  22. public static readonly StyledProperty<IBrush?> SelectedBackgroundProperty = AvaloniaProperty.Register<ButtonStripItem, IBrush?>(
  23. nameof(SelectedBackground));
  24. public static readonly StyledProperty<IBrush?> SelectedForegroundProperty = AvaloniaProperty.Register<ButtonStripItem, IBrush?>(
  25. nameof(SelectedForeground));
  26. public static readonly StyledProperty<double> ItemSpacingProperty = AvaloniaProperty.Register<ButtonStripItem, double>(
  27. nameof(ItemSpacing));
  28. public static readonly StyledProperty<ObservableCollection<ButtonStripItem>> ItemsProperty =
  29. AvaloniaProperty.Register<ButtonStripItem, ObservableCollection<ButtonStripItem>>(nameof(Items));
  30. public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
  31. AvaloniaProperty.Register<ButtonStripItem, IEnumerable?>(nameof(ItemsSource));
  32. public static readonly StyledProperty<ICommand> SelectedCommandProperty =
  33. AvaloniaProperty.Register<ButtonStrip, ICommand>(nameof(SelectedCommand));
  34. public static readonly StyledProperty<object?> SelectedItemProperty =
  35. AvaloniaProperty.Register<ButtonStrip, object?>(nameof(SelectedItem));
  36. public IBrush? SelectedBackground
  37. {
  38. get => GetValue(SelectedBackgroundProperty);
  39. set => SetValue(SelectedBackgroundProperty, value);
  40. }
  41. public IBrush? SelectedForeground
  42. {
  43. get => GetValue(SelectedForegroundProperty);
  44. set => SetValue(SelectedForegroundProperty, value);
  45. }
  46. public double ItemSpacing
  47. {
  48. get => GetValue(ItemSpacingProperty);
  49. set => SetValue(ItemSpacingProperty, value);
  50. }
  51. public ICommand SelectedCommand
  52. {
  53. get => GetValue(SelectedCommandProperty);
  54. set => SetValue(SelectedCommandProperty, value);
  55. }
  56. public IEnumerable? ItemsSource
  57. {
  58. get => GetValue(ItemsSourceProperty);
  59. set => SetValue(ItemsSourceProperty, value);
  60. }
  61. [Content]
  62. public ObservableCollection<ButtonStripItem> Items
  63. {
  64. get => GetValue(ItemsProperty);
  65. set => SetValue(ItemsProperty, value);
  66. }
  67. private ButtonStripItem? SelectedButton
  68. {
  69. get => Items.FirstOrDefault(x => x.Selected);
  70. set
  71. {
  72. foreach(var item in Items)
  73. {
  74. item.Selected = item == value;
  75. }
  76. SelectedItem = value?.Tag;
  77. }
  78. }
  79. public object? SelectedItem
  80. {
  81. get => GetValue(SelectedItemProperty);
  82. set => SetValue(SelectedItemProperty, value);
  83. }
  84. public int SelectedIndex
  85. {
  86. get => SelectedButton != null && Items.Contains(SelectedButton) ? Items.IndexOf(SelectedButton) : -1;
  87. set => SelectedButton = value > -1 && value < Items.Count ? Items[value] : null;
  88. }
  89. public event EventHandler? SelectionChanged;
  90. static ButtonStrip()
  91. {
  92. ItemsProperty.Changed.AddClassHandler<ButtonStrip>(Items_Changed);
  93. ItemsSourceProperty.Changed.AddClassHandler<ButtonStrip>(ItemsSource_Changed);
  94. }
  95. private static void ItemsSource_Changed(ButtonStrip strip, AvaloniaPropertyChangedEventArgs args)
  96. {
  97. if(strip.ItemsSource is INotifyCollectionChanged notify)
  98. {
  99. notify.CollectionChanged += (sender, e) => strip.RebuildItems();
  100. }
  101. strip.RebuildItems();
  102. }
  103. private void RebuildItems()
  104. {
  105. Items.Clear();
  106. if(ItemsSource is not null)
  107. {
  108. foreach(var item in ItemsSource)
  109. {
  110. if(item is ButtonStripItem btn)
  111. {
  112. Items.Add(btn);
  113. }
  114. else
  115. {
  116. Items.Add(new(item?.ToString() ?? "") { Tag = item });
  117. }
  118. }
  119. }
  120. }
  121. private static void Items_Changed(ButtonStrip strip, AvaloniaPropertyChangedEventArgs args)
  122. {
  123. strip.LogicalChildren.Clear();
  124. if (strip.Items is not null)
  125. {
  126. strip.SelectedButton = strip.Items.FirstOrDefault();
  127. strip.LogicalChildren.AddRange(strip.Items);
  128. foreach(var item in strip.Items)
  129. {
  130. item.Command = strip.ItemSelectedCommand;
  131. }
  132. }
  133. }
  134. public ButtonStrip()
  135. {
  136. Items = new();
  137. Items.CollectionChanged += Items_CollectionChanged;
  138. }
  139. private void Items_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  140. {
  141. SelectedButton ??= Items.FirstOrDefault();
  142. switch (e.Action)
  143. {
  144. case NotifyCollectionChangedAction.Add:
  145. AddControlItemsToLogicalChildren(e.NewItems);
  146. break;
  147. case NotifyCollectionChangedAction.Remove:
  148. RemoveControlItemsFromLogicalChildren(e.OldItems);
  149. break;
  150. }
  151. foreach(var item in Items)
  152. {
  153. item.Command = ItemSelectedCommand;
  154. }
  155. }
  156. private void AddControlItemsToLogicalChildren(IEnumerable? items)
  157. {
  158. if (items is null) return;
  159. List<ILogical>? toAdd = null;
  160. foreach(var i in items)
  161. {
  162. if(i is Control control && !LogicalChildren.Contains(control))
  163. {
  164. toAdd ??= new();
  165. toAdd.Add(control);
  166. }
  167. }
  168. if(toAdd is not null)
  169. {
  170. LogicalChildren.AddRange(toAdd);
  171. }
  172. }
  173. private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
  174. {
  175. if (items is null) return;
  176. List<ILogical>? toRemove = null;
  177. foreach(var i in items)
  178. {
  179. if(i is Control control)
  180. {
  181. toRemove ??= new();
  182. toRemove.Add(control);
  183. }
  184. }
  185. if(toRemove is not null)
  186. {
  187. LogicalChildren.RemoveAll(toRemove);
  188. }
  189. }
  190. [RelayCommand]
  191. private void ItemSelected(ButtonStripItem item)
  192. {
  193. var children = this.GetLogicalChildren().ToArray();
  194. SelectedButton = item;
  195. SelectionChanged?.Invoke(this, new());
  196. SelectedCommand?.Execute(item.Tag);
  197. }
  198. public void AddRange(IEnumerable<string> items)
  199. {
  200. foreach(var item in items)
  201. {
  202. Items.Add(new(item));
  203. }
  204. }
  205. }
  206. [PseudoClasses(":selected")]
  207. public class ButtonStripItem : TemplatedControl
  208. {
  209. public static StyledProperty<string> TextProperty = AvaloniaProperty.Register<ButtonStripItem, string>(nameof(Text));
  210. public static StyledProperty<bool> SelectedProperty = AvaloniaProperty.Register<ButtonStripItem, bool>(nameof(Selected));
  211. public static StyledProperty<int> IndexProperty = AvaloniaProperty.Register<ButtonStripItem, int>(nameof(Index));
  212. public static StyledProperty<ICommand> CommandProperty = AvaloniaProperty.Register<ButtonStripItem, ICommand>(nameof(Command));
  213. public string Text
  214. {
  215. get => GetValue(TextProperty);
  216. set => SetValue(TextProperty, value);
  217. }
  218. public bool Selected
  219. {
  220. get => GetValue(SelectedProperty);
  221. set => SetValue(SelectedProperty, value);
  222. }
  223. public int Index
  224. {
  225. get => GetValue(IndexProperty);
  226. set => SetValue(IndexProperty, value);
  227. }
  228. public ICommand Command
  229. {
  230. get => GetValue(CommandProperty);
  231. set => SetValue(CommandProperty, value);
  232. }
  233. public ButtonStripItem()
  234. {
  235. }
  236. public ButtonStripItem(string text)
  237. {
  238. Text = text;
  239. }
  240. static ButtonStripItem()
  241. {
  242. SelectedProperty.Changed.AddClassHandler<ButtonStripItem>(Selected_Changed);
  243. }
  244. private static void Selected_Changed(ButtonStripItem item, AvaloniaPropertyChangedEventArgs args)
  245. {
  246. item.PseudoClasses.Set(":selected", item.Selected);
  247. }
  248. }