-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Implement RichSuggestBox #3650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Implement RichSuggestBox #3650
Changes from all commits
Commits
Show all changes
102 commits
Select commit
Hold shift + click to select a range
435ec70
add RichSuggestBox to repo
huynhsontung 4ee5606
update headers
huynhsontung 583d5ae
add comments
huynhsontung 60169ae
fix indentation issues
huynhsontung 74c3219
more fixes
huynhsontung 08122c3
xaml formatting
huynhsontung ce3726f
no conditional xaml due to bug
huynhsontung 91093cf
add sample page
huynhsontung 99872b1
add more template bindings
huynhsontung 5f7a847
update headers (again)
huynhsontung 3ba061e
no need for PlainTextCommandBarFlyout anymore
huynhsontung 0fda83b
prune duplicate properties and fix flyout placement
huynhsontung 197e36e
open suggestion up or down depends on available screen
huynhsontung 4a0f38b
add SuggestionPopupPlacement property
huynhsontung 71fcd9d
suggestion list scroll item into view on selection
huynhsontung ec4602e
move static helper methods into a separate file
huynhsontung 5a7bc1c
fix formatting bug on token delete
huynhsontung a4b9114
fix floating placement calculation errors
huynhsontung ccaf97a
keep interior corners square in attached placement mode
huynhsontung a49a27e
fix lint issue
huynhsontung b67ae42
option to have plain text
huynhsontung 53d9a3e
fix double pasting
huynhsontung 275a87e
add sample code
huynhsontung be6f402
handle duplicate links + some refactoring
huynhsontung a4fd239
fix some bugs when pasting
huynhsontung 67583c0
move to Microsoft.Toolkit.Uwp.UI.Controls.Input
huynhsontung 418a646
unregister events before registering
huynhsontung 8e2a7ac
remove copy of DefaultRichEditBoxStyle
huynhsontung 79d1755
handle removing token at position 0 special case
huynhsontung 1d3cfc8
handle rare case where link not updating
huynhsontung 723824b
add Ctrl + Tab to commit
huynhsontung 08b48e3
allow empty prefixes
huynhsontung 557657b
add more formatting options
huynhsontung eab2cc8
forgot to set _ignoreChange
huynhsontung 03a40ed
remove redundant ApplyDefaultFormatToRange
huynhsontung 91c8cfc
fix suggestion refresh unnecessarily when text composition changes
huynhsontung a38d1e5
remove unused TextControlCommandBarFlyouts
huynhsontung 4983b0e
refactor
huynhsontung 5be33ce
add key to default RichSuggestBox style
huynhsontung 68a17e2
no enforcing non-empty prefix
huynhsontung fda6c40
add tab key to select token + fix properly invalidate tokens
huynhsontung 7876aa9
add TokenSelected event + some renaming
huynhsontung a3faee9
expose SelectionChanged event + bug fixes
huynhsontung 13747b1
token background: transparent; foreground: HyperlinkButtonForeground
huynhsontung 2387ba3
add more features to RichSuggestBox sample
huynhsontung 2ac3e4f
added TokenHovered event and some refactoring
huynhsontung b150736
update sample to use TokenHovered
huynhsontung 9d66f1a
better way to handle on hovered position
huynhsontung 7c7c290
take into account scrollviewer offsets when calculating positions
huynhsontung 6e4fb90
rework undo group handling
huynhsontung 76e7c88
fix some bugs when there is a token at the start of the document
huynhsontung becfc10
miss 1 InvokeTokenSelected invocation
huynhsontung 6e2d6f2
add synchronization for token dict
huynhsontung b36c283
pass ITextCharacterFormat to user instead of using custom RichSuggest…
huynhsontung 70e7a41
reset formatting on text change that contains a token at range start
huynhsontung 02f1076
minor tweaks
huynhsontung 6686e74
add tests
huynhsontung 8f10e3b
pad token with Zero-Width-Spaces to avoid character format "bleed"
huynhsontung 6e53a50
update test for padding
huynhsontung 17e3399
try to fix test App not running on CI
huynhsontung fd99d9d
delete text instead of resetting their character format
huynhsontung d6e64c1
add winui dependency in test app
huynhsontung 169c716
update token list before triggering TextChanged
huynhsontung b13fc08
use content link foreground to avoid incompatible colors
huynhsontung 2e7d978
update TestAdapter and TestFramework
huynhsontung 2ab8096
temporarily remove tests
huynhsontung 56104c1
TokenHovering event repeatedly fire on pointer moved
huynhsontung b25cdd5
use ignore attribute instead
huynhsontung be29388
refine hit test for TokenHovering
huynhsontung 1c1c370
TokenHoveringEventArgs to include a PointerPoint object
huynhsontung e40e461
add richsuggestbox UI test
huynhsontung 22cae50
test token text separate from item
huynhsontung 1b2e483
test observale position
huynhsontung 1f3dcc7
dont use UIElement.ActualSize
huynhsontung b39d0ba
modify size of suggestionsContainer instead of the actual listview
huynhsontung db0599a
retry adding unit tests
huynhsontung bc95b9a
fix Tab key trap
huynhsontung 0428550
TokenHovering -> TokenPointerOver
huynhsontung 9195720
SuggestionsRequestedEventArgs should inherit DeferredEventArgs instead
huynhsontung a2b3880
rework SuggestionsRequested to support AdvancedCollectionView
huynhsontung d091e6a
protected LockObj -> private _tokensLock
huynhsontung 9d4809c
trigger RichSuggestToken PropertyChanged individually
huynhsontung 58a3cde
rename Query -> QueryText everywhere
huynhsontung c363f62
improve clarity for SuggestionChosenEventArgs
huynhsontung 2ffd1ff
add header for RichSuggestQuery.cs
huynhsontung 1ba3228
add Clear() and AddTokens() methods
huynhsontung 17b9b32
reafactor out _suggestionRequestedCancellationSource
huynhsontung f772542
add Load() method
huynhsontung cddc5a6
add tests for Load and Clear
huynhsontung 70c9ef0
minor fix for the Load test case
huynhsontung 3587c40
SuggestionsRequested -> SuggestionRequested
huynhsontung 73e5f15
update sample code bind
huynhsontung 9b34912
improve some comments
huynhsontung 32c3743
fix suggestions not showing if no SuggestionRequested handler
huynhsontung f5a97d4
add icon for sample
huynhsontung ab1d3b3
improve sample icon
huynhsontung 2e9b350
update code url for sample
huynhsontung 7b7974b
update doc url for sample
huynhsontung 3d03ce7
dont use ApplicationView when XamlRoot is available
huynhsontung 3401db0
disable element on screen check
huynhsontung 4e327ed
Still apply Screen size algorithm for UWP and annotate calculation
michael-hawker 46bd862
Provide better guards for UWP
michael-hawker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+34.7 KB
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RichSuggestBox/RichSuggestBox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions
50
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RichSuggestBox/RichSuggestBoxCode.bind
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
private void SuggestingBox_OnTokenPointerOver(RichSuggestBox sender, RichSuggestTokenPointerOverEventArgs args) | ||
{ | ||
var flyout = (Flyout)FlyoutBase.GetAttachedFlyout(sender); | ||
var pointerPosition = args.CurrentPoint.Position; | ||
|
||
if (flyout?.Content is ContentPresenter cp && sender.TextDocument.Selection.Type != SelectionType.Normal && | ||
(!flyout.IsOpen || cp.Content != args.Token.Item)) | ||
{ | ||
this._dispatcherQueue.TryEnqueue(() => | ||
{ | ||
cp.Content = args.Token.Item; | ||
flyout.ShowAt(sender, new FlyoutShowOptions | ||
{ | ||
Position = pointerPosition, | ||
ExclusionRect = sender.GetRectFromRange(args.Range), | ||
ShowMode = FlyoutShowMode.TransientWithDismissOnPointerMoveAway, | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
private void SuggestingBox_OnSuggestionChosen(RichSuggestBox sender, SuggestionChosenEventArgs args) | ||
{ | ||
if (args.Prefix == "#") | ||
{ | ||
args.Format.BackgroundColor = Colors.DarkOrange; | ||
args.Format.ForegroundColor = Colors.OrangeRed; | ||
args.Format.Bold = FormatEffect.On; | ||
args.Format.Italic = FormatEffect.On; | ||
args.DisplayText = ((SampleDataType)args.SelectedItem).Text; | ||
} | ||
else | ||
{ | ||
args.DisplayText = ((SampleEmailDataType)args.SelectedItem).DisplayName; | ||
} | ||
} | ||
|
||
private void SuggestingBox_OnSuggestionRequested(RichSuggestBox sender, SuggestionRequestedEventArgs args) | ||
{ | ||
if (args.Prefix == "#") | ||
{ | ||
sender.ItemsSource = | ||
this._samples.Where(x => x.Text.Contains(args.QueryText, StringComparison.OrdinalIgnoreCase)); | ||
} | ||
else | ||
{ | ||
sender.ItemsSource = | ||
this._emailSamples.Where(x => x.DisplayName.Contains(args.QueryText, StringComparison.OrdinalIgnoreCase)); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RichSuggestBox/RichSuggestBoxPage.xaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<Page x:Class="Microsoft.Toolkit.Uwp.SampleApp.SamplePages.RichSuggestBoxPage" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:local="using:Microsoft.Toolkit.Uwp.SampleApp.SamplePages" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
mc:Ignorable="d"> | ||
<Page.Resources> | ||
<ResourceDictionary> | ||
<local:SuggestionTemplateSelector x:Key="SuggestionTemplateSelector" /> | ||
<local:NameToColorConverter x:Key="NameToColorConverter" /> | ||
</ResourceDictionary> | ||
</Page.Resources> | ||
|
||
<Grid Visibility="Collapsed"> | ||
<controls:RichSuggestBox /> | ||
<ListView /> | ||
</Grid> | ||
</Page> |
191 changes: 191 additions & 0 deletions
191
Microsoft.Toolkit.Uwp.SampleApp/SamplePages/RichSuggestBox/RichSuggestBoxPage.xaml.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
// 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.Generic; | ||
using System.Linq; | ||
using Microsoft.Toolkit.Uwp.UI; | ||
using Microsoft.Toolkit.Uwp.UI.Controls; | ||
using Windows.System; | ||
using Windows.UI; | ||
using Windows.UI.Text; | ||
using Windows.UI.Xaml; | ||
using Windows.UI.Xaml.Controls; | ||
using Windows.UI.Xaml.Controls.Primitives; | ||
|
||
namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages | ||
{ | ||
/// <summary> | ||
/// An empty page that can be used on its own or navigated to within a Frame. | ||
/// </summary> | ||
public sealed partial class RichSuggestBoxPage : Page, IXamlRenderListener | ||
{ | ||
private readonly List<SampleEmailDataType> _emailSamples = new List<SampleEmailDataType>() | ||
{ | ||
new SampleEmailDataType() { FirstName = "Marcus", FamilyName = "Perryman" }, | ||
new SampleEmailDataType() { FirstName = "Michael", FamilyName = "Hawker" }, | ||
new SampleEmailDataType() { FirstName = "Matt", FamilyName = "Lacey" }, | ||
new SampleEmailDataType() { FirstName = "Alexandre", FamilyName = "Chohfi" }, | ||
new SampleEmailDataType() { FirstName = "Filip", FamilyName = "Wallberg" }, | ||
new SampleEmailDataType() { FirstName = "Shane", FamilyName = "Weaver" }, | ||
new SampleEmailDataType() { FirstName = "Vincent", FamilyName = "Gromfeld" }, | ||
new SampleEmailDataType() { FirstName = "Sergio", FamilyName = "Pedri" }, | ||
new SampleEmailDataType() { FirstName = "Alex", FamilyName = "Wilber" }, | ||
new SampleEmailDataType() { FirstName = "Allan", FamilyName = "Deyoung" }, | ||
new SampleEmailDataType() { FirstName = "Adele", FamilyName = "Vance" }, | ||
new SampleEmailDataType() { FirstName = "Grady", FamilyName = "Archie" }, | ||
new SampleEmailDataType() { FirstName = "Megan", FamilyName = "Bowen" }, | ||
new SampleEmailDataType() { FirstName = "Ben", FamilyName = "Walters" }, | ||
new SampleEmailDataType() { FirstName = "Debra", FamilyName = "Berger" }, | ||
new SampleEmailDataType() { FirstName = "Emily", FamilyName = "Braun" }, | ||
new SampleEmailDataType() { FirstName = "Christine", FamilyName = "Cline" }, | ||
new SampleEmailDataType() { FirstName = "Enrico", FamilyName = "Catteneo" }, | ||
new SampleEmailDataType() { FirstName = "Davit", FamilyName = "Badalyan" }, | ||
new SampleEmailDataType() { FirstName = "Diego", FamilyName = "Siciliani" }, | ||
new SampleEmailDataType() { FirstName = "Raul", FamilyName = "Razo" }, | ||
new SampleEmailDataType() { FirstName = "Miriam", FamilyName = "Graham" }, | ||
new SampleEmailDataType() { FirstName = "Lynne", FamilyName = "Robbins" }, | ||
new SampleEmailDataType() { FirstName = "Lydia", FamilyName = "Holloway" }, | ||
new SampleEmailDataType() { FirstName = "Nestor", FamilyName = "Wilke" }, | ||
new SampleEmailDataType() { FirstName = "Patti", FamilyName = "Fernandez" }, | ||
new SampleEmailDataType() { FirstName = "Pradeep", FamilyName = "Gupta" }, | ||
new SampleEmailDataType() { FirstName = "Joni", FamilyName = "Sherman" }, | ||
new SampleEmailDataType() { FirstName = "Isaiah", FamilyName = "Langer" }, | ||
new SampleEmailDataType() { FirstName = "Irvin", FamilyName = "Sayers" }, | ||
new SampleEmailDataType() { FirstName = "Tung", FamilyName = "Huynh" }, | ||
}; | ||
|
||
private readonly List<SampleDataType> _samples = new List<SampleDataType>() | ||
{ | ||
new SampleDataType() { Text = "Account", Icon = Symbol.Account }, | ||
new SampleDataType() { Text = "Add Friend", Icon = Symbol.AddFriend }, | ||
new SampleDataType() { Text = "Attach", Icon = Symbol.Attach }, | ||
new SampleDataType() { Text = "Attach Camera", Icon = Symbol.AttachCamera }, | ||
new SampleDataType() { Text = "Audio", Icon = Symbol.Audio }, | ||
new SampleDataType() { Text = "Block Contact", Icon = Symbol.BlockContact }, | ||
new SampleDataType() { Text = "Calculator", Icon = Symbol.Calculator }, | ||
new SampleDataType() { Text = "Calendar", Icon = Symbol.Calendar }, | ||
new SampleDataType() { Text = "Camera", Icon = Symbol.Camera }, | ||
new SampleDataType() { Text = "Contact", Icon = Symbol.Contact }, | ||
new SampleDataType() { Text = "Favorite", Icon = Symbol.Favorite }, | ||
new SampleDataType() { Text = "Link", Icon = Symbol.Link }, | ||
new SampleDataType() { Text = "Mail", Icon = Symbol.Mail }, | ||
new SampleDataType() { Text = "Map", Icon = Symbol.Map }, | ||
new SampleDataType() { Text = "Phone", Icon = Symbol.Phone }, | ||
new SampleDataType() { Text = "Pin", Icon = Symbol.Pin }, | ||
new SampleDataType() { Text = "Rotate", Icon = Symbol.Rotate }, | ||
new SampleDataType() { Text = "Rotate Camera", Icon = Symbol.RotateCamera }, | ||
new SampleDataType() { Text = "Send", Icon = Symbol.Send }, | ||
new SampleDataType() { Text = "Tags", Icon = Symbol.Tag }, | ||
new SampleDataType() { Text = "UnFavorite", Icon = Symbol.UnFavorite }, | ||
new SampleDataType() { Text = "UnPin", Icon = Symbol.UnPin }, | ||
new SampleDataType() { Text = "Zoom", Icon = Symbol.Zoom }, | ||
new SampleDataType() { Text = "ZoomIn", Icon = Symbol.ZoomIn }, | ||
new SampleDataType() { Text = "ZoomOut", Icon = Symbol.ZoomOut }, | ||
}; | ||
|
||
private RichSuggestBox _rsb; | ||
private RichSuggestBox _tsb; | ||
private DispatcherQueue _dispatcherQueue; | ||
|
||
public RichSuggestBoxPage() | ||
{ | ||
this.InitializeComponent(); | ||
this._dispatcherQueue = DispatcherQueue.GetForCurrentThread(); | ||
Loaded += (sender, e) => { this.OnXamlRendered(this); }; | ||
} | ||
|
||
public void OnXamlRendered(FrameworkElement control) | ||
{ | ||
if (this._rsb != null) | ||
{ | ||
this._rsb.SuggestionChosen -= this.SuggestingBox_OnSuggestionChosen; | ||
this._rsb.SuggestionRequested -= this.SuggestingBox_OnSuggestionRequested; | ||
} | ||
|
||
if (this._tsb != null) | ||
{ | ||
this._tsb.SuggestionChosen -= this.SuggestingBox_OnSuggestionChosen; | ||
this._tsb.SuggestionRequested -= this.SuggestingBox_OnSuggestionRequested; | ||
this._tsb.TokenPointerOver -= this.SuggestingBox_OnTokenPointerOver; | ||
} | ||
|
||
if (control.FindChild("SuggestingBox") is RichSuggestBox rsb) | ||
{ | ||
this._rsb = rsb; | ||
this._rsb.SuggestionChosen += this.SuggestingBox_OnSuggestionChosen; | ||
this._rsb.SuggestionRequested += this.SuggestingBox_OnSuggestionRequested; | ||
} | ||
|
||
if (control.FindChild("PlainTextSuggestingBox") is RichSuggestBox tsb) | ||
{ | ||
this._tsb = tsb; | ||
this._tsb.SuggestionChosen += this.SuggestingBox_OnSuggestionChosen; | ||
this._tsb.SuggestionRequested += this.SuggestingBox_OnSuggestionRequested; | ||
this._tsb.TokenPointerOver += this.SuggestingBox_OnTokenPointerOver; | ||
} | ||
|
||
if (control.FindChild("TokenListView1") is ListView tls1) | ||
{ | ||
tls1.ItemsSource = this._rsb?.Tokens; | ||
} | ||
|
||
if (control.FindChild("TokenListView2") is ListView tls2) | ||
{ | ||
tls2.ItemsSource = this._tsb?.Tokens; | ||
} | ||
} | ||
|
||
private void SuggestingBox_OnTokenPointerOver(RichSuggestBox sender, RichSuggestTokenPointerOverEventArgs args) | ||
{ | ||
var flyout = (Flyout)FlyoutBase.GetAttachedFlyout(sender); | ||
var pointerPosition = args.CurrentPoint.Position; | ||
|
||
if (flyout?.Content is ContentPresenter cp && sender.TextDocument.Selection.Type != SelectionType.Normal && | ||
(!flyout.IsOpen || cp.Content != args.Token.Item)) | ||
{ | ||
this._dispatcherQueue.TryEnqueue(() => | ||
{ | ||
cp.Content = args.Token.Item; | ||
flyout.ShowAt(sender, new FlyoutShowOptions | ||
{ | ||
Position = pointerPosition, | ||
ExclusionRect = sender.GetRectFromRange(args.Range), | ||
ShowMode = FlyoutShowMode.TransientWithDismissOnPointerMoveAway, | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
private void SuggestingBox_OnSuggestionChosen(RichSuggestBox sender, SuggestionChosenEventArgs args) | ||
{ | ||
if (args.Prefix == "#") | ||
{ | ||
args.Format.BackgroundColor = Colors.DarkOrange; | ||
args.Format.ForegroundColor = Colors.OrangeRed; | ||
args.Format.Bold = FormatEffect.On; | ||
args.Format.Italic = FormatEffect.On; | ||
args.DisplayText = ((SampleDataType)args.SelectedItem).Text; | ||
} | ||
else | ||
{ | ||
args.DisplayText = ((SampleEmailDataType)args.SelectedItem).DisplayName; | ||
} | ||
} | ||
|
||
private void SuggestingBox_OnSuggestionRequested(RichSuggestBox sender, SuggestionRequestedEventArgs args) | ||
{ | ||
if (args.Prefix == "#") | ||
{ | ||
sender.ItemsSource = | ||
this._samples.Where(x => x.Text.Contains(args.QueryText, StringComparison.OrdinalIgnoreCase)); | ||
} | ||
else | ||
{ | ||
sender.ItemsSource = | ||
this._emailSamples.Where(x => x.DisplayName.Contains(args.QueryText, StringComparison.OrdinalIgnoreCase)); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.