< Summary

Information
Class: MyNet.Observable.Collections.Selection.SelectionEngine<T>
Assembly: MyNet.Observable
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Collections/Selection/SelectionEngine.cs
Tag: 323_28699572109
Line coverage
86%
Covered lines: 73
Uncovered lines: 11
Coverable lines: 84
Total lines: 245
Line coverage: 86.9%
Branch coverage
84%
Covered branches: 38
Total branches: 45
Branch coverage: 84.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_SelectedItems()100%11100%
get_Count()100%11100%
Connect()100%11100%
Select(...)100%44100%
Unselect(...)100%11100%
Toggle(...)87.5%8892.3%
Clear()100%11100%
Set(...)87.5%88100%
Contains(...)100%11100%
Publish()100%11100%
ApplySourceChanges(...)72%712558.06%
Dispose()100%11100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Observable/Collections/Selection/SelectionEngine.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="SelectionEngine.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Linq;
 10using System.Reactive.Disposables;
 11using System.Reactive.Subjects;
 12using DynamicData;
 13
 14namespace MyNet.Observable.Collections.Selection;
 15
 16/// <summary>
 17/// Manages the selection state of a collection of items, enforcing selection rules based on the specified selection mod
 18/// </summary>
 19/// <typeparam name="T">The type of items in the collection, which must implement INotifyPropertyChanged.</typeparam>
 20public sealed class SelectionEngine<T> : IDisposable
 21    where T : notnull
 22{
 8723    private readonly HashSet<T> _items = [];
 8724    private readonly HashSet<T> _selected = [];
 25    private readonly BehaviorSubject<IReadOnlyCollection<T>> _subject;
 8726    private readonly CompositeDisposable _disposables = [];
 27
 28    /// <summary>
 29    /// Initializes a new instance of the <see cref="SelectionEngine{T}"/> class with the specified source of items, sel
 30    /// </summary>
 31    /// <param name="source">The source of items to be managed by the selection engine.</param>
 32    /// <param name="mode">The selection mode to be enforced by the selection engine (single or multiple).</param>
 33    public SelectionEngine(IObservable<IChangeSet<T>> source, SelectionMode mode)
 34    {
 8735        Mode = mode;
 8736        _subject = new([]);
 37
 8738        _disposables.Add(source.Subscribe(ApplySourceChanges));
 8739    }
 40
 41    /// <summary>
 42    /// Gets the selection mode enforced by the selection engine. This property indicates whether the selection engine a
 43    /// </summary>
 44    public SelectionMode Mode { get; }
 45
 46    /// <summary>
 47    /// Gets a read-only collection of the currently selected items. The collection is automatically updated as the sele
 48    /// </summary>
 7249    public IReadOnlyCollection<T> SelectedItems => _selected;
 50
 51    /// <summary>
 52    /// Gets the count of currently selected items. This property provides a quick way to determine how many items are c
 53    /// </summary>
 954    public int Count => _selected.Count;
 55
 56    /// <summary>
 57    /// Connects to the selection engine and returns an observable sequence of the currently selected items. This allows
 58    /// </summary>
 59    /// <returns>An observable sequence of the currently selected items.</returns>
 360    public IObservable<IReadOnlyCollection<T>> Connect() => _subject;
 61
 62    /// <summary>
 63    /// Selects the specified item according to the selection rules defined by the current selection mode. If the item i
 64    /// </summary>
 65    /// <param name="item">The item to be selected.</param>
 66    public void Select(T item)
 67    {
 5768        if (!_items.Contains(item))
 369            return;
 70
 5471        if (Mode == SelectionMode.Single)
 2472            _selected.Clear();
 73
 5474        _selected.Add(item);
 5475        Publish();
 5476    }
 77
 78    /// <summary>
 79    /// Deselects the specified item. If the item is not selectable, this method does nothing. This method provides a co
 80    /// </summary>
 81    /// <param name="item">The item to be deselected.</param>
 82    public void Unselect(T item)
 83    {
 684        _selected.Remove(item);
 685        Publish();
 686    }
 87
 88    /// <summary>
 89    /// Toggles the selection state of the specified item. If the item is not selectable, this method does nothing. This
 90    /// </summary>
 91    /// <param name="item">The item whose selection state is to be toggled.</param>
 92    public void Toggle(T item)
 93    {
 1294        if (!_items.Contains(item))
 095            return;
 96
 1297        if (Mode == SelectionMode.Single)
 98        {
 699            if (_selected.Contains(item))
 100            {
 3101                _selected.Clear();
 102            }
 103            else
 104            {
 3105                _selected.Clear();
 3106                _selected.Add(item);
 107            }
 108
 6109            Publish();
 6110            return;
 111        }
 112
 6113        if (!_selected.Add(item))
 3114            _selected.Remove(item);
 115
 6116        Publish();
 6117    }
 118
 119    /// <summary>
 120    /// Clears all selections in the collection. This method deselects all currently selected items, regardless of the c
 121    /// </summary>
 122    public void Clear()
 123    {
 6124        _selected.Clear();
 6125        Publish();
 6126    }
 127
 128    /// <summary>
 129    /// Sets the selection state of the specified items according to the selection rules defined by the current selectio
 130    /// </summary>
 131    /// <param name="items">The items whose selection state is to be set.</param>
 132    public void Set(IEnumerable<T> items)
 133    {
 18134        _selected.Clear();
 135
 18136        if (Mode == SelectionMode.Single)
 137        {
 3138            var first = items.FirstOrDefault(_items.Contains);
 3139            if (first is not null)
 3140                _selected.Add(first);
 141
 3142            Publish();
 3143            return;
 144        }
 145
 96146        foreach (var item in items)
 147        {
 33148            if (_items.Contains(item))
 30149                _selected.Add(item);
 150        }
 151
 15152        Publish();
 15153    }
 154
 155    /// <summary>
 156    /// Determines whether the specified item is currently selected in the collection. This method checks if the given i
 157    /// </summary>
 158    /// <param name="item">The wrapper representing the item to check.</param>
 159    /// <returns>True if the item is selected; otherwise, false.</returns>
 6160    public bool Contains(T item) => _selected.Contains(item);
 161
 162    /// <summary>
 163    /// Publishes the current selection state to the observers. This method is called whenever there is a change in the 
 164    /// </summary>
 102165    private void Publish() => _subject.OnNext([.. _selected]);
 166
 167    /// <summary>
 168    /// Rebuilds the selection state based on the current items in the collection. This method is called when there are 
 169    /// </summary>
 170    private void ApplySourceChanges(IChangeSet<T> changes)
 171    {
 234172        var selectionChanged = false;
 173
 960174        foreach (var change in changes)
 175        {
 246176            switch (change.Reason)
 177            {
 178                case ListChangeReason.Add:
 21179                    _items.Add(change.Item.Current);
 21180                    break;
 181                case ListChangeReason.AddRange:
 102504182                    foreach (var item in change.Range)
 51111183                        _items.Add(item);
 184
 185                    break;
 186                case ListChangeReason.Remove:
 3187                    _items.Remove(change.Item.Current);
 3188                    selectionChanged |= _selected.Remove(change.Item.Current);
 3189                    break;
 190                case ListChangeReason.RemoveRange:
 18191                    foreach (var item in change.Range)
 192                    {
 6193                        _items.Remove(item);
 6194                        selectionChanged |= _selected.Remove(item);
 195                    }
 196
 197                    break;
 198                case ListChangeReason.Replace:
 0199                    if (change.Item.Previous.HasValue)
 200                    {
 0201                        var previous = change.Item.Previous.Value;
 0202                        _items.Remove(previous);
 0203                        selectionChanged |= _selected.Remove(previous);
 204                    }
 205
 0206                    _items.Add(change.Item.Current);
 0207                    break;
 208                case ListChangeReason.Clear:
 78209                    _items.Clear();
 78210                    if (_selected.Count > 0)
 211                    {
 3212                        _selected.Clear();
 3213                        selectionChanged = true;
 214                    }
 215
 216                    break;
 217                case ListChangeReason.Moved:
 218                case ListChangeReason.Refresh:
 219                default:
 220                    break;
 221            }
 222        }
 223
 234224        if (Mode == SelectionMode.Single && _selected.Count > 1)
 225        {
 0226            var first = _selected.First();
 0227            _selected.Clear();
 0228            _selected.Add(first);
 0229            selectionChanged = true;
 230        }
 231
 234232        if (selectionChanged)
 6233            Publish();
 234234    }
 235
 236    /// <summary>
 237    /// Disposes of the resources used by the selection engine. This method should be called when the selection engine i
 238    /// </summary>
 239    public void Dispose()
 240    {
 42241        _subject.Dispose();
 42242        _disposables.Dispose();
 42243    }
 244}
 245