ionite34
10 months ago
1 changed files with 141 additions and 0 deletions
@ -0,0 +1,141 @@
|
||||
using System; |
||||
using System.Linq; |
||||
using System.Reactive; |
||||
using System.Reactive.Disposables; |
||||
using System.Reactive.Linq; |
||||
using DynamicData; |
||||
using DynamicData.Binding; |
||||
using JetBrains.Annotations; |
||||
|
||||
namespace StabilityMatrix.Avalonia.Collections; |
||||
|
||||
[PublicAPI] |
||||
public class SearchCollection<TObject, TKey, TQuery> : AbstractNotifyPropertyChanged, IDisposable |
||||
where TObject : notnull |
||||
where TKey : notnull |
||||
{ |
||||
private readonly IDisposable cleanUp; |
||||
|
||||
private Func<TQuery?, Func<TObject, bool>>? PredicateSelector { get; } |
||||
private Func<TQuery?, Func<TObject, (bool, int)>>? ScorerSelector { get; } |
||||
private Func<TObject, (bool, int)>? Scorer { get; set; } |
||||
|
||||
private TQuery? _query; |
||||
public TQuery? Query |
||||
{ |
||||
get => _query; |
||||
set => SetAndRaise(ref _query, value); |
||||
} |
||||
|
||||
private SortExpressionComparer<TObject> _sortComparer = []; |
||||
public SortExpressionComparer<TObject> SortComparer |
||||
{ |
||||
get => _sortComparer; |
||||
set => SetAndRaise(ref _sortComparer, value); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Converts <see cref="SortComparer"/> to <see cref="SortExpressionComparer{SearchItem}"/>. |
||||
/// </summary> |
||||
private SortExpressionComparer<SearchItem> SearchItemSortComparer => |
||||
[ |
||||
..SortComparer |
||||
.Select(sortExpression => new SortExpression<SearchItem>( |
||||
item => sortExpression.Expression(item.Item), |
||||
sortExpression.Direction |
||||
)).Prepend(new SortExpression<SearchItem>(item => item.Score, SortDirection.Descending)) |
||||
]; |
||||
|
||||
public IObservableCollection<TObject> Items { get; } = new ObservableCollectionExtended<TObject>(); |
||||
|
||||
public IObservableCollection<TObject> FilteredItems { get; } = |
||||
new ObservableCollectionExtended<TObject>(); |
||||
|
||||
public SearchCollection( |
||||
IObservable<IChangeSet<TObject, TKey>> source, |
||||
Func<TQuery?, Func<TObject, bool>> predicateSelector, |
||||
SortExpressionComparer<TObject>? sortComparer = null |
||||
) |
||||
{ |
||||
PredicateSelector = predicateSelector; |
||||
|
||||
if (sortComparer is not null) |
||||
{ |
||||
SortComparer = sortComparer; |
||||
} |
||||
|
||||
// Observable which creates a new predicate whenever Query property changes |
||||
var dynamicPredicate = this.WhenValueChanged(@this => @this.Query).Select(predicateSelector); |
||||
|
||||
cleanUp = source |
||||
.Bind(Items) |
||||
.Filter(dynamicPredicate) |
||||
.Sort(SortComparer) |
||||
.Bind(FilteredItems) |
||||
.Subscribe(); |
||||
} |
||||
|
||||
public SearchCollection( |
||||
IObservable<IChangeSet<TObject, TKey>> source, |
||||
Func<TQuery?, Func<TObject, (bool, int)>> scorerSelector, |
||||
SortExpressionComparer<TObject>? sortComparer = null |
||||
) |
||||
{ |
||||
ScorerSelector = scorerSelector; |
||||
|
||||
if (sortComparer is not null) |
||||
{ |
||||
SortComparer = sortComparer; |
||||
} |
||||
|
||||
// Monitor Query property for changes |
||||
var queryChanged = this.WhenValueChanged(@this => @this.Query).Select(_ => Unit.Default); |
||||
|
||||
cleanUp = new CompositeDisposable( |
||||
// Update Scorer property whenever Query property changes |
||||
queryChanged.Subscribe(_ => Scorer = scorerSelector(Query)), |
||||
// Transform source items into SearchItems |
||||
source |
||||
.Transform( |
||||
obj => |
||||
{ |
||||
var (isMatch, score) = Scorer?.Invoke(obj) ?? (true, 0); |
||||
|
||||
return new SearchItem |
||||
{ |
||||
Item = obj, |
||||
IsMatch = isMatch, |
||||
Score = score |
||||
}; |
||||
}, |
||||
forceTransform: queryChanged |
||||
) |
||||
.Filter(item => item.IsMatch) |
||||
.Sort(SearchItemSortComparer, SortOptimisations.ComparesImmutableValuesOnly) |
||||
.Transform(searchItem => searchItem.Item) |
||||
.Bind(FilteredItems) |
||||
.Subscribe() |
||||
); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Clears <see cref="Query"/> property by setting it to default value. |
||||
/// </summary> |
||||
public void ClearQuery() |
||||
{ |
||||
Query = default; |
||||
} |
||||
|
||||
public void Dispose() |
||||
{ |
||||
cleanUp.Dispose(); |
||||
GC.SuppressFinalize(this); |
||||
} |
||||
|
||||
private readonly record struct SearchItem |
||||
{ |
||||
public TObject Item { get; init; } |
||||
public int Score { get; init; } |
||||
public bool IsMatch { get; init; } |
||||
} |
||||
} |
Loading…
Reference in new issue