From 435ec70088c4153125ab9dda2891b1dc91173878 Mon Sep 17 00:00:00 2001 From: Tung Huynh Date: Sat, 2 Jan 2021 16:38:32 -0800 Subject: [PATCH 001/102] add RichSuggestBox to repo --- .../PlainTextCommandBarFlyout.cs | 17 + .../RichSuggestBox/RichSuggestBox.Events.cs | 15 + .../RichSuggestBox.Properties.cs | 137 +++++ .../RichSuggestBox/RichSuggestBox.cs | 554 ++++++++++++++++++ .../RichSuggestBox/RichSuggestBox.xaml | 110 ++++ .../SuggestionChosenEventArgs.cs | 20 + .../RichSuggestBox/SuggestionInfo.cs | 59 ++ .../RichSuggestBox/SuggestionTokenFormat.cs | 18 + .../SuggestionsRequestedEventArgs.cs | 11 + 9 files changed, 941 insertions(+) create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/PlainTextCommandBarFlyout.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.xaml create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs create mode 100644 Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/PlainTextCommandBarFlyout.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/PlainTextCommandBarFlyout.cs new file mode 100644 index 00000000000..7adbc988c2e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/PlainTextCommandBarFlyout.cs @@ -0,0 +1,17 @@ +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + internal class PlainTextCommandBarFlyout : TextCommandBarFlyout + { + public PlainTextCommandBarFlyout() + { + Opening += (sender, o) => + { + PrimaryCommands.Clear(); + + // TODO: Limit Pasting to plain-text only + }; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs new file mode 100644 index 00000000000..73f6229aa30 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs @@ -0,0 +1,15 @@ +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + public partial class RichSuggestBox + { + public event TypedEventHandler SuggestionsRequested; + + public event TypedEventHandler SuggestionChosen; + + public event TypedEventHandler TextChanged; + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs new file mode 100644 index 00000000000..b5083430df5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs @@ -0,0 +1,137 @@ +using System.Collections.ObjectModel; +using Windows.UI.Text; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + public partial class RichSuggestBox + { + public static readonly DependencyProperty PlaceholderTextProperty = + DependencyProperty.Register( + nameof(PlaceholderText), + typeof(string), + typeof(RichSuggestBox), + new PropertyMetadata(string.Empty)); + + public static readonly DependencyProperty RichEditBoxStyleProperty = + DependencyProperty.Register( + nameof(RichEditBoxStyle), + typeof(Style), + typeof(RichSuggestBox), + new PropertyMetadata(null)); + + public static readonly DependencyProperty HeaderProperty = + DependencyProperty.Register( + nameof(Header), + typeof(object), + typeof(RichSuggestBox), + new PropertyMetadata(null)); + + public static readonly DependencyProperty DescriptionProperty = + DependencyProperty.Register( + nameof(Description), + typeof(object), + typeof(RichSuggestBox), + new PropertyMetadata(null)); + + public static readonly DependencyProperty TextWrappingProperty = + DependencyProperty.Register( + nameof(TextWrapping), + typeof(TextWrapping), + typeof(RichSuggestBox), + new PropertyMetadata(TextWrapping.NoWrap)); + + public static readonly DependencyProperty ClipboardCopyFormatProperty = + DependencyProperty.Register( + nameof(ClipboardCopyFormat), + typeof(RichEditClipboardFormat), + typeof(RichSuggestBox), + new PropertyMetadata(RichEditClipboardFormat.PlainText)); + + public static readonly DependencyProperty SuggestionBackgroundProperty = + DependencyProperty.Register( + nameof(SuggestionBackground), + typeof(SolidColorBrush), + typeof(RichSuggestBox), + new PropertyMetadata(null)); + + public static readonly DependencyProperty SuggestionForegroundProperty = + DependencyProperty.Register( + nameof(SuggestionForeground), + typeof(SolidColorBrush), + typeof(RichSuggestBox), + new PropertyMetadata(null)); + + public static readonly DependencyProperty PrefixesProperty = + DependencyProperty.Register( + nameof(Prefixes), + typeof(string), + typeof(RichSuggestBox), + new PropertyMetadata("@", OnPrefixesChanged)); + + public string PlaceholderText + { + get => (string)GetValue(PlaceholderTextProperty); + set => SetValue(PlaceholderTextProperty, value); + } + + public Style RichEditBoxStyle + { + get => (Style)GetValue(RichEditBoxStyleProperty); + set => SetValue(RichEditBoxStyleProperty, value); + } + + public object Header + { + get => GetValue(HeaderProperty); + set => SetValue(HeaderProperty, value); + } + + public object Description + { + get => GetValue(DescriptionProperty); + set => SetValue(DescriptionProperty, value); + } + + public TextWrapping TextWrapping + { + get => (TextWrapping)GetValue(TextWrappingProperty); + set => SetValue(TextWrappingProperty, value); + } + + public RichEditClipboardFormat ClipboardCopyFormat + { + get => (RichEditClipboardFormat)GetValue(ClipboardCopyFormatProperty); + set => SetValue(ClipboardCopyFormatProperty, value); + } + + public SolidColorBrush SuggestionBackground + { + get => (SolidColorBrush)GetValue(SuggestionBackgroundProperty); + set => SetValue(SuggestionBackgroundProperty, value); + } + + public SolidColorBrush SuggestionForeground + { + get => (SolidColorBrush)GetValue(SuggestionForegroundProperty); + set => SetValue(SuggestionForegroundProperty, value); + } + + public string Prefixes + { + get => (string)GetValue(PrefixesProperty); + set => SetValue(PrefixesProperty, value); + } + + /// + /// Gets object used for lock + /// + protected object LockObj { get; } + + public RichEditTextDocument TextDocument => _richEditBox?.TextDocument; + + public ReadOnlyObservableCollection Tokens { get; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs new file mode 100644 index 00000000000..bd00ece615f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs @@ -0,0 +1,554 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.Deferred; +using Windows.System; +using Windows.UI.Text; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Input; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + [TemplatePart(Name = PartRichEditBox, Type = typeof(RichEditBox))] + [TemplatePart(Name = PartSuggestionsPopup, Type = typeof(Popup))] + [TemplatePart(Name = PartSuggestionsList, Type = typeof(ListViewBase))] + public partial class RichSuggestBox : ItemsControl + { + private const string PartRichEditBox = "RichEditBox"; + private const string PartSuggestionsPopup = "SuggestionsPopup"; + private const string PartSuggestionsList = "SuggestionsList"; + + private Popup _suggestionPopup; + private RichEditBox _richEditBox; + private ListViewBase _suggestionsList; + + private int _suggestionChoice; + private string _currentQuery; + private string _currentPrefix; + private bool _ignoreChange; + private ITextRange _currentRange; + private CancellationTokenSource _suggestionRequestedTokenSource; + private readonly Dictionary _tokens; + private readonly ObservableCollection _visibleTokens; + + /// + /// Initializes a new instance of the class. + /// + public RichSuggestBox() + { + _tokens = new Dictionary(); + _visibleTokens = new ObservableCollection(); + Tokens = new ReadOnlyObservableCollection(_visibleTokens); + LockObj = new object(); + + DefaultStyleKey = typeof(RichSuggestBox); + + RegisterPropertyChangedCallback(ItemsSourceProperty, ItemsSource_PropertyChanged); + } + + public void ClearUndoRedoSuggestionHistory() + { + TextDocument.ClearUndoRedoHistory(); + if (_tokens.Count == 0) + { + return; + } + + var keysToDelete = _tokens.Where(pair => !pair.Value.Active).Select(pair => pair.Key).ToArray(); + foreach (var key in keysToDelete) + { + _tokens.Remove(key); + } + } + + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _suggestionPopup = (Popup)GetTemplateChild(PartSuggestionsPopup); + _richEditBox = (RichEditBox)GetTemplateChild(PartRichEditBox); + _suggestionsList = (ListViewBase)GetTemplateChild(PartSuggestionsList); + + _richEditBox.SizeChanged += RichEditBox_SizeChanged; + _richEditBox.TextChanging += RichEditBox_TextChanging; + _richEditBox.TextChanged += RichEditBox_TextChanged; + _richEditBox.SelectionChanging += RichEditBox_SelectionChanging; + _richEditBox.SelectionChanged += RichEditBox_SelectionChanged; + _richEditBox.AddHandler(PointerPressedEvent, new PointerEventHandler(RichEditBoxPointerEventHandler), true); + AddKeyboardAccelerators(); + _suggestionsList.ItemClick += SuggestionsList_ItemClick; + + _suggestionsList.GotFocus += (sender, args) => _richEditBox.Focus(FocusState.Programmatic); + LostFocus += (sender, args) => ShowSuggestionsPopup(false); + } + + private static void OnPrefixesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var view = (RichSuggestBox)d; + + var newValue = (string)e.NewValue; + var prefixes = EnforcePrefixesRequirements(newValue); + + if (newValue != prefixes) + { + view.SetValue(PrefixesProperty, prefixes); + } + } + + private static string EnforcePrefixesRequirements(string value) + { + if (string.IsNullOrEmpty(value)) + { + return "@"; + } + + var possibles = string.Concat(value.Where(char.IsPunctuation)); + return string.IsNullOrEmpty(possibles) ? "@" : possibles; + } + + private void RichEditBox_SelectionChanging(RichEditBox sender, RichEditBoxSelectionChangingEventArgs args) + { + TextDocument.BeginUndoGroup(); + + var selection = TextDocument.Selection; + if (selection.Type != SelectionType.InsertionPoint) + { + return; + } + + var range = selection.GetClone(); + range.Expand(TextRangeUnit.Link); + if (!_tokens.ContainsKey(range.Link)) + { + return; + } + + if (range.StartPosition < selection.StartPosition && selection.EndPosition < range.EndPosition) + { + // Prevent user from manually editing the link + selection.SetRange(range.StartPosition, range.EndPosition); + } + else if (selection.StartPosition == range.StartPosition) + { + // Reset formatting if selection is sandwiched between 2 adjacent links + // or if the link is at the beginning of the document + range.MoveStart(TextRangeUnit.Link, -1); + if (selection.StartPosition != range.StartPosition || selection.StartPosition == 0) + { + ApplyDefaultFormatToRange(selection); + } + } + } + + private async void RichEditBox_SelectionChanged(object sender, RoutedEventArgs e) + { + await RequestForSuggestionsAsync(); + } + + private void RichEditBoxPointerEventHandler(object sender, PointerRoutedEventArgs e) + { + ShowSuggestionsPopup(false); + } + + private void AddKeyboardAccelerators() + { + var enterKeyAccelerator = new KeyboardAccelerator { Key = VirtualKey.Enter }; + var downKeyAccelerator = new KeyboardAccelerator { Key = VirtualKey.Down }; + var upKeyAccelerator = new KeyboardAccelerator { Key = VirtualKey.Up }; + var escapeKeyAccelerator = new KeyboardAccelerator { Key = VirtualKey.Escape }; + + enterKeyAccelerator.Invoked += RichEditBoxKeyboardAccelerator_Invoked; + downKeyAccelerator.Invoked += RichEditBoxKeyboardAccelerator_Invoked; + upKeyAccelerator.Invoked += RichEditBoxKeyboardAccelerator_Invoked; + escapeKeyAccelerator.Invoked += RichEditBoxKeyboardAccelerator_Invoked; + + _richEditBox.KeyboardAccelerators.Add(enterKeyAccelerator); + _richEditBox.KeyboardAccelerators.Add(downKeyAccelerator); + _richEditBox.KeyboardAccelerators.Add(upKeyAccelerator); + _richEditBox.KeyboardAccelerators.Add(escapeKeyAccelerator); + } + + private void RichEditBoxKeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + var itemsList = _suggestionsList.Items; + if (!_suggestionPopup.IsOpen || itemsList == null || itemsList.Count == 0) + { + return; + } + + var key = args.KeyboardAccelerator.Key; + switch (key) + { + case VirtualKey.Up when itemsList.Count == 1: + case VirtualKey.Down when itemsList.Count == 1: + { + _suggestionsList.SelectedItem = itemsList[0]; + break; + } + case VirtualKey.Up: + { + _suggestionChoice = _suggestionChoice <= 0 ? itemsList.Count : _suggestionChoice - 1; + _suggestionsList.SelectedItem = _suggestionChoice == 0 ? null : itemsList[_suggestionChoice - 1]; + args.Handled = true; + break; + } + case VirtualKey.Down: + { + _suggestionChoice = _suggestionChoice >= itemsList.Count ? 0 : _suggestionChoice + 1; + _suggestionsList.SelectedItem = _suggestionChoice == 0 ? null : itemsList[_suggestionChoice - 1]; + args.Handled = true; + break; + } + case VirtualKey.Enter when _suggestionsList.SelectedItem != null: + { + ShowSuggestionsPopup(false); + _ = OnSuggestionSelectedAsync(_suggestionsList.SelectedItem); + args.Handled = true; + break; + } + case VirtualKey.Escape: + { + ShowSuggestionsPopup(false); + args.Handled = true; + break; + } + } + } + + private async void SuggestionsList_ItemClick(object sender, ItemClickEventArgs e) + { + var selectedItem = e.ClickedItem; + await OnSuggestionSelectedAsync(selectedItem); + } + + private void RichEditBox_TextChanging(RichEditBox sender, RichEditBoxTextChangingEventArgs args) + { + if (_ignoreChange) + { + return; + } + + _ignoreChange = true; + ValidateTokensInDocument(); + TextDocument.EndUndoGroup(); + TextDocument.BeginUndoGroup(); + _ignoreChange = false; + } + + private void RichEditBox_TextChanged(object sender, RoutedEventArgs e) + { + TextChanged?.Invoke((RichEditBox)sender, e); + UpdateVisibleTokenList(); + } + + private void RichEditBox_SizeChanged(object sender, SizeChangedEventArgs e) + { + _suggestionPopup.VerticalOffset = e.NewSize.Height; + _suggestionsList.MaxWidth = e.NewSize.Width; + } + + private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProperty dp) + { + _suggestionChoice = 0; + ShowSuggestionsPopup(ItemsSource is IEnumerable); + } + + private async Task RequestForSuggestionsAsync() + { + _suggestionRequestedTokenSource?.Cancel(); + + if (TryExtractQueryFromSelection(out var prefix, out var query, out var range) && + SuggestionsRequested != null) + { + _suggestionRequestedTokenSource = new CancellationTokenSource(); + _currentPrefix = prefix; + _currentQuery = query; + _currentRange = range; + + var cancellationToken = _suggestionRequestedTokenSource.Token; + var eventArgs = new SuggestionsRequestedEventArgs { Query = query, Prefix = prefix }; + try + { + await SuggestionsRequested.InvokeAsync(this, eventArgs, cancellationToken); + } + catch (TaskCanceledException) + { + eventArgs.Cancel = true; + } + } + else + { + ShowSuggestionsPopup(false); + } + } + + private async Task OnSuggestionSelectedAsync(object selectedItem) + { + var range = _currentRange; + var id = Guid.NewGuid(); + var prefix = _currentPrefix; + var query = _currentQuery; + + // range has length of 0 at the end of the commit. + // Checking length == 0 to avoid committing twice. + if (SuggestionChosen == null || prefix == null || query == null || range == null || + range.Length == 0) + { + return; + } + + var eventArgs = new SuggestionChosenEventArgs + { + Id = id, + Prefix = prefix, + Query = query, + SelectedItem = selectedItem, + Format = CreateSuggestionTokenFormat() + }; + var textBefore = range.Text; + await SuggestionChosen.InvokeAsync(this, eventArgs); + var text = eventArgs.Text; + + // Since this operation is async, the document may have changed at this point. + // Double check if the range still has the expected query. + if (string.IsNullOrEmpty(text) || textBefore != range.Text || + !TryExtractQueryFromRange(range, out var testPrefix, out var testQuery) || + testPrefix != prefix || testQuery != query) + { + return; + } + + lock (LockObj) + { + var displayText = prefix + text; + var tokenRange = CommitSuggestionIntoDocument(range, displayText, id, eventArgs.Format); + + var token = new SuggestionInfo(id, displayText, this) { Active = true, Item = selectedItem }; + token.UpdateTextRange(tokenRange); + _tokens.Add(tokenRange.Link, token); + } + } + + private ITextRange CommitSuggestionIntoDocument(ITextRange range, string displayText, Guid id, SuggestionTokenFormat format) + { + _ignoreChange = true; + TextDocument.BeginUndoGroup(); + + range.SetText(TextSetOptions.Unhide, displayText); + range.Link = $"\"{id}\""; + + range.CharacterFormat.BackgroundColor = format.Background; + range.CharacterFormat.ForegroundColor = format.Foreground; + range.CharacterFormat.Bold = format.Bold; + range.CharacterFormat.Italic = format.Italic; + range.CharacterFormat.Underline = format.Underline; + + var returnRange = TextDocument.GetRange(range.StartPosition, range.EndPosition); + + range.Collapse(false); + range.SetText(TextSetOptions.Unhide, " "); + range.Collapse(false); + TextDocument.Selection.SetRange(range.EndPosition, range.EndPosition); + + TextDocument.EndUndoGroup(); + _ignoreChange = false; + return returnRange; + } + + private void ValidateTokensInDocument() + { + foreach (var (_, token) in _tokens) + { + token.Active = false; + } + + var range = TextDocument.GetRange(0, 0); + range.SetIndex(TextRangeUnit.Character, -1, false); + + // Handle link at the very end of the document where GetIndex fails to detect + range.Expand(TextRangeUnit.Link); + ValidateTokenFromRange(range); + + var nextIndex = range.GetIndex(TextRangeUnit.Link); + while (nextIndex != 0 && nextIndex != 1) + { + range.Move(TextRangeUnit.Link, -1); + + var validateRange = range.GetClone(); + validateRange.Expand(TextRangeUnit.Link); + + // Adjacent links have the same index. Manually check each link with Collapse and Expand. + var previousText = validateRange.Text; + var hasAdjacentToken = true; + while (hasAdjacentToken) + { + ValidateTokenFromRange(validateRange); + validateRange.Collapse(false); + validateRange.Expand(TextRangeUnit.Link); + + hasAdjacentToken = validateRange.Text != previousText; + previousText = validateRange.Text; + } + + nextIndex = range.GetIndex(TextRangeUnit.Link); + } + } + + private bool ValidateTokenFromRange(ITextRange range) + { + if (range.Length == 0 || string.IsNullOrEmpty(range.Link) || + !_tokens.TryGetValue(range.Link, out var token)) + { + // Handle case where range.Link is empty but it still recognized and rendered as a link + if (range.CharacterFormat.LinkType == LinkType.FriendlyLinkName) + { + range.Link = string.Empty; + } + return false; + } + + if (token.ToString() != range.Text) + { + //range.Link = string.Empty; + range.CharacterFormat = TextDocument.GetDefaultCharacterFormat(); + token.Active = false; + return false; + } + + token.UpdateTextRange(range); + token.Active = true; + return true; + } + + private void ShowSuggestionsPopup(bool show) + { + _suggestionPopup.IsOpen = show; + if (!show) + { + _suggestionChoice = 0; + } + } + + private bool TryExtractQueryFromSelection(out string prefix, out string query, out ITextRange range) + { + prefix = string.Empty; + query = string.Empty; + range = null; + if (TextDocument.Selection.Type != SelectionType.InsertionPoint) + { + return false; + } + + // Check if selection is on existing link (suggestion) + var expandCount = TextDocument.Selection.GetClone().Expand(TextRangeUnit.Link); + if (expandCount != 0) + { + return false; + } + + var selection = TextDocument.Selection.GetClone(); + selection.MoveStart(TextRangeUnit.Word, -1); + if (selection.Length == 0) + { + return false; + } + + range = selection; + if (TryExtractQueryFromRange(selection, out prefix, out query)) + { + return true; + } + + selection.MoveStart(TextRangeUnit.Word, -1); + if (TryExtractQueryFromRange(selection, out prefix, out query)) + { + return true; + } + + range = null; + return false; + } + + private bool TryExtractQueryFromRange(ITextRange range, out string prefix, out string query) + { + prefix = string.Empty; + query = string.Empty; + range.GetText(TextGetOptions.NoHidden, out var possibleQuery); + if (possibleQuery.Length > 0 && Prefixes.Contains(possibleQuery[0]) && + !possibleQuery.Any(char.IsWhiteSpace) && string.IsNullOrEmpty(range.Link)) + { + if (possibleQuery.Length == 1) + { + prefix = possibleQuery; + return true; + } + + prefix = possibleQuery[0].ToString(); + query = possibleQuery.Substring(1); + return true; + } + + return false; + } + + private SuggestionTokenFormat CreateSuggestionTokenFormat() + { + var defaultFormat = TextDocument.GetDefaultCharacterFormat(); + if (SuggestionBackground != null) + { + defaultFormat.BackgroundColor = SuggestionBackground.Color; + } + + if (SuggestionForeground != null) + { + defaultFormat.ForegroundColor = SuggestionForeground.Color; + } + + return new SuggestionTokenFormat + { + Foreground = defaultFormat.ForegroundColor, + Background = defaultFormat.BackgroundColor, + Italic = defaultFormat.Italic, + Bold = defaultFormat.Bold, + Underline = defaultFormat.Underline + }; + } + + private void ApplyDefaultFormatToRange(ITextRange range) + { + var defaultFormat = TextDocument.GetDefaultCharacterFormat(); + range.CharacterFormat.BackgroundColor = defaultFormat.BackgroundColor; + range.CharacterFormat.ForegroundColor = defaultFormat.ForegroundColor; + range.CharacterFormat.Bold = defaultFormat.Bold; + range.CharacterFormat.Italic = defaultFormat.Italic; + range.CharacterFormat.Underline = defaultFormat.Underline; + } + + private void UpdateVisibleTokenList() + { + lock (LockObj) + { + var toBeRemoved = _visibleTokens.Where(x => !x.Active).ToArray(); + + foreach (var elem in toBeRemoved) + { + _visibleTokens.Remove(elem); + } + + var toBeAdded = _tokens.Where(pair => pair.Value.Active && !_visibleTokens.Contains(pair.Value)) + .Select(pair => pair.Value).ToArray(); + + foreach (var elem in toBeAdded) + { + _visibleTokens.Add(elem); + } + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.xaml new file mode 100644 index 00000000000..5f3cb590dd6 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.xaml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs new file mode 100644 index 00000000000..6ba680e6a3b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.Toolkit.Uwp.Deferred; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + public class SuggestionChosenEventArgs : DeferredEventArgs + { + public string Query { get; internal set; } + + public string Prefix { get; internal set; } + + public string Text { get; set; } + + public object SelectedItem { get; internal set; } + + public Guid Id { get; internal set; } + + public SuggestionTokenFormat Format { get; internal set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs new file mode 100644 index 00000000000..9893cde2ae5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs @@ -0,0 +1,59 @@ +using System; +using System.ComponentModel; +using Windows.UI.Text; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + public class SuggestionInfo : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + public RichSuggestBox Owner { get; } + + public Guid Id { get; } + + public string DisplayText { get; } + + public object Item { get; set; } + + public int RangeStart { get; private set; } + + public int RangeEnd { get; private set; } + + public int Position => _range?.GetIndex(TextRangeUnit.Character) - 1 ?? 0; + + internal bool Active { get; set; } + + private ITextRange _range; + + public SuggestionInfo(Guid id, string displayText, RichSuggestBox owner) + { + Id = id; + DisplayText = displayText; + Owner = owner; + } + + internal void UpdateTextRange(ITextRange range) + { + if (_range == null) + { + _range = range; + RangeStart = _range.StartPosition; + RangeEnd = _range.EndPosition; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty)); + } + else if (RangeStart != range.StartPosition || RangeEnd != range.EndPosition) + { + _range.SetRange(range.StartPosition, range.EndPosition); + RangeStart = _range.StartPosition; + RangeEnd = _range.EndPosition; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty)); + } + } + + public override string ToString() + { + return $"HYPERLINK \"{Id}\"{DisplayText}"; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs new file mode 100644 index 00000000000..61d7ad16720 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs @@ -0,0 +1,18 @@ +using Windows.UI; +using Windows.UI.Text; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + public class SuggestionTokenFormat + { + public Color Foreground { get; set; } + + public Color Background { get; set; } + + public FormatEffect Italic { get; set; } + + public FormatEffect Bold { get; set; } + + public UnderlineType Underline { get; set; } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs new file mode 100644 index 00000000000..def23b9ec13 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs @@ -0,0 +1,11 @@ +using Microsoft.Toolkit.Uwp.Deferred; + +namespace Microsoft.Toolkit.Uwp.UI.Controls +{ + public class SuggestionsRequestedEventArgs : DeferredCancelEventArgs + { + public string Prefix { get; set; } + + public string Query { get; set; } + } +} From 4ee560666cb49cc186cd21c5149d2e5d9c89c0ba Mon Sep 17 00:00:00 2001 From: Tung Huynh Date: Thu, 14 Jan 2021 23:06:35 -0800 Subject: [PATCH 002/102] update headers --- .../RichSuggestBox/PlainTextCommandBarFlyout.cs | 6 +++++- .../RichSuggestBox/RichSuggestBox.Events.cs | 6 +++++- .../RichSuggestBox/RichSuggestBox.Properties.cs | 6 +++++- .../RichSuggestBox/RichSuggestBox.cs | 6 +++++- .../RichSuggestBox/SuggestionChosenEventArgs.cs | 6 +++++- .../RichSuggestBox/SuggestionInfo.cs | 6 +++++- .../RichSuggestBox/SuggestionTokenFormat.cs | 6 +++++- .../RichSuggestBox/SuggestionsRequestedEventArgs.cs | 6 +++++- 8 files changed, 40 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/PlainTextCommandBarFlyout.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/PlainTextCommandBarFlyout.cs index 7adbc988c2e..342514f5298 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/PlainTextCommandBarFlyout.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/PlainTextCommandBarFlyout.cs @@ -1,4 +1,8 @@ -using Windows.UI.Xaml.Controls; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml.Controls; namespace Microsoft.Toolkit.Uwp.UI.Controls { diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs index 73f6229aa30..8b735c3935f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs @@ -1,4 +1,8 @@ -using Windows.Foundation; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs index b5083430df5..882ac0138e5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs @@ -1,4 +1,8 @@ -using System.Collections.ObjectModel; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.ObjectModel; using Windows.UI.Text; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs index bd00ece615f..dd87d688355 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs index 6ba680e6a3b..1b10a12a9e5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using Microsoft.Toolkit.Uwp.Deferred; namespace Microsoft.Toolkit.Uwp.UI.Controls diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs index 9893cde2ae5..f0332dc7176 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs @@ -1,4 +1,8 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; using System.ComponentModel; using Windows.UI.Text; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs index 61d7ad16720..ebb1b48f99c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs @@ -1,4 +1,8 @@ -using Windows.UI; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI; using Windows.UI.Text; namespace Microsoft.Toolkit.Uwp.UI.Controls diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs index def23b9ec13..c9f7a71d43c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs @@ -1,4 +1,8 @@ -using Microsoft.Toolkit.Uwp.Deferred; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.Deferred; namespace Microsoft.Toolkit.Uwp.UI.Controls { From 583d5ae2e44932636bbbd92176b9413e5b0c2c60 Mon Sep 17 00:00:00 2001 From: Tung Huynh Date: Tue, 19 Jan 2021 22:02:13 -0800 Subject: [PATCH 003/102] add comments --- .../RichSuggestBox/RichSuggestBox.Events.cs | 13 ++++ .../RichSuggestBox.Properties.cs | 63 +++++++++++++++++++ .../RichSuggestBox/RichSuggestBox.cs | 8 +++ .../SuggestionChosenEventArgs.cs | 21 +++++++ .../RichSuggestBox/SuggestionInfo.cs | 32 ++++++++++ .../RichSuggestBox/SuggestionTokenFormat.cs | 18 ++++++ .../SuggestionsRequestedEventArgs.cs | 9 +++ 7 files changed, 164 insertions(+) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs index 8b735c3935f..3351c3d81c5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Events.cs @@ -8,12 +8,25 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// The RichSuggestBox control extends control that suggests and embeds custom data in a rich document. + /// public partial class RichSuggestBox { + /// + /// Event raised when the control needs to show suggestions. + /// public event TypedEventHandler SuggestionsRequested; + /// + /// Event raised when user click on a suggestion. + /// This event lets you customize the token appearance in the document. + /// public event TypedEventHandler SuggestionChosen; + /// + /// Event raised when text is changed, either by user or by internal formatting. + /// public event TypedEventHandler TextChanged; } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs index 882ac0138e5..59b499e9e4f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.Properties.cs @@ -10,8 +10,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// The RichSuggestBox control extends control that suggests and embeds custom data in a rich document. + /// public partial class RichSuggestBox { + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register( nameof(PlaceholderText), @@ -19,6 +25,9 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata(string.Empty)); + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty RichEditBoxStyleProperty = DependencyProperty.Register( nameof(RichEditBoxStyle), @@ -26,6 +35,9 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata(null)); + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register( nameof(Header), @@ -33,6 +45,9 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata(null)); + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register( nameof(Description), @@ -40,6 +55,9 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata(null)); + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register( nameof(TextWrapping), @@ -47,6 +65,9 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata(TextWrapping.NoWrap)); + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty ClipboardCopyFormatProperty = DependencyProperty.Register( nameof(ClipboardCopyFormat), @@ -54,6 +75,9 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata(RichEditClipboardFormat.PlainText)); + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty SuggestionBackgroundProperty = DependencyProperty.Register( nameof(SuggestionBackground), @@ -61,6 +85,9 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata(null)); + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty SuggestionForegroundProperty = DependencyProperty.Register( nameof(SuggestionForeground), @@ -68,6 +95,9 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata(null)); + /// + /// Identifies the dependency property. + /// public static readonly DependencyProperty PrefixesProperty = DependencyProperty.Register( nameof(Prefixes), @@ -75,54 +105,81 @@ public partial class RichSuggestBox typeof(RichSuggestBox), new PropertyMetadata("@", OnPrefixesChanged)); + /// + /// Gets or sets the text that is displayed in the control until the value is changed by a user action or some other operation. + /// public string PlaceholderText { get => (string)GetValue(PlaceholderTextProperty); set => SetValue(PlaceholderTextProperty, value); } + /// + /// Gets or sets the style of the underlying . + /// public Style RichEditBoxStyle { get => (Style)GetValue(RichEditBoxStyleProperty); set => SetValue(RichEditBoxStyleProperty, value); } + /// + /// Gets or sets the content for the control's header. + /// public object Header { get => GetValue(HeaderProperty); set => SetValue(HeaderProperty, value); } + /// + /// Gets or sets content that is shown below the control. The content should provide guidance about the input expected by the control. + /// public object Description { get => GetValue(DescriptionProperty); set => SetValue(DescriptionProperty, value); } + /// + /// Gets or sets a value that indicates how text wrapping occurs if a line of text extends beyond the available width of the control. + /// public TextWrapping TextWrapping { get => (TextWrapping)GetValue(TextWrappingProperty); set => SetValue(TextWrappingProperty, value); } + /// + /// Gets or sets a value that specifies whether text is copied with all formats, or as plain text only. + /// public RichEditClipboardFormat ClipboardCopyFormat { get => (RichEditClipboardFormat)GetValue(ClipboardCopyFormatProperty); set => SetValue(ClipboardCopyFormatProperty, value); } + /// + /// Gets or sets the default brush used to color the suggestion token background. + /// public SolidColorBrush SuggestionBackground { get => (SolidColorBrush)GetValue(SuggestionBackgroundProperty); set => SetValue(SuggestionBackgroundProperty, value); } + /// + /// Gets or sets the default brush used to color the suggestion token foreground. + /// public SolidColorBrush SuggestionForeground { get => (SolidColorBrush)GetValue(SuggestionForegroundProperty); set => SetValue(SuggestionForegroundProperty, value); } + /// + /// Gets or sets prefix characters to start a query. + /// public string Prefixes { get => (string)GetValue(PrefixesProperty); @@ -134,8 +191,14 @@ public string Prefixes /// protected object LockObj { get; } + /// + /// Gets an object that enables access to the text object model for the text contained in a . + /// public RichEditTextDocument TextDocument => _richEditBox?.TextDocument; + /// + /// Gets a collection of suggestion tokens that are present in the document. + /// public ReadOnlyObservableCollection Tokens { get; } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs index dd87d688355..3d311912f8b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs @@ -19,6 +19,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// The RichSuggestBox control extends control that suggests and embeds custom data in a rich document. + /// [TemplatePart(Name = PartRichEditBox, Type = typeof(RichEditBox))] [TemplatePart(Name = PartSuggestionsPopup, Type = typeof(Popup))] [TemplatePart(Name = PartSuggestionsList, Type = typeof(ListViewBase))] @@ -56,6 +59,10 @@ public RichSuggestBox() RegisterPropertyChangedCallback(ItemsSourceProperty, ItemsSource_PropertyChanged); } + /// + /// Clear unused tokens and undo/redo history. saves all of previously committed tokens + /// even when they are removed from the text. They have to be manually removed using this method. + /// public void ClearUndoRedoSuggestionHistory() { TextDocument.ClearUndoRedoHistory(); @@ -71,6 +78,7 @@ public void ClearUndoRedoSuggestionHistory() } } + /// protected override void OnApplyTemplate() { base.OnApplyTemplate(); diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs index 1b10a12a9e5..3ab5f5a80c0 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionChosenEventArgs.cs @@ -7,18 +7,39 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// Provides data for the event. + /// public class SuggestionChosenEventArgs : DeferredEventArgs { + /// + /// Gets the query used for this token. + /// public string Query { get; internal set; } + /// + /// Gets the prefix character used for this token. + /// public string Prefix { get; internal set; } + /// + /// Gets or sets the display text. + /// public string Text { get; set; } + /// + /// Gets the suggestion item associated with this token. + /// public object SelectedItem { get; internal set; } + /// + /// Gets token ID. + /// public Guid Id { get; internal set; } + /// + /// Gets the formatting construct to override this token formatting. + /// public SuggestionTokenFormat Format { get; internal set; } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs index f0332dc7176..823c8def124 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionInfo.cs @@ -8,28 +8,59 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// SuggestionInfo describes a suggestion token in the document. + /// public class SuggestionInfo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// Gets the instance that owns this token. + /// public RichSuggestBox Owner { get; } + /// + /// Gets the token ID. + /// public Guid Id { get; } + /// + /// Gets the text displayed in the document. + /// public string DisplayText { get; } + /// + /// Gets or sets the suggested item associated with this token. + /// public object Item { get; set; } + /// + /// Gets the start position of the text range. + /// public int RangeStart { get; private set; } + /// + /// Gets the end position of the text range. + /// public int RangeEnd { get; private set; } + /// + /// Gets the start position of the token in number of characters. + /// public int Position => _range?.GetIndex(TextRangeUnit.Character) - 1 ?? 0; internal bool Active { get; set; } private ITextRange _range; + /// + /// Initializes a new instance of the class. + /// + /// Token ID + /// Text in the document + /// instance that owns this token public SuggestionInfo(Guid id, string displayText, RichSuggestBox owner) { Id = id; @@ -55,6 +86,7 @@ internal void UpdateTextRange(ITextRange range) } } + /// public override string ToString() { return $"HYPERLINK \"{Id}\"{DisplayText}"; diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs index ebb1b48f99c..588076f562f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionTokenFormat.cs @@ -7,16 +7,34 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// SuggestionTokenFormat describes how a token should be formatted. + /// public class SuggestionTokenFormat { + /// + /// Gets or sets token foreground color. + /// public Color Foreground { get; set; } + /// + /// Gets or sets token background color. + /// public Color Background { get; set; } + /// + /// Gets or sets token italic style. + /// public FormatEffect Italic { get; set; } + /// + /// Gets or sets token bold style. + /// public FormatEffect Bold { get; set; } + /// + /// Gets or sets token underline style. + /// public UnderlineType Underline { get; set; } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs index c9f7a71d43c..60e358b2ff9 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/SuggestionsRequestedEventArgs.cs @@ -6,10 +6,19 @@ namespace Microsoft.Toolkit.Uwp.UI.Controls { + /// + /// Provide data for event. + /// public class SuggestionsRequestedEventArgs : DeferredCancelEventArgs { + /// + /// Gets or sets the prefix character used for the query. + /// public string Prefix { get; set; } + /// + /// Gets or sets the query for suggestions. + /// public string Query { get; set; } } } From 60169ae95883f724413c6a5f1ed5a380437c6844 Mon Sep 17 00:00:00 2001 From: Tung Huynh Date: Tue, 19 Jan 2021 22:37:03 -0800 Subject: [PATCH 004/102] fix indentation issues --- .../RichSuggestBox/RichSuggestBox.cs | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs index 3d311912f8b..ca8b9738e32 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs @@ -199,37 +199,31 @@ private void RichEditBoxKeyboardAccelerator_Invoked(KeyboardAccelerator sender, { case VirtualKey.Up when itemsList.Count == 1: case VirtualKey.Down when itemsList.Count == 1: - { - _suggestionsList.SelectedItem = itemsList[0]; - break; - } + _suggestionsList.SelectedItem = itemsList[0]; + break; + case VirtualKey.Up: - { - _suggestionChoice = _suggestionChoice <= 0 ? itemsList.Count : _suggestionChoice - 1; - _suggestionsList.SelectedItem = _suggestionChoice == 0 ? null : itemsList[_suggestionChoice - 1]; - args.Handled = true; - break; - } + _suggestionChoice = _suggestionChoice <= 0 ? itemsList.Count : _suggestionChoice - 1; + _suggestionsList.SelectedItem = _suggestionChoice == 0 ? null : itemsList[_suggestionChoice - 1]; + args.Handled = true; + break; + case VirtualKey.Down: - { - _suggestionChoice = _suggestionChoice >= itemsList.Count ? 0 : _suggestionChoice + 1; - _suggestionsList.SelectedItem = _suggestionChoice == 0 ? null : itemsList[_suggestionChoice - 1]; - args.Handled = true; - break; - } + _suggestionChoice = _suggestionChoice >= itemsList.Count ? 0 : _suggestionChoice + 1; + _suggestionsList.SelectedItem = _suggestionChoice == 0 ? null : itemsList[_suggestionChoice - 1]; + args.Handled = true; + break; + case VirtualKey.Enter when _suggestionsList.SelectedItem != null: - { - ShowSuggestionsPopup(false); - _ = OnSuggestionSelectedAsync(_suggestionsList.SelectedItem); - args.Handled = true; - break; - } + ShowSuggestionsPopup(false); + _ = OnSuggestionSelectedAsync(_suggestionsList.SelectedItem); + args.Handled = true; + break; + case VirtualKey.Escape: - { - ShowSuggestionsPopup(false); - args.Handled = true; - break; - } + ShowSuggestionsPopup(false); + args.Handled = true; + break; } } From 74c32190f31e8c404081baa10106a8df32568779 Mon Sep 17 00:00:00 2001 From: Tung Huynh Date: Tue, 19 Jan 2021 23:30:24 -0800 Subject: [PATCH 005/102] more fixes --- .../RichSuggestBox/RichSuggestBox.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs index ca8b9738e32..55d8a2850ba 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.cs @@ -31,6 +31,9 @@ public partial class RichSuggestBox : ItemsControl private const string PartSuggestionsPopup = "SuggestionsPopup"; private const string PartSuggestionsList = "SuggestionsList"; + private readonly Dictionary _tokens; + private readonly ObservableCollection _visibleTokens; + private Popup _suggestionPopup; private RichEditBox _richEditBox; private ListViewBase _suggestionsList; @@ -41,8 +44,6 @@ public partial class RichSuggestBox : ItemsControl private bool _ignoreChange; private ITextRange _currentRange; private CancellationTokenSource _suggestionRequestedTokenSource; - private readonly Dictionary _tokens; - private readonly ObservableCollection _visibleTokens; /// /// Initializes a new instance of the class. @@ -416,12 +417,13 @@ private bool ValidateTokenFromRange(ITextRange range) { range.Link = string.Empty; } + return false; } if (token.ToString() != range.Text) { - //range.Link = string.Empty; + range.Link = string.Empty; range.CharacterFormat = TextDocument.GetDefaultCharacterFormat(); token.Active = false; return false; From 08122c3bed92d664402b125188ac718179a411b8 Mon Sep 17 00:00:00 2001 From: Tung Huynh Date: Thu, 21 Jan 2021 03:19:05 -0800 Subject: [PATCH 006/102] xaml formatting --- .../RichSuggestBox/RichSuggestBox.xaml | 108 ++++++++++-------- 1 file changed, 58 insertions(+), 50 deletions(-) diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.xaml index 5f3cb590dd6..44065225f1c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/RichSuggestBox/RichSuggestBox.xaml @@ -1,34 +1,40 @@ - + - - + + - - + + - - + + - -