DeclarativeProperty nullability fixes

This commit is contained in:
2023-09-27 15:58:59 +02:00
parent 30638c6c97
commit 373847b1ad
25 changed files with 163 additions and 151 deletions

View File

@@ -88,7 +88,7 @@ public record SizeScanContainer : ISizeScanContainer
return Task.CompletedTask; return Task.CompletedTask;
} }
public required IDeclarativeProperty<string> Status { get; init; } = new DeclarativeProperty<string>(); public required IDeclarativeProperty<string> Status { get; init; } = new DeclarativeProperty<string>(string.Empty);
public required SizeScanTask SizeScanTask { get; init; } public required SizeScanTask SizeScanTask { get; init; }

View File

@@ -17,7 +17,7 @@ public class SizeScanTask : ISizeScanTask
private readonly ILogger<SizeScanTask> _logger; private readonly ILogger<SizeScanTask> _logger;
private Thread? _sizeScanThread; private Thread? _sizeScanThread;
private static int _searchId = 1; private static int _searchId = 1;
private readonly DeclarativeProperty<string> _containerStatus = new(); private readonly DeclarativeProperty<string> _containerStatus = new(string.Empty);
private readonly IDeclarativeProperty<string> _containerStatusDebounced; private readonly IDeclarativeProperty<string> _containerStatusDebounced;
public ISizeScanContainer SizeSizeScanContainer { get; private set; } = null!; public ISizeScanContainer SizeSizeScanContainer { get; private set; } = null!;
public bool IsRunning { get; private set; } public bool IsRunning { get; private set; }

View File

@@ -13,7 +13,7 @@ namespace FileTime.App.Core.ViewModels;
public abstract partial class AppStateBase : IAppState public abstract partial class AppStateBase : IAppState
{ {
private readonly BehaviorSubject<string?> _searchText = new(null); private readonly BehaviorSubject<string?> _searchText = new(null);
private readonly DeclarativeProperty<ITabViewModel?> _selectedTab = new(); private readonly DeclarativeProperty<ITabViewModel?> _selectedTab = new(null);
private readonly DeclarativeProperty<ViewMode> _viewMode = new(Models.Enums.ViewMode.Default); private readonly DeclarativeProperty<ViewMode> _viewMode = new(Models.Enums.ViewMode.Default);
private readonly ObservableCollection<ITabViewModel> _tabs = new(); private readonly ObservableCollection<ITabViewModel> _tabs = new();
private readonly DeclarativeProperty<string?> _rapidTravelText; private readonly DeclarativeProperty<string?> _rapidTravelText;

View File

@@ -15,11 +15,11 @@ public class CopyCommand : CommandBase, ITransportationCommand
private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly CancellationTokenSource _cancellationTokenSource = new();
private readonly List<OperationProgress> _operationProgresses = new(); private readonly List<OperationProgress> _operationProgresses = new();
private readonly DeclarativeProperty<OperationProgress?> _currentOperationProgress = new(); private readonly DeclarativeProperty<OperationProgress?> _currentOperationProgress = new(null);
private long _recentTotalSum; private long _recentTotalSum;
private readonly DeclarativeProperty<long> _recentTotalProcessed = new(); private readonly DeclarativeProperty<long> _recentTotalProcessed = new(0);
private readonly DeclarativeProperty<DateTime> _recentStartTime = new(); private readonly DeclarativeProperty<DateTime> _recentStartTime = new(DateTime.Now);
public IReadOnlyList<FullName> Sources { get; } public IReadOnlyList<FullName> Sources { get; }

View File

@@ -19,9 +19,9 @@ public class Tab : ITab
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ITabEvents _tabEvents; private readonly ITabEvents _tabEvents;
private readonly DeclarativeProperty<IContainer?> _currentLocation = new(); private readonly DeclarativeProperty<IContainer?> _currentLocation = new(null);
private readonly DeclarativeProperty<IContainer?> _currentLocationForced = new(); private readonly DeclarativeProperty<IContainer?> _currentLocationForced = new(null);
private readonly DeclarativeProperty<AbsolutePath?> _currentRequestItem = new(); private readonly DeclarativeProperty<AbsolutePath?> _currentRequestItem = new(null);
private readonly ObservableCollection<ItemFilter> _itemFilters = new(); private readonly ObservableCollection<ItemFilter> _itemFilters = new();
private readonly CircularBuffer<FullName> _history = new(20); private readonly CircularBuffer<FullName> _history = new(20);
private readonly CircularBuffer<FullName> _future = new(20); private readonly CircularBuffer<FullName> _future = new(20);
@@ -64,28 +64,26 @@ public class Tab : ITab
return Task.CompletedTask; return Task.CompletedTask;
}); });
CurrentItems = DeclarativePropertyHelpers.CombineLatest( IDeclarativeProperty<ObservableCollection<IItem>?> asd1 = DeclarativePropertyHelpers.CombineLatest(
CurrentLocation, CurrentLocation,
itemFiltersProperty, itemFiltersProperty,
(container, filters) => Task<ObservableCollection<IItem>?> (container, filters) =>
{ {
ObservableCollection<IItem>? items = null; ObservableCollection<IItem>? items = null;
if (container is not null) if (container is null) return Task.FromResult(items);
{
items = container items = container
.Items .Items
.Selecting<AbsolutePath, IItem>(i => MapItem(i)); .Selecting<AbsolutePath, IItem>(i => MapItem(i));
if (filters is not null)
{
items = items.Filtering(i => filters.All(f => f.Filter(i))); items = items.Filtering(i => filters.All(f => f.Filter(i)));
}
}
return Task.FromResult(items); return Task.FromResult(items)!;
} }
).CombineLatest( );
var asd2 = asd1.CombineLatest(
Ordering.Map(ordering => Ordering.Map(ordering =>
{ {
var (itemComparer, order) = ordering switch var (itemComparer, order) = ordering switch
@@ -103,7 +101,7 @@ public class Tab : ITab
return (itemComparer, order, ordering is not ItemOrdering.Name and not ItemOrdering.NameDesc); return (itemComparer, order, ordering is not ItemOrdering.Name and not ItemOrdering.NameDesc);
}), }),
(items, ordering) => Task<ObservableCollection<IItem>?> (items, ordering) =>
{ {
if (items is null) return Task.FromResult<ObservableCollection<IItem>?>(null); if (items is null) return Task.FromResult<ObservableCollection<IItem>?>(null);
@@ -117,14 +115,16 @@ public class Tab : ITab
? primaryOrderedItems.ThenOrdering(i => i.DisplayName) ? primaryOrderedItems.ThenOrdering(i => i.DisplayName)
: primaryOrderedItems; : primaryOrderedItems;
return Task.FromResult(orderedItems); return Task.FromResult<ObservableCollection<IItem>?>(orderedItems);
} }
); );
CurrentItems = asd2;
CurrentSelectedItem = DeclarativePropertyHelpers.CombineLatest( CurrentSelectedItem = DeclarativePropertyHelpers.CombineLatest(
CurrentItems.Watch<ObservableCollection<IItem>, IItem>(), CurrentItems.Watch<ObservableCollection<IItem>?, IItem>(),
_currentRequestItem.DistinctUntilChanged(), _currentRequestItem.DistinctUntilChanged(),
(items, selected) => Task<AbsolutePath?> (items, selected) =>
{ {
var itemSelectingContext = new LastItemSelectingContext(CurrentLocation.Value); var itemSelectingContext = new LastItemSelectingContext(CurrentLocation.Value);
var lastItemSelectingContext = _lastItemSelectingContext; var lastItemSelectingContext = _lastItemSelectingContext;
@@ -143,7 +143,7 @@ public class Tab : ITab
var candidate = _selectedItemCandidates.FirstOrDefault(c => items.Any(i => i.FullName?.Path == c.Path.Path)); var candidate = _selectedItemCandidates.FirstOrDefault(c => items.Any(i => i.FullName?.Path == c.Path.Path));
if (candidate != null) if (candidate != null)
{ {
return Task.FromResult(candidate); return Task.FromResult<AbsolutePath?>(candidate);
} }
} }
@@ -160,7 +160,7 @@ public class Tab : ITab
DeclarativePropertyHelpers.CombineLatest( DeclarativePropertyHelpers.CombineLatest(
CurrentItems, CurrentItems,
CurrentSelectedItem, CurrentSelectedItem,
(items, selected) => Task<IEnumerable<AbsolutePath>?> (items, selected) =>
{ {
if (items is null || selected is null) return Task.FromResult<IEnumerable<AbsolutePath>?>(null); if (items is null || selected is null) return Task.FromResult<IEnumerable<AbsolutePath>?>(null);
var primaryCandidates = items.SkipWhile(i => i.FullName is {Path: var p} && p != selected.Path.Path).Skip(1); var primaryCandidates = items.SkipWhile(i => i.FullName is {Path: var p} && p != selected.Path.Path).Skip(1);
@@ -169,7 +169,7 @@ public class Tab : ITab
.Concat(secondaryCandidates) .Concat(secondaryCandidates)
.Select(c => new AbsolutePath(_timelessContentProvider, c)); .Select(c => new AbsolutePath(_timelessContentProvider, c));
return Task.FromResult(candidates); return Task.FromResult<IEnumerable<AbsolutePath>?>(candidates);
}) })
.Subscribe(candidates => .Subscribe(candidates =>
{ {

View File

@@ -8,6 +8,6 @@ public interface IMainWindowViewModelBase
DeclarativeProperty<string> Title { get; } DeclarativeProperty<string> Title { get; }
bool Loading { get; } bool Loading { get; }
IObservable<string?> MainFont { get; } IObservable<string?> MainFont { get; }
DeclarativeProperty<string> FatalError { get; } DeclarativeProperty<string?> FatalError { get; }
IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; } IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; }
} }

View File

@@ -9,6 +9,6 @@ public class MainWindowLoadingViewModel : IMainWindowViewModelBase
public bool Loading => true; public bool Loading => true;
public IObservable<string?> MainFont { get; } = new BehaviorSubject<string?>(""); public IObservable<string?> MainFont { get; } = new BehaviorSubject<string?>("");
public DeclarativeProperty<string> Title { get; } = new("Loading..."); public DeclarativeProperty<string> Title { get; } = new("Loading...");
public DeclarativeProperty<string> FatalError { get; } = new(); public DeclarativeProperty<string?> FatalError { get; } = new(null);
public IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; } = new[] {WindowTransparencyLevel.AcrylicBlur}; public IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; } = new[] {WindowTransparencyLevel.AcrylicBlur};
} }

View File

@@ -53,10 +53,10 @@ public partial class MainWindowViewModel : IMainWindowViewModel
private readonly OcConsumer _rootDriveInfosConsumer = new(); private readonly OcConsumer _rootDriveInfosConsumer = new();
public bool Loading => false; public bool Loading => false;
public IObservable<string?> MainFont => _fontService.MainFont.Select(x => x ?? ""); public IObservable<string?> MainFont => _fontService.MainFont.Select(x => x ?? "");
public DeclarativeProperty<string> FatalError { get; } = new(); public DeclarativeProperty<string?> FatalError { get; } = new(null);
public IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; } = new[] {WindowTransparencyLevel.Blur}; public IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint { get; } = new[] {WindowTransparencyLevel.Blur};
public IGuiAppState AppState => _appState; public IGuiAppState AppState => _appState;
public DeclarativeProperty<string> Title { get; } = new(); public DeclarativeProperty<string> Title { get; } = new(string.Empty);
public Thickness IconStatusPanelMargin { get; private set; } = new(20, 10, 10, 10); public Thickness IconStatusPanelMargin { get; private set; } = new(20, 10, 10, 10);
public Action? FocusDefaultElement { get; set; } public Action? FocusDefaultElement { get; set; }
public Action? ShowWindow { get; set; } public Action? ShowWindow { get; set; }

View File

@@ -3,11 +3,11 @@
namespace DeclarativeProperty; namespace DeclarativeProperty;
public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropertyBase<TCollection> public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropertyBase<TCollection>
where TCollection : IList<TItem>, INotifyCollectionChanged where TCollection : IList<TItem>?, INotifyCollectionChanged?
{ {
private TCollection? _currentCollection; private TCollection? _currentCollection;
public CollectionRepeaterProperty(IDeclarativeProperty<TCollection?> from) : base(from.Value) public CollectionRepeaterProperty(IDeclarativeProperty<TCollection> from) : base(from.Value)
{ {
_currentCollection = from.Value; _currentCollection = from.Value;
if (from.Value is { } value) if (from.Value is { } value)
@@ -18,7 +18,7 @@ public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropert
AddDisposable(from.Subscribe(Handle)); AddDisposable(from.Subscribe(Handle));
} }
public CollectionRepeaterProperty(TCollection? collection) : base(collection) public CollectionRepeaterProperty(TCollection collection) : base(collection)
{ {
ArgumentNullException.ThrowIfNull(collection); ArgumentNullException.ThrowIfNull(collection);
@@ -32,7 +32,7 @@ public class CollectionRepeaterProperty<TCollection, TItem> : DeclarativePropert
t.Wait(); t.Wait();
} }
private async Task Handle(TCollection? collection, CancellationToken cancellationToken = default) private async Task Handle(TCollection collection, CancellationToken cancellationToken = default)
{ {
if (_currentCollection is { } currentCollection) if (_currentCollection is { } currentCollection)
{ {

View File

@@ -3,18 +3,18 @@
public class CombineAllProperty<T, TResult> : DeclarativePropertyBase<TResult> public class CombineAllProperty<T, TResult> : DeclarativePropertyBase<TResult>
{ {
private readonly List<IDeclarativeProperty<T>> _sources; private readonly List<IDeclarativeProperty<T>> _sources;
private readonly Func<IEnumerable<T>, Task<TResult?>> _combiner; private readonly Func<IEnumerable<T>, Task<TResult>> _combiner;
public CombineAllProperty( public CombineAllProperty(
IEnumerable<IDeclarativeProperty<T>> sources, IEnumerable<IDeclarativeProperty<T>> sources,
Func<IEnumerable<T>, Task<TResult?>> combiner, Func<IEnumerable<T>, Task<TResult>> combiner,
Action<TResult?>? setValueHook = null) : base(setValueHook) Action<TResult>? setValueHook = null) : base(default!, setValueHook)
{ {
var sourcesList = sources.ToList(); var sourcesList = sources.ToList();
_sources = sourcesList; _sources = sourcesList;
_combiner = combiner; _combiner = combiner;
var initialValueTask = _combiner(sourcesList.Select(p => p.Value)!); var initialValueTask = Task.Run(async () => await _combiner(sourcesList.Select(p => p.Value)));
initialValueTask.Wait(); initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result); SetNewValueSync(initialValueTask.Result);
@@ -24,12 +24,12 @@ public class CombineAllProperty<T, TResult> : DeclarativePropertyBase<TResult>
} }
} }
private async Task OnSourceChanged(T? arg1, CancellationToken arg2) => await Update(); private async Task OnSourceChanged(T arg1, CancellationToken arg2) => await Update();
private async Task Update() private async Task Update()
{ {
var values = _sources.Select(p => p.Value); var values = _sources.Select(p => p.Value);
var result = await _combiner(values!); var result = await _combiner(values);
await SetNewValueAsync(result); await SetNewValueAsync(result);
} }

View File

@@ -2,23 +2,27 @@
public sealed class CombineLatestProperty<T1, T2, TResult> : DeclarativePropertyBase<TResult> public sealed class CombineLatestProperty<T1, T2, TResult> : DeclarativePropertyBase<TResult>
{ {
private readonly Func<T1?, T2?, Task<TResult>> _func; private readonly Func<T1, T2, Task<TResult>> _func;
private T1? _value1; private T1 _value1;
private T2? _value2; private T2 _value2;
public CombineLatestProperty( public CombineLatestProperty(
IDeclarativeProperty<T1> prop1, IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2, IDeclarativeProperty<T2> prop2,
Func<T1?, T2?, Task<TResult>> func, Func<T1, T2, Task<TResult>> func,
Action<TResult?>? setValueHook = null) : base(setValueHook) Action<TResult>? setValueHook = null) : base(default!, setValueHook)
{ {
ArgumentNullException.ThrowIfNull(prop1); ArgumentNullException.ThrowIfNull(prop1);
ArgumentNullException.ThrowIfNull(prop2); ArgumentNullException.ThrowIfNull(prop2);
_func = func; _func = func;
_value1 = prop1.Value is null ? default : prop1.Value; _value1 = prop1.Value;
_value2 = prop2.Value is null ? default : prop2.Value; _value2 = prop2.Value;
var initialValueTask = Task.Run(async () => await _func(_value1, _value2));
initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result);
prop1.Subscribe(async (value1, token) => prop1.Subscribe(async (value1, token) =>
{ {
@@ -51,7 +55,7 @@ public sealed class CombineLatestProperty<T1, T2, T3, TResult> : DeclarativeProp
IDeclarativeProperty<T2> prop2, IDeclarativeProperty<T2> prop2,
IDeclarativeProperty<T3> prop3, IDeclarativeProperty<T3> prop3,
Func<T1?, T2?, T3?,Task<TResult>> func, Func<T1?, T2?, T3?,Task<TResult>> func,
Action<TResult?>? setValueHook = null) : base(setValueHook) Action<TResult>? setValueHook = null) : base(default!, setValueHook)
{ {
ArgumentNullException.ThrowIfNull(prop1); ArgumentNullException.ThrowIfNull(prop1);
ArgumentNullException.ThrowIfNull(prop2); ArgumentNullException.ThrowIfNull(prop2);
@@ -59,9 +63,13 @@ public sealed class CombineLatestProperty<T1, T2, T3, TResult> : DeclarativeProp
_func = func; _func = func;
_value1 = prop1.Value is null ? default : prop1.Value; _value1 = prop1.Value;
_value2 = prop2.Value is null ? default : prop2.Value; _value2 = prop2.Value;
_value3 = prop3.Value is null ? default : prop3.Value; _value3 = prop3.Value;
var initialValueTask = Task.Run(async () => await _func(_value1, _value2, _value3));
initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result);
prop1.Subscribe(async (value1, token) => prop1.Subscribe(async (value1, token) =>
{ {

View File

@@ -5,7 +5,7 @@ public sealed class CombineProperty<TFrom, TTo> : DeclarativePropertyBase<TTo>
private readonly Func<IReadOnlyList<TFrom?>, Task<TTo>> _combiner; private readonly Func<IReadOnlyList<TFrom?>, Task<TTo>> _combiner;
private readonly List<IDeclarativeProperty<TFrom>> _sourceProperties = new(); private readonly List<IDeclarativeProperty<TFrom>> _sourceProperties = new();
public CombineProperty(Func<IReadOnlyList<TFrom?>, Task<TTo>> combiner) public CombineProperty(Func<IReadOnlyList<TFrom?>, Task<TTo>> combiner) : base(default!)
{ {
_combiner = combiner; _combiner = combiner;
} }

View File

@@ -3,9 +3,9 @@
public sealed class DebounceProperty<T> : DeclarativePropertyBase<T> public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
{ {
private readonly object _lock = new(); private readonly object _lock = new();
private readonly Func<T?, TimeSpan> _interval; private readonly Func<T, TimeSpan> _interval;
private DateTime _startTime = DateTime.MinValue; private DateTime _startTime = DateTime.MinValue;
private T? _nextValue; private T _nextValue = default!;
private CancellationToken _nextCancellationToken; private CancellationToken _nextCancellationToken;
private bool _isThrottleTaskRunning; private bool _isThrottleTaskRunning;
public bool ResetTimer { get; init; } public bool ResetTimer { get; init; }
@@ -13,14 +13,14 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
public DebounceProperty( public DebounceProperty(
IDeclarativeProperty<T> from, IDeclarativeProperty<T> from,
Func<T?, TimeSpan> interval, Func<T, TimeSpan> interval,
Action<T?>? setValueHook = null) : base(from.Value, setValueHook) Action<T>? setValueHook = null) : base(from.Value, setValueHook)
{ {
_interval = interval; _interval = interval;
AddDisposable(from.Subscribe(SetValue)); AddDisposable(from.Subscribe(SetValue));
} }
private Task SetValue(T? next, CancellationToken cancellationToken = default) private Task SetValue(T next, CancellationToken cancellationToken = default)
{ {
lock (_lock) lock (_lock)
{ {
@@ -38,7 +38,7 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
_startTime = DateTime.Now; _startTime = DateTime.Now;
_isThrottleTaskRunning = true; _isThrottleTaskRunning = true;
Task.Run(async () => await StartDebounceTask()); Task.Run(StartDebounceTask);
} }
return Task.CompletedTask; return Task.CompletedTask;
@@ -51,7 +51,7 @@ public sealed class DebounceProperty<T> : DeclarativePropertyBase<T>
await Task.Delay(WaitInterval); await Task.Delay(WaitInterval);
} }
T? next; T next;
CancellationToken cancellationToken; CancellationToken cancellationToken;
lock (_lock) lock (_lock)
{ {

View File

@@ -6,15 +6,15 @@ public static class DeclarativePropertyHelpers
IDeclarativeProperty<T1> prop1, IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2, IDeclarativeProperty<T2> prop2,
Func<T1, T2, Task<TResult>> func, Func<T1, T2, Task<TResult>> func,
Action<TResult?>? setValueHook = null) Action<TResult>? setValueHook = null)
=> new(prop1, prop2, func!, setValueHook); => new(prop1, prop2, func, setValueHook);
public static CombineLatestProperty<T1, T2, T3, TResult> CombineLatest<T1, T2, T3, TResult>( public static CombineLatestProperty<T1, T2, T3, TResult> CombineLatest<T1, T2, T3, TResult>(
IDeclarativeProperty<T1> prop1, IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2, IDeclarativeProperty<T2> prop2,
IDeclarativeProperty<T3> prop3, IDeclarativeProperty<T3> prop3,
Func<T1, T2, T3, Task<TResult>> func, Func<T1, T2, T3, Task<TResult>> func,
Action<TResult?>? setValueHook = null) Action<TResult>? setValueHook = null)
=> new(prop1, prop2, prop3, func!, setValueHook); => new(prop1, prop2, prop3, func!, setValueHook);
public static MergeProperty<T> Merge<T>(params IDeclarativeProperty<T>[] props) public static MergeProperty<T> Merge<T>(params IDeclarativeProperty<T>[] props)
@@ -23,11 +23,7 @@ public static class DeclarativePropertyHelpers
public sealed class DeclarativeProperty<T> : DeclarativePropertyBase<T> public sealed class DeclarativeProperty<T> : DeclarativePropertyBase<T>
{ {
public DeclarativeProperty(Action<T?>? setValueHook = null) : base(setValueHook) public DeclarativeProperty(T initialValue, Action<T>? setValueHook = null) : base(initialValue, setValueHook)
{
}
public DeclarativeProperty(T initialValue, Action<T?>? setValueHook = null) : base(initialValue, setValueHook)
{ {
} }

View File

@@ -5,8 +5,8 @@ namespace DeclarativeProperty;
public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T> public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
{ {
private readonly List<Func<T?, CancellationToken, Task>> _subscribers = new(); private readonly List<Func<T, CancellationToken, Task>> _subscribers = new();
private readonly Action<T?>? _setValueHook; private readonly Action<T>? _setValueHook;
private readonly List<IDisposable> _disposables = new(); private readonly List<IDisposable> _disposables = new();
private readonly List<Func<IDeclarativeProperty<T>, T, IDisposable?>> _subscribeTriggers = new(); private readonly List<Func<IDeclarativeProperty<T>, T, IDisposable?>> _subscribeTriggers = new();
private readonly List<Action<IDeclarativeProperty<T>, T>> _unsubscribeTriggers = new(); private readonly List<Action<IDeclarativeProperty<T>, T>> _unsubscribeTriggers = new();
@@ -14,29 +14,24 @@ public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
private readonly object _triggerLock = new(); private readonly object _triggerLock = new();
private readonly object _subscriberLock = new(); private readonly object _subscriberLock = new();
private T? _value; private T _value;
public T? Value public T Value
{ {
get => _value; get => _value;
set => _setValueHook?.Invoke(value); set => _setValueHook?.Invoke(value);
} }
protected DeclarativePropertyBase(Action<T?>? setValueHook = null) protected DeclarativePropertyBase(T initialValue, Action<T>? setValueHook = null)
{
_setValueHook = setValueHook;
}
protected DeclarativePropertyBase(T? initialValue, Action<T?>? setValueHook = null)
{ {
_setValueHook = setValueHook; _setValueHook = setValueHook;
_value = initialValue; _value = initialValue;
} }
protected async Task NotifySubscribersAsync(T? newValue, CancellationToken cancellationToken = default) protected async Task NotifySubscribersAsync(T newValue, CancellationToken cancellationToken = default)
{ {
List<Func<T?, CancellationToken, Task>> subscribers; List<Func<T, CancellationToken, Task>> subscribers;
lock (_subscriberLock) lock (_subscriberLock)
{ {
subscribers = _subscribers.ToList(); subscribers = _subscribers.ToList();
@@ -54,7 +49,7 @@ public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public IDisposable Subscribe(Func<T?, CancellationToken, Task> onChange) public IDisposable Subscribe(Func<T, CancellationToken, Task> onChange)
{ {
lock (_subscriberLock) lock (_subscriberLock)
{ {
@@ -70,7 +65,7 @@ public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
{ {
lock (_subscriberLock) lock (_subscriberLock)
{ {
_subscribers.Remove(onChange!); _subscribers.Remove(onChange);
} }
} }
@@ -99,14 +94,14 @@ public abstract class DeclarativePropertyBase<T> : IDeclarativeProperty<T>
return Task.CompletedTask; return Task.CompletedTask;
}); });
protected async Task SetNewValueAsync(T? newValue, CancellationToken cancellationToken = default) protected async Task SetNewValueAsync(T newValue, CancellationToken cancellationToken = default)
{ {
SetNewValueSync(newValue, cancellationToken); SetNewValueSync(newValue, cancellationToken);
if (cancellationToken.IsCancellationRequested) return; if (cancellationToken.IsCancellationRequested) return;
await NotifySubscribersAsync(newValue, cancellationToken); await NotifySubscribersAsync(newValue, cancellationToken);
} }
protected void SetNewValueSync(T? newValue, CancellationToken cancellationToken = default) protected void SetNewValueSync(T newValue, CancellationToken cancellationToken = default)
{ {
if (!(Value?.Equals(newValue) ?? false)) if (!(Value?.Equals(newValue) ?? false))
{ {

View File

@@ -21,16 +21,16 @@ public static class DeclarativePropertyExtensions
public static IDeclarativeProperty<T> DistinctUntilChanged<T>(this IDeclarativeProperty<T> from) public static IDeclarativeProperty<T> DistinctUntilChanged<T>(this IDeclarativeProperty<T> from)
=> new DistinctUntilChangedProperty<T>(from); => new DistinctUntilChangedProperty<T>(from);
public static IDeclarativeProperty<TTo> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, Task<TTo?>> mapper) public static IDeclarativeProperty<TTo> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom> from, Func<TFrom, Task<TTo>> mapper)
=> Map(from, async (v, _) => await mapper(v)); => Map(from, async (v, _) => await mapper(v));
public static IDeclarativeProperty<TTo> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, CancellationToken, Task<TTo?>> mapper) public static IDeclarativeProperty<TTo> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom> from, Func<TFrom, CancellationToken, Task<TTo>> mapper)
=> new MapProperty<TFrom?, TTo>(mapper, from); => new MapProperty<TFrom, TTo>(mapper, from);
public static IDeclarativeProperty<TTo?> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, TTo?> mapper) public static IDeclarativeProperty<TTo> Map<TFrom, TTo>(this IDeclarativeProperty<TFrom> from, Func<TFrom, TTo> mapper)
=> new MapProperty<TFrom?, TTo?>((next, _) => Task.FromResult(mapper(next)), from); => new MapProperty<TFrom, TTo>((next, _) => Task.FromResult(mapper(next)), from);
public static async Task<IDeclarativeProperty<TTo>> MapAsync<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, CancellationToken, Task<TTo?>> mapper) public static async Task<IDeclarativeProperty<TTo>> MapAsync<TFrom, TTo>(this IDeclarativeProperty<TFrom> from, Func<TFrom, CancellationToken, Task<TTo>> mapper)
=> await MapProperty<TFrom, TTo>.CreateAsync(mapper, from); => await MapProperty<TFrom, TTo>.CreateAsync(mapper, from);
public static async Task<IDeclarativeProperty<TTo?>> MapAsync<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, TTo?> mapper) public static async Task<IDeclarativeProperty<TTo?>> MapAsync<TFrom, TTo>(this IDeclarativeProperty<TFrom?> from, Func<TFrom?, TTo?> mapper)
@@ -43,39 +43,39 @@ public static class DeclarativePropertyExtensions
return Task.CompletedTask; return Task.CompletedTask;
}); });
public static IDisposable Subscribe<T>(this IDeclarativeProperty<T> property, Action<T?> onChange) public static IDisposable Subscribe<T>(this IDeclarativeProperty<T> property, Action<T> onChange)
=> property.Subscribe((value, _) => => property.Subscribe((value, _) =>
{ {
onChange(value); onChange(value);
return Task.CompletedTask; return Task.CompletedTask;
}); });
public static IDeclarativeProperty<T> Extract<T>( public static IDeclarativeProperty<TResult> Extract<T, TResult>(
this IDeclarativeProperty<ReadOnlyObservableCollection<T>> from, this IDeclarativeProperty<ReadOnlyObservableCollection<T>?> from,
Func<IList<T>?, T?> extractor Func<IList<T>?, TResult> extractor
) )
=> new ExtractorProperty<T>(from, extractor); => new ExtractorProperty<T, TResult>(from, extractor);
public static IDeclarativeProperty<T> Extract<T>( public static IDeclarativeProperty<TResult> Extract<T, TResult>(
this IDeclarativeProperty<ObservableCollection<T>> from, this IDeclarativeProperty<ObservableCollection<T>?> from,
Func<IList<T>?, T?> extractor Func<IList<T>?, TResult> extractor
) )
=> new ExtractorProperty<T>(from, extractor); => new ExtractorProperty<T, TResult>(from, extractor);
public static IDeclarativeProperty<T> Extract<T>( public static IDeclarativeProperty<TResult> Extract<T, TResult>(
this IDeclarativeProperty<CollectionComputing<T>> from, this IDeclarativeProperty<CollectionComputing<T>?> from,
Func<IList<T>?, T?> extractor Func<IList<T>?, TResult> extractor
) )
=> new ExtractorProperty<T>(from, extractor); => new ExtractorProperty<T, TResult>(from, extractor);
public static IDeclarativeProperty<TCollection> Watch<TCollection, TItem>( public static IDeclarativeProperty<TCollection> Watch<TCollection, TItem>(
this IDeclarativeProperty<TCollection?> collection) this IDeclarativeProperty<TCollection> collection)
where TCollection : IList<TItem>, INotifyCollectionChanged where TCollection : IList<TItem>?, INotifyCollectionChanged?
=> new CollectionRepeaterProperty<TCollection, TItem>(collection); => new CollectionRepeaterProperty<TCollection, TItem>(collection);
public static IDeclarativeProperty<TCollection> Watch<TCollection, TItem>( public static IDeclarativeProperty<TCollection> Watch<TCollection, TItem>(
this TCollection collection) this TCollection collection)
where TCollection : IList<TItem>, INotifyCollectionChanged where TCollection : IList<TItem>?, INotifyCollectionChanged?
=> new CollectionRepeaterProperty<TCollection, TItem>(collection); => new CollectionRepeaterProperty<TCollection, TItem>(collection);
public static IDeclarativeProperty<ObservableCollection<TItem>> Watch<TItem>( public static IDeclarativeProperty<ObservableCollection<TItem>> Watch<TItem>(
@@ -90,16 +90,16 @@ public static class DeclarativePropertyExtensions
public static IDeclarativeProperty<TResult> CombineLatest<T1, T2, TResult>( public static IDeclarativeProperty<TResult> CombineLatest<T1, T2, TResult>(
this IDeclarativeProperty<T1> prop1, this IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2, IDeclarativeProperty<T2> prop2,
Func<T1, T2, Task<TResult?>> func, Func<T1, T2, Task<TResult>> func,
Action<TResult?>? setValueHook = null) Action<TResult?>? setValueHook = null)
=> new CombineLatestProperty<T1,T2,TResult>(prop1, prop2, func!, setValueHook); => new CombineLatestProperty<T1,T2,TResult>(prop1, prop2, func, setValueHook);
public static IDeclarativeProperty<T?> Switch<T>(this IDeclarativeProperty<IDeclarativeProperty<T?>?> from) public static IDeclarativeProperty<T> Switch<T>(this IDeclarativeProperty<IDeclarativeProperty<T>?> from)
=> new SwitchProperty<T?>(from); => new SwitchProperty<T>(from);
public static IDeclarativeProperty<TResult> CombineAll<T, TResult>( public static IDeclarativeProperty<TResult> CombineAll<T, TResult>(
this IEnumerable<IDeclarativeProperty<T>> sources, this IEnumerable<IDeclarativeProperty<T>> sources,
Func<IEnumerable<T>, Task<TResult?>> combiner, Func<IEnumerable<T>, Task<TResult>> combiner,
Action<TResult?>? setValueHook = null) Action<TResult?>? setValueHook = null)
=> new CombineAllProperty<T,TResult>(sources, combiner, setValueHook); => new CombineAllProperty<T,TResult>(sources, combiner, setValueHook);
} }

View File

@@ -11,7 +11,7 @@ public sealed class DistinctUntilChangedProperty<T> : DeclarativePropertyBase<T>
AddDisposable(from.Subscribe(Handle)); AddDisposable(from.Subscribe(Handle));
} }
async Task Handle(T? next, CancellationToken cancellationToken = default) async Task Handle(T next, CancellationToken cancellationToken = default)
{ {
if (_comparer is { } comparer) if (_comparer is { } comparer)
{ {
@@ -20,8 +20,10 @@ public sealed class DistinctUntilChangedProperty<T> : DeclarativePropertyBase<T>
return; return;
} }
} }
else if ((next is null && Value is null && !_firstFire) else if (
|| (Value?.Equals(next) ?? false)) (next is null && Value is null && !_firstFire)
|| (Value?.Equals(next) ?? false)
)
{ {
return; return;
} }

View File

@@ -4,7 +4,7 @@ using ObservableComputations;
namespace DeclarativeProperty; namespace DeclarativeProperty;
public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T> public sealed class ExtractorProperty<T, TResult> : DeclarativePropertyBase<TResult>
{ {
private interface ICollectionWrapper : IDisposable private interface ICollectionWrapper : IDisposable
{ {
@@ -108,12 +108,12 @@ public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
) => new CollectionComputingWrapper(collection, handler); ) => new CollectionComputingWrapper(collection, handler);
} }
private readonly Func<IList<T>?, T?> _extractor; private readonly Func<IList<T>?, TResult> _extractor;
private ICollectionWrapper? _collectionWrapper; private ICollectionWrapper? _collectionWrapper;
public ExtractorProperty( public ExtractorProperty(
IDeclarativeProperty<ObservableCollection<T>> from, IDeclarativeProperty<ObservableCollection<T>?> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value)) Func<IList<T>?, TResult> extractor) : base(extractor(from.Value))
{ {
_extractor = extractor; _extractor = extractor;
_collectionWrapper = from.Value is null _collectionWrapper = from.Value is null
@@ -124,8 +124,8 @@ public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
} }
public ExtractorProperty( public ExtractorProperty(
IDeclarativeProperty<ReadOnlyObservableCollection<T>> from, IDeclarativeProperty<ReadOnlyObservableCollection<T>?> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value)) Func<IList<T>?, TResult> extractor) : base(extractor(from.Value))
{ {
_extractor = extractor; _extractor = extractor;
_collectionWrapper = from.Value is null _collectionWrapper = from.Value is null
@@ -136,8 +136,8 @@ public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
} }
public ExtractorProperty( public ExtractorProperty(
IDeclarativeProperty<CollectionComputing<T>> from, IDeclarativeProperty<CollectionComputing<T>?> from,
Func<IList<T>?, T?> extractor) : base(extractor(from.Value)) Func<IList<T>?, TResult> extractor) : base(extractor(from.Value))
{ {
_extractor = extractor; _extractor = extractor;
_collectionWrapper = from.Value is null _collectionWrapper = from.Value is null
@@ -165,9 +165,7 @@ public sealed class ExtractorProperty<T> : DeclarativePropertyBase<T>
private async Task Fire(IList<T>? items, CancellationToken cancellationToken = default) private async Task Fire(IList<T>? items, CancellationToken cancellationToken = default)
{ {
var newValue = items is null var newValue = _extractor(items);
? default
: _extractor(items);
await SetNewValueAsync(newValue, cancellationToken); await SetNewValueAsync(newValue, cancellationToken);
} }

View File

@@ -7,13 +7,22 @@ public sealed class FilterProperty<T> : DeclarativePropertyBase<T>
public FilterProperty( public FilterProperty(
Func<T?, Task<bool>> filter, Func<T?, Task<bool>> filter,
IDeclarativeProperty<T> from, IDeclarativeProperty<T> from,
Action<T?>? setValueHook = null) : base(setValueHook) Action<T>? setValueHook = null) : base(default!, setValueHook)
{ {
_filter = filter; _filter = filter;
var initialValueTask = Task.Run(async () => await _filter(from.Value));
initialValueTask.Wait();
if (initialValueTask.Result)
{
SetNewValueSync(from.Value);
}
// Unfortunately we can't set a default value if the parent current value can not passed by the filter.
AddDisposable(from.Subscribe(SetValue)); AddDisposable(from.Subscribe(SetValue));
} }
private async Task SetValue(T? next, CancellationToken cancellationToken = default) private async Task SetValue(T next, CancellationToken cancellationToken = default)
{ {
if (await _filter(next)) if (await _filter(next))
{ {
@@ -24,7 +33,7 @@ public sealed class FilterProperty<T> : DeclarativePropertyBase<T>
public static async Task<FilterProperty<T>> CreateAsync( public static async Task<FilterProperty<T>> CreateAsync(
Func<T?, Task<bool>> filter, Func<T?, Task<bool>> filter,
IDeclarativeProperty<T> from, IDeclarativeProperty<T> from,
Action<T?>? setValueHook = null) Action<T>? setValueHook = null)
{ {
var prop = new FilterProperty<T>(filter, from, setValueHook); var prop = new FilterProperty<T>(filter, from, setValueHook);
await prop.SetValue(from.Value); await prop.SetValue(from.Value);

View File

@@ -4,9 +4,9 @@ namespace DeclarativeProperty;
public interface IDeclarativeProperty<T> : INotifyPropertyChanged, IDisposable, IObservable<T> public interface IDeclarativeProperty<T> : INotifyPropertyChanged, IDisposable, IObservable<T>
{ {
T? Value { get; set; } T Value { get; set; }
IDisposable Subscribe(Func<T?, CancellationToken, Task> onChange); IDisposable Subscribe(Func<T, CancellationToken, Task> onChange);
void Unsubscribe(Func<T?, CancellationToken, Task> onChange); void Unsubscribe(Func<T, CancellationToken, Task> onChange);
IDeclarativeProperty<T> RegisterTrigger( IDeclarativeProperty<T> RegisterTrigger(
Func<IDeclarativeProperty<T>, T, IDisposable?> triggerSubscribe, Func<IDeclarativeProperty<T>, T, IDisposable?> triggerSubscribe,

View File

@@ -6,14 +6,19 @@ public sealed class MapProperty<TFrom, TTo> : DeclarativePropertyBase<TTo>
public MapProperty( public MapProperty(
Func<TFrom, CancellationToken, Task<TTo>> mapper, Func<TFrom, CancellationToken, Task<TTo>> mapper,
IDeclarativeProperty<TFrom?> from, IDeclarativeProperty<TFrom> from,
Action<TTo?>? setValueHook = null) : base(setValueHook) Action<TTo>? setValueHook = null) : base(default!, setValueHook)
{ {
_mapper = mapper; _mapper = mapper;
var initialValueTask = Task.Run(async () => await _mapper(from.Value, CancellationToken.None));
initialValueTask.Wait();
SetNewValueSync(initialValueTask.Result);
AddDisposable(from.Subscribe(SetValue)); AddDisposable(from.Subscribe(SetValue));
} }
private async Task SetValue(TFrom? next, CancellationToken cancellationToken = default) private async Task SetValue(TFrom next, CancellationToken cancellationToken = default)
{ {
var newValue = await _mapper(next!, cancellationToken); var newValue = await _mapper(next!, cancellationToken);
await SetNewValueAsync(newValue, cancellationToken); await SetNewValueAsync(newValue, cancellationToken);
@@ -21,8 +26,8 @@ public sealed class MapProperty<TFrom, TTo> : DeclarativePropertyBase<TTo>
public static async Task<MapProperty<TFrom, TTo>> CreateAsync( public static async Task<MapProperty<TFrom, TTo>> CreateAsync(
Func<TFrom, CancellationToken, Task<TTo>> mapper, Func<TFrom, CancellationToken, Task<TTo>> mapper,
IDeclarativeProperty<TFrom?> from, IDeclarativeProperty<TFrom> from,
Action<TTo?>? setValueHook = null) Action<TTo>? setValueHook = null)
{ {
var prop = new MapProperty<TFrom, TTo>(mapper, from, setValueHook); var prop = new MapProperty<TFrom, TTo>(mapper, from, setValueHook);
await prop.SetValue(from.Value); await prop.SetValue(from.Value);

View File

@@ -2,7 +2,7 @@
public class MergeProperty<T> : DeclarativePropertyBase<T> public class MergeProperty<T> : DeclarativePropertyBase<T>
{ {
public MergeProperty(params IDeclarativeProperty<T>[] props) public MergeProperty(params IDeclarativeProperty<T>[] props) : base(default!)
{ {
ArgumentNullException.ThrowIfNull(props); ArgumentNullException.ThrowIfNull(props);
@@ -12,6 +12,6 @@ public class MergeProperty<T> : DeclarativePropertyBase<T>
} }
} }
private async Task UpdateAsync(T? newValue, CancellationToken token) private async Task UpdateAsync(T newValue, CancellationToken token)
=> await SetNewValueAsync(newValue, token); => await SetNewValueAsync(newValue, token);
} }

View File

@@ -4,7 +4,7 @@ public sealed class SwitchProperty<TItem> : DeclarativePropertyBase<TItem>
{ {
private IDisposable? _innerSubscription; private IDisposable? _innerSubscription;
public SwitchProperty(IDeclarativeProperty<IDeclarativeProperty<TItem>?> from) : base(from.Value is null ? default : from.Value.Value) public SwitchProperty(IDeclarativeProperty<IDeclarativeProperty<TItem>?> from) : base(from.Value is null ? default! : from.Value.Value)
{ {
AddDisposable(from.Subscribe(HandleStreamChange)); AddDisposable(from.Subscribe(HandleStreamChange));
_innerSubscription = from.Value?.Subscribe(HandleInnerValueChange); _innerSubscription = from.Value?.Subscribe(HandleInnerValueChange);
@@ -15,10 +15,9 @@ public sealed class SwitchProperty<TItem> : DeclarativePropertyBase<TItem>
_innerSubscription?.Dispose(); _innerSubscription?.Dispose();
_innerSubscription = next?.Subscribe(HandleInnerValueChange); _innerSubscription = next?.Subscribe(HandleInnerValueChange);
await SetNewValueAsync(next is null ? default! : next.Value, token);
await SetNewValueAsync(next is null ? default : next.Value, token);
} }
private async Task HandleInnerValueChange(TItem? next, CancellationToken token) private async Task HandleInnerValueChange(TItem next, CancellationToken token)
=> await SetNewValueAsync(next, token); => await SetNewValueAsync(next, token);
} }

View File

@@ -9,13 +9,13 @@ public class ThrottleProperty<T> : DeclarativePropertyBase<T>
public ThrottleProperty( public ThrottleProperty(
IDeclarativeProperty<T> from, IDeclarativeProperty<T> from,
Func<TimeSpan> interval, Func<TimeSpan> interval,
Action<T?>? setValueHook = null) : base(from.Value, setValueHook) Action<T>? setValueHook = null) : base(from.Value, setValueHook)
{ {
_interval = interval; _interval = interval;
AddDisposable(from.Subscribe(SetValue)); AddDisposable(from.Subscribe(SetValue));
} }
private async Task SetValue(T? next, CancellationToken cancellationToken = default) private async Task SetValue(T next, CancellationToken cancellationToken = default)
{ {
lock (_lock) lock (_lock)
{ {

View File

@@ -3,9 +3,9 @@
internal sealed class Unsubscriber<T> : IDisposable internal sealed class Unsubscriber<T> : IDisposable
{ {
private readonly IDeclarativeProperty<T> _owner; private readonly IDeclarativeProperty<T> _owner;
private readonly Func<T?, CancellationToken, Task> _onChange; private readonly Func<T, CancellationToken, Task> _onChange;
public Unsubscriber(IDeclarativeProperty<T> owner, Func<T?, CancellationToken, Task> onChange) public Unsubscriber(IDeclarativeProperty<T> owner, Func<T, CancellationToken, Task> onChange)
{ {
_owner = owner; _owner = owner;
_onChange = onChange; _onChange = onChange;