CommandSearchControl.xaml.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. using PixiEditor.Helpers;
  2. using PixiEditor.Helpers.Extensions;
  3. using PixiEditor.Models.Commands;
  4. using PixiEditor.Models.Commands.Search;
  5. using PixiEditor.Models.DataHolders;
  6. using System.Collections.ObjectModel;
  7. using System.ComponentModel;
  8. using System.Text;
  9. using System.Windows;
  10. using System.Windows.Controls;
  11. using System.Windows.Input;
  12. using System.Windows.Threading;
  13. namespace PixiEditor.Views.UserControls.CommandSearch;
  14. #nullable enable
  15. public partial class CommandSearchControl : UserControl, INotifyPropertyChanged
  16. {
  17. public static readonly DependencyProperty SearchTermProperty =
  18. DependencyProperty.Register(nameof(SearchTerm), typeof(string), typeof(CommandSearchControl), new PropertyMetadata(OnSearchTermChange));
  19. public string SearchTerm
  20. {
  21. get => (string)GetValue(SearchTermProperty);
  22. set => SetValue(SearchTermProperty, value);
  23. }
  24. private string warnings = "";
  25. public string Warnings
  26. {
  27. get => warnings;
  28. set
  29. {
  30. warnings = value;
  31. PropertyChanged?.Invoke(this, new(nameof(Warnings)));
  32. PropertyChanged?.Invoke(this, new(nameof(HasWarnings)));
  33. }
  34. }
  35. public bool HasWarnings => Warnings != string.Empty;
  36. public RelayCommand ButtonClickedCommand { get; }
  37. public event PropertyChangedEventHandler? PropertyChanged;
  38. private SearchResult? selectedResult;
  39. public SearchResult? SelectedResult
  40. {
  41. get => selectedResult;
  42. private set
  43. {
  44. if (selectedResult is not null)
  45. selectedResult.IsSelected = false;
  46. if (value is not null)
  47. value.IsSelected = true;
  48. selectedResult = value;
  49. }
  50. }
  51. private SearchResult? mouseSelectedResult;
  52. public SearchResult? MouseSelectedResult
  53. {
  54. get => mouseSelectedResult;
  55. private set
  56. {
  57. if (mouseSelectedResult is not null)
  58. mouseSelectedResult.IsMouseSelected = false;
  59. if (value is not null)
  60. value.IsMouseSelected = true;
  61. mouseSelectedResult = value;
  62. }
  63. }
  64. public ObservableCollection<SearchResult> Results { get; } = new();
  65. public CommandSearchControl()
  66. {
  67. ButtonClickedCommand = new RelayCommand(_ =>
  68. {
  69. Hide();
  70. MouseSelectedResult?.Execute();
  71. MouseSelectedResult = null;
  72. });
  73. InitializeComponent();
  74. IsVisibleChanged += (_, args) =>
  75. {
  76. if (IsVisible)
  77. {
  78. Dispatcher.BeginInvoke(DispatcherPriority.Render, () =>
  79. {
  80. textBox.Focus();
  81. UpdateSearchResults();
  82. Mouse.Capture(this, CaptureMode.SubTree);
  83. });
  84. }
  85. };
  86. MouseDown += OnMouseDown;
  87. PreviewKeyDown += OnPreviewKeyDown;
  88. Loaded += (_, _) => UpdateSearchResults();
  89. }
  90. private void OnMouseDown(object sender, MouseButtonEventArgs e)
  91. {
  92. var pos = e.GetPosition(this);
  93. bool outside = pos.X < 0 || pos.Y < 0 || pos.X > ActualWidth || pos.Y > ActualHeight;
  94. if (outside)
  95. Hide();
  96. }
  97. private void UpdateSearchResults()
  98. {
  99. Results.Clear();
  100. (List<SearchResult> newResults, List<string> warnings) = CommandSearchControlHelper.ConstructSearchResults(SearchTerm);
  101. foreach (var result in newResults)
  102. Results.Add(result);
  103. Warnings = warnings.Aggregate(new StringBuilder(), static (builder, item) =>
  104. {
  105. builder.AppendLine(item);
  106. return builder;
  107. }).ToString();
  108. SelectedResult = Results.FirstOrDefault(x => x.CanExecute);
  109. }
  110. private void Hide()
  111. {
  112. FocusManager.SetFocusedElement(FocusManager.GetFocusScope(textBox), null);
  113. Keyboard.ClearFocus();
  114. Visibility = Visibility.Collapsed;
  115. ReleaseMouseCapture();
  116. }
  117. private void OnPreviewKeyDown(object sender, KeyEventArgs e)
  118. {
  119. e.Handled = true;
  120. if (e.Key == Key.Enter && SelectedResult is not null)
  121. {
  122. Hide();
  123. SelectedResult.Execute();
  124. SelectedResult = null;
  125. }
  126. else if (e.Key is Key.Down or Key.PageDown)
  127. {
  128. MoveSelection(1);
  129. }
  130. else if (e.Key is Key.Up or Key.PageUp)
  131. {
  132. MoveSelection(-1);
  133. }
  134. else if (e.Key == Key.Escape ||
  135. CommandController.Current.Commands["PixiEditor.Search.Toggle"].Shortcut
  136. == new KeyCombination(e.Key, Keyboard.Modifiers))
  137. {
  138. Hide();
  139. }
  140. else if (e.Key == Key.R && Keyboard.Modifiers == ModifierKeys.Control)
  141. {
  142. SearchTerm = "rgb(,,)";
  143. textBox.CaretIndex = 4;
  144. textBox.SelectionLength = 0;
  145. }
  146. else if (e.Key == Key.Space && SearchTerm.StartsWith("rgb") && char.IsDigit(SearchTerm[textBox.CaretIndex - 1]))
  147. {
  148. var prev = textBox.CaretIndex;
  149. if (SearchTerm.Length == textBox.CaretIndex || SearchTerm[textBox.CaretIndex] != ',')
  150. {
  151. SearchTerm = SearchTerm.Insert(textBox.CaretIndex, ",");
  152. }
  153. textBox.CaretIndex = prev + 1;
  154. }
  155. else
  156. {
  157. e.Handled = false;
  158. }
  159. }
  160. private void MoveSelection(int delta)
  161. {
  162. if (delta == 0)
  163. return;
  164. if (SelectedResult is null)
  165. {
  166. SelectedResult = Results.Where(x => x.CanExecute).First();
  167. return;
  168. }
  169. int newIndex = Results.IndexOf(SelectedResult) + delta;
  170. newIndex = (newIndex % Results.Count + Results.Count) % Results.Count;
  171. SelectedResult = delta > 0 ? Results.IndexOrNext(x => x.CanExecute, newIndex) : Results.IndexOrPrevious(x => x.CanExecute, newIndex);
  172. }
  173. private void Button_MouseMove(object sender, MouseEventArgs e)
  174. {
  175. var searchResult = ((Button)sender).DataContext as SearchResult;
  176. MouseSelectedResult = searchResult;
  177. }
  178. private static void OnSearchTermChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
  179. {
  180. CommandSearchControl control = ((CommandSearchControl)d);
  181. control.UpdateSearchResults();
  182. control.PropertyChanged?.Invoke(control, new PropertyChangedEventArgs(nameof(control.SearchTerm)));
  183. }
  184. }