diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index 5ca00c15..7a51f3e3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -298,6 +298,7 @@ + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml index 8be405da..e3cab592 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml @@ -7,7 +7,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mxi="using:Microsoft.Xaml.Interactivity" - xmlns:mxic="using:Microsoft.Xaml.Interactions.Core" xmlns:shc="using:Snap.Hutao.Control" xmlns:shcb="using:Snap.Hutao.Control.Behavior" xmlns:shci="using:Snap.Hutao.Control.Image" @@ -74,10 +73,7 @@ - + @@ -272,18 +268,20 @@ - + QueryIcon="{cw:FontIconSource Glyph=}" + SuggestedItemTemplate="{StaticResource SuggestionTemplate}" + SuggestedItemsSource="{Binding AvailableQueries}" + Text="{Binding FilterToken, Mode=TwoWay}" + TokenItemTemplate="{StaticResource SuggestionTemplate}"/> (); InitializeComponent(); - viewModel.Initialize(new AutoSuggestBoxAccessor(AvatarSuggestBox)); + viewModel.Initialize(new TokenizingTextBoxAccessor(AvatarSuggestBox)); } - private class AutoSuggestBoxAccessor : IAutoSuggestBoxAccessor + private class TokenizingTextBoxAccessor : ITokenizingTextBoxAccessor { - public AutoSuggestBoxAccessor(AutoSuggestBox autoSuggestBox) + public TokenizingTextBoxAccessor(TokenizingTextBox tokenizingTextBox) { - AutoSuggestBox = autoSuggestBox; + TokenizingTextBox = tokenizingTextBox; } - public AutoSuggestBox AutoSuggestBox { get; private set; } + public TokenizingTextBox TokenizingTextBox { get; private set; } } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml index 5358afed..219c399c 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiWeaponPage.xaml @@ -33,10 +33,7 @@ - + @@ -138,17 +135,21 @@ LocalSettingKeySuffixForCurrent="WikiWeaponPage.Weapons"/> - + QueryIcon="{cw:FontIconSource Glyph=}" + SuggestedItemTemplate="{StaticResource SuggestionTemplate}" + SuggestedItemsSource="{Binding AvailableQueries}" + Text="{Binding FilterToken, Mode=TwoWay}" + TokenItemTemplate="{StaticResource SuggestionTemplate}"/> (); InitializeComponent(); - viewModel.Initialize(new AutoSuggestBoxAccessor(WeaponSuggestBox)); + viewModel.Initialize(new TokenizingTextBoxAccessor(WeaponSuggestBox)); } - private class AutoSuggestBoxAccessor : IAutoSuggestBoxAccessor + private class TokenizingTextBoxAccessor : ITokenizingTextBoxAccessor { - public AutoSuggestBoxAccessor(AutoSuggestBox autoSuggestBox) + public TokenizingTextBoxAccessor(TokenizingTextBox tokenizingTextBox) { - AutoSuggestBox = autoSuggestBox; + TokenizingTextBox = tokenizingTextBox; } - public AutoSuggestBox AutoSuggestBox { get; private set; } + public TokenizingTextBox TokenizingTextBox { get; private set; } } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IAutoSuggestBoxAccessor.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/ITokenizingTextBoxAccessor.cs similarity index 50% rename from src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IAutoSuggestBoxAccessor.cs rename to src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/ITokenizingTextBoxAccessor.cs index 2f5233ca..64964f1e 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IAutoSuggestBoxAccessor.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/ITokenizingTextBoxAccessor.cs @@ -1,12 +1,12 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.UI.Xaml.Controls; +using CommunityToolkit.WinUI.Controls; using Snap.Hutao.Control; namespace Snap.Hutao.ViewModel.Wiki; -internal interface IAutoSuggestBoxAccessor : IXamlElementAccessor +internal interface ITokenizingTextBoxAccessor : IXamlElementAccessor { - AutoSuggestBox AutoSuggestBox { get; } + TokenizingTextBox TokenizingTextBox { get; } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IWikiViewModelInitialization.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IWikiViewModelInitialization.cs index 45335020..c41c48cf 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IWikiViewModelInitialization.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/IWikiViewModelInitialization.cs @@ -5,5 +5,5 @@ namespace Snap.Hutao.ViewModel.Wiki; internal interface IWikiViewModelInitialization { - void Initialize(IAutoSuggestBoxAccessor accessor); + void Initialize(ITokenizingTextBoxAccessor accessor); } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index 2c2283cc..53f44fbf 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using CommunityToolkit.WinUI.Controls; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Control.Collection.AdvancedCollectionView; using Snap.Hutao.Factory.ContentDialog; @@ -20,6 +21,7 @@ using Snap.Hutao.Service.User; using Snap.Hutao.View.Dialog; using Snap.Hutao.Web.Response; using System.Collections.Frozen; +using System.Collections.ObjectModel; using System.Runtime.InteropServices; using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; @@ -48,7 +50,8 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel, IWiki private AdvancedCollectionView? avatars; private Avatar? selected; - private string? filterText; + private ObservableCollection? filterTokens; + private string? filterToken; private BaseValueInfo? baseValueInfo; private Dictionary>? levelAvatarCurveMap; private List? promotes; @@ -79,15 +82,20 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel, IWiki public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); } /// - /// 筛选文本 + /// 保存的筛选标志 /// - public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); } + public ObservableCollection? FilterTokens { get => filterTokens; set => SetProperty(ref filterTokens, value); } - public void Initialize(IAutoSuggestBoxAccessor accessor) + public string? FilterToken { get => filterToken; set => SetProperty(ref filterToken, value); } + + public FrozenSet? AvailableQueries { get => availableQueries; } + + public void Initialize(ITokenizingTextBoxAccessor accessor) { - accessor.AutoSuggestBox.TextChanged += OnFilterSuggestionRequested; - accessor.AutoSuggestBox.SuggestionChosen += OnFilterSuggestionChosen; - accessor.AutoSuggestBox.QuerySubmitted += ApplyFilter; + accessor.TokenizingTextBox.TextChanged += OnFilterSuggestionRequested; + accessor.TokenizingTextBox.QuerySubmitted += OnQuerySubmitted; + accessor.TokenizingTextBox.TokenItemAdded += OnTokenItemModified; + accessor.TokenizingTextBox.TokenItemRemoved += OnTokenItemModified; } protected override async ValueTask InitializeUIAsync() @@ -112,6 +120,7 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel, IWiki await taskContext.SwitchToMainThreadAsync(); Avatars = new(list, true); Selected = Avatars.View.ElementAtOrDefault(0); + FilterTokens = []; availableQueries = FrozenSet.ToFrozenSet( [ @@ -231,36 +240,46 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel, IWiki return; } - if (string.IsNullOrWhiteSpace(FilterText)) + if (string.IsNullOrWhiteSpace(FilterToken)) { return; } if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) { - sender.ItemsSource = availableQueries.Where(q => q.Contains(FilterText, StringComparison.OrdinalIgnoreCase)); + sender.ItemsSource = availableQueries.Where(q => q.Contains(FilterToken, StringComparison.OrdinalIgnoreCase)); } } - private void OnFilterSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + private void OnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { - sender.Text = args.SelectedItem.ToString(); + if (args.ChosenSuggestion is not null) + { + return; + } + + ApplyFilter(); } - private void ApplyFilter(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + private void OnTokenItemModified(TokenizingTextBox sender, object args) + { + ApplyFilter(); + } + + private void ApplyFilter() { if (Avatars is null) { return; } - if (string.IsNullOrWhiteSpace(FilterText)) + if (FilterTokens.IsNullOrEmpty()) { Avatars.Filter = default!; return; } - Avatars.Filter = AvatarFilter.Compile(FilterText); + Avatars.Filter = AvatarFilter.Compile(string.Join(' ', FilterTokens)); if (Selected is not null && Avatars.Contains(Selected)) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index 2cc99cbb..a1912c90 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using CommunityToolkit.WinUI.Controls; using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Control.Collection.AdvancedCollectionView; using Snap.Hutao.Factory.ContentDialog; @@ -45,7 +46,8 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel, IWiki private AdvancedCollectionView? weapons; private Weapon? selected; - private string? filterText; + private List? filterTokens; + private string? filterToken; private BaseValueInfo? baseValueInfo; private Dictionary>? levelWeaponCurveMap; private List? promotes; @@ -76,15 +78,20 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel, IWiki public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); } /// - /// 筛选文本 + /// 保存的筛选标志 /// - public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); } + public List? FilterTokens { get => filterTokens; set => SetProperty(ref filterTokens, value); } - public void Initialize(IAutoSuggestBoxAccessor accessor) + public string? FilterToken { get => filterToken; set => SetProperty(ref filterToken, value); } + + public FrozenSet AvailableQueries { get => availableQueries; } + + public void Initialize(ITokenizingTextBoxAccessor accessor) { - accessor.AutoSuggestBox.TextChanged += OnFilterSuggestionRequested; - accessor.AutoSuggestBox.SuggestionChosen += OnFilterSuggestionChosen; - accessor.AutoSuggestBox.QuerySubmitted += ApplyFilter; + accessor.TokenizingTextBox.TextChanged += OnFilterSuggestionRequested; + accessor.TokenizingTextBox.QuerySubmitted += OnQuerySubmitted; + accessor.TokenizingTextBox.TokenItemAdded += OnTokenItemModified; + accessor.TokenizingTextBox.TokenItemRemoved += OnTokenItemModified; } /// @@ -109,6 +116,7 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel, IWiki Weapons = new(list, true); Selected = Weapons.View.ElementAtOrDefault(0); + FilterTokens = []; availableQueries = FrozenSet.ToFrozenSet( [ @@ -213,36 +221,46 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel, IWiki return; } - if (string.IsNullOrWhiteSpace(FilterText)) + if (string.IsNullOrWhiteSpace(FilterToken)) { return; } if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) { - sender.ItemsSource = availableQueries.Where(q => q.Contains(FilterText, StringComparison.OrdinalIgnoreCase)); + sender.ItemsSource = availableQueries.Where(q => q.Contains(FilterToken, StringComparison.OrdinalIgnoreCase)); } } - private void OnFilterSuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + private void OnQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { - sender.Text = args.SelectedItem.ToString(); + if (args.ChosenSuggestion is not null) + { + return; + } + + ApplyFilter(); } - private void ApplyFilter(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + private void OnTokenItemModified(TokenizingTextBox sender, object args) + { + ApplyFilter(); + } + + private void ApplyFilter() { if (Weapons is null) { return; } - if (string.IsNullOrWhiteSpace(FilterText)) + if (FilterTokens.IsNullOrEmpty()) { Weapons.Filter = default!; return; } - Weapons.Filter = WeaponFilter.Compile(FilterText); + Weapons.Filter = WeaponFilter.Compile(string.Join(' ', FilterTokens)); if (Selected is not null && Weapons.Contains(Selected)) {