using System; using System.Linq; using System.Linq.Expressions; using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading.Tasks; using System.Windows.Threading; using DynamicData; using DynamicData.Kernel; using ReactiveUI; using Wabbajack; using Wabbajack.Extensions; namespace Wabbajack { public static class ReactiveUIExt { /// /// Convenience function to not have to specify the selector function in the default ReactiveUI WhenAny() call. /// Subscribes to changes in a property on a given object. /// /// Type of object to watch /// The type of property watched /// Object to watch /// Expression path to the property to subscribe to /// public static IObservable WhenAny( this TSender This, Expression> property1) where TSender : class { return This.WhenAny(property1, selector: x => x.GetValue()); } /// /// Convenience wrapper to observe following calls on the GUI thread. /// public static IObservable ObserveOnGuiThread(this IObservable source) { return source.ObserveOn(RxApp.MainThreadScheduler); } /// /// Like IObservable.Select but supports async map functions /// /// /// /// /// public static IObservable SelectAsync(this IObservable source, Func> f) { return source.Select(itm => Observable.FromAsync(async () => await f(itm))).Merge(10); } public static IObservable StartingExecution(this IReactiveCommand cmd) { return cmd.IsExecuting .DistinctUntilChanged() .Where(x => x) .Unit(); } public static IObservable EndingExecution(this IReactiveCommand cmd) { return cmd.IsExecuting .DistinctUntilChanged() .Pairwise() .Where(x => x.Previous && !x.Current) .Unit(); } /// These snippets were provided by RolandPheasant (author of DynamicData) /// They'll be going into the official library at some point, but are here for now. #region Dynamic Data EnsureUniqueChanges /// /// Removes outdated key events from a changeset, only leaving the last relevent change for each key. /// public static IObservable> EnsureUniqueChanges(this IObservable> source) where TKey : notnull { return source.Select(EnsureUniqueChanges); } /// /// Removes outdated key events from a changeset, only leaving the last relevent change for each key. /// public static IChangeSet EnsureUniqueChanges(this IChangeSet input) where TKey : notnull { var changes = input .GroupBy(kvp => kvp.Key) .Select(g => g.Aggregate(Optional>.None, Reduce)) .Where(x => x.HasValue) .Select(x => x.Value); return new ChangeSet(changes); } public static ObservableAsPropertyHelper ToGuiProperty( this IObservable source, ViewModel vm, string property, TRet? initialValue = default, bool deferSubscription = false) { return source .ToProperty(vm, property, initialValue, deferSubscription, RxApp.MainThreadScheduler) .DisposeWith(vm.CompositeDisposable)!; } /* public static void ToGuiProperty( this IObservable source, ViewModel vm, string property, out ObservableAsPropertyHelper result, TRet initialValue = default, bool deferSubscription = false) { source.ToProperty(vm, property, out result!, initialValue, deferSubscription, RxApp.MainThreadScheduler) .DisposeWith(vm.CompositeDisposable); }*/ internal static Optional> Reduce(Optional> previous, Change next) where TKey : notnull { if (!previous.HasValue) { return next; } var previousValue = previous.Value; switch (previousValue.Reason) { case ChangeReason.Add when next.Reason == ChangeReason.Remove: return Optional>.None; case ChangeReason.Remove when next.Reason == ChangeReason.Add: return new Change(ChangeReason.Update, next.Key, next.Current, previousValue.Current, next.CurrentIndex, previousValue.CurrentIndex); case ChangeReason.Add when next.Reason == ChangeReason.Update: return new Change(ChangeReason.Add, next.Key, next.Current, next.CurrentIndex); case ChangeReason.Update when next.Reason == ChangeReason.Update: return new Change(ChangeReason.Update, previousValue.Key, next.Current, previousValue.Previous, next.CurrentIndex, previousValue.PreviousIndex); default: return next; } } #endregion } }