using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace Tango.Console { public class ConsoleTextBox : TextBox { private Border _caret; private ListBox _listSuggestions; private Popup _popup; private Popup _suggestionsPopup; static ConsoleTextBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(ConsoleTextBox), new FrameworkPropertyMetadata(typeof(ConsoleTextBox))); } public Brush CaretBottomBrush { get { return (Brush)GetValue(CaretBottomBrushProperty); } set { SetValue(CaretBottomBrushProperty, value); } } public static readonly DependencyProperty CaretBottomBrushProperty = DependencyProperty.Register("CaretBottomBrush", typeof(Brush), typeof(ConsoleTextBox), new PropertyMetadata(null)); public double CaretPosition { get { return (double)GetValue(CaretPositionProperty); } set { SetValue(CaretPositionProperty, value); } } public static readonly DependencyProperty CaretPositionProperty = DependencyProperty.Register("CaretPosition", typeof(double), typeof(ConsoleTextBox), new PropertyMetadata(0.0)); public bool IsSuggestionsOpened { get { return (bool)GetValue(IsSuggestionsOpenedProperty); } set { SetValue(IsSuggestionsOpenedProperty, value); } } public static readonly DependencyProperty IsSuggestionsOpenedProperty = DependencyProperty.Register("IsSuggestionsOpened", typeof(bool), typeof(ConsoleTextBox), new PropertyMetadata(false)); public List FilteredSuggestions { get { return (List)GetValue(FilteredSuggestionsProperty); } set { SetValue(FilteredSuggestionsProperty, value); } } public static readonly DependencyProperty FilteredSuggestionsProperty = DependencyProperty.Register("FilteredSuggestions", typeof(List), typeof(ConsoleTextBox), new PropertyMetadata(null)); public IEnumerable Suggestions { get { return (IEnumerable)GetValue(SuggestionsProperty); } set { SetValue(SuggestionsProperty, value); } } public static readonly DependencyProperty SuggestionsProperty = DependencyProperty.Register("Suggestions", typeof(IEnumerable), typeof(ConsoleTextBox), new PropertyMetadata(null)); public ConsoleSuggestion SelectedSuggestion { get { return (ConsoleSuggestion)GetValue(SelectedSuggestionProperty); } set { SetValue(SelectedSuggestionProperty, value); } } public static readonly DependencyProperty SelectedSuggestionProperty = DependencyProperty.Register("SelectedSuggestion", typeof(ConsoleSuggestion), typeof(ConsoleTextBox), new PropertyMetadata(null)); public int MaxSuggestions { get { return (int)GetValue(MaxSuggestionsProperty); } set { SetValue(MaxSuggestionsProperty, value); } } public static readonly DependencyProperty MaxSuggestionsProperty = DependencyProperty.Register("MaxSuggestions", typeof(int), typeof(ConsoleTextBox), new PropertyMetadata(1000)); public Brush SuggestionsBorderBrush { get { return (Brush)GetValue(SuggestionsBorderBrushProperty); } set { SetValue(SuggestionsBorderBrushProperty, value); } } public static readonly DependencyProperty SuggestionsBorderBrushProperty = DependencyProperty.Register("SuggestionsBorderBrush", typeof(Brush), typeof(ConsoleTextBox), new PropertyMetadata(null)); public Brush SuggestionsBackground { get { return (Brush)GetValue(SuggestionsBackgroundProperty); } set { SetValue(SuggestionsBackgroundProperty, value); } } public static readonly DependencyProperty SuggestionsBackgroundProperty = DependencyProperty.Register("SuggestionsBackground", typeof(Brush), typeof(ConsoleTextBox), new PropertyMetadata(null)); public Brush SuggestionsForeground { get { return (Brush)GetValue(SuggestionsForegroundProperty); } set { SetValue(SuggestionsForegroundProperty, value); } } public static readonly DependencyProperty SuggestionsForegroundProperty = DependencyProperty.Register("SuggestionsForeground", typeof(Brush), typeof(ConsoleTextBox), new PropertyMetadata(null)); public ConsoleTextBox() { SelectionChanged += (sender, e) => MoveCustomCaret(); LostFocus += (sender, e) => _caret.Visibility = Visibility.Collapsed; GotKeyboardFocus += (sender, e) => _caret.Visibility = Visibility.Visible; LostKeyboardFocus += (sender, e) => _caret.Visibility = Visibility.Collapsed; Suggestions = ConsoleDictionary.GetKnownCommands().Select(x => new ConsoleSuggestion() { Name = x.Name, Description = x.Description }).ToObservableCollection(); } protected override void OnPreviewKeyDown(KeyEventArgs e) { if (IsSuggestionsOpened) { if (e.Key == Key.Down) { if (_listSuggestions.SelectedIndex < _listSuggestions.Items.Count - 1) { _listSuggestions.SelectedIndex++; _listSuggestions.ScrollIntoView(SelectedSuggestion); _popup.IsOpen = false; if (SelectedSuggestion != null && SelectedSuggestion.Description != null) { _popup.IsOpen = FilteredSuggestions.Count > 0; } } e.Handled = true; return; } else if (e.Key == Key.Up) { if (_listSuggestions.SelectedIndex > 0) { _listSuggestions.SelectedIndex--; _listSuggestions.ScrollIntoView(SelectedSuggestion); _popup.IsOpen = false; if (SelectedSuggestion != null && SelectedSuggestion.Description != null) { _popup.IsOpen = FilteredSuggestions.Count > 0; } } e.Handled = true; return; } else if (e.Key == Key.Enter) { var selectedItem = (_listSuggestions.SelectedItem as ConsoleSuggestion); if (selectedItem != null) { var words = Text.Split(' ').ToList(); if (words.Count > 1) { words = words.Take(words.Count - 1).ToList(); Text = String.Join(" ", words) + " " + selectedItem.Name; } else { Text = selectedItem.Name; } IsSuggestionsOpened = false; CaretIndex = Text.Length; e.Handled = true; return; } IsSuggestionsOpened = false; } } base.OnPreviewKeyDown(e); } protected override void OnTextChanged(TextChangedEventArgs e) { base.OnTextChanged(e); try { if (CaretIndex == Text.Length) { String lastWord = Text.Split(' ').LastOrDefault(); lastWord?.Trim(); if (Suggestions != null) { FilteredSuggestions = Suggestions.Where(x => (lastWord.IsNotNullOrEmpty() && x.Name.ToLower().StartsWith(lastWord.ToLower())) || Text.EndsWith(" ")).OrderBy(x => x.Name).Take(MaxSuggestions).ToList(); if (Text.Contains(" ")) { FilteredSuggestions = FilteredSuggestions.Where(x => x.Type != ConsoleSuggestionType.Command).ToList(); } IsSuggestionsOpened = FilteredSuggestions.Count > 0; _popup.IsOpen = false; _popup.IsOpen = FilteredSuggestions.Count > 0 && SelectedSuggestion != null; } } else { IsSuggestionsOpened = false; _popup.IsOpen = false; } } catch (Exception ex) { Debug.WriteLine(ex); Debugger.Break(); } } protected override void OnSelectionChanged(RoutedEventArgs e) { base.OnSelectionChanged(e); if (CaretIndex < Text.Length) { IsSuggestionsOpened = false; _popup.IsOpen = false; } } public override void OnApplyTemplate() { base.OnApplyTemplate(); _caret = GetTemplateChild("PART_Caret") as Border; _listSuggestions = GetTemplateChild("PART_listSuggestions") as ListBox; _popup = GetTemplateChild("PART_popup") as Popup; _suggestionsPopup = GetTemplateChild("PART_SuggestionsPopup") as Popup; } private void MoveCustomCaret() { var caretLocation = GetRectFromCharacterIndex(CaretIndex).Location; if (!double.IsInfinity(caretLocation.X)) { Canvas.SetLeft(_caret, caretLocation.X); CaretPosition = caretLocation.X; _suggestionsPopup.PlacementRectangle = new Rect(CaretPosition + 10, 20, 0, 0); } } } }