using System; using System.Linq.Expressions; using System.Reactive.Linq; using DynamicData; using ReactiveUI; namespace NodeNetwork.Utilities { public static class ReactiveExtensions { /// /// Pass through values if and only if the last value produced by 'throttleCondition' is false. /// When 'throttleCondition' is false, no values are passed through. /// When 'throttleCondition' changes from true to false, if one or more values was blocked during the period /// in which the throttle was active, the latest value will be passed through. /// /// The datatype in the observable /// The source observable /// An observable of booleans that determines the current throttle state /// The new observable public static IObservable ThrottleWhen(this IObservable self, IObservable throttleCondition) { var isPaused = throttleCondition.Prepend(false).DistinctUntilChanged(); return Observable.Defer(() => { object lockObj = new object(); bool gateIsOpen = false; return Observable.CombineLatest( self.Synchronize(lockObj).Do(_ => gateIsOpen = true), isPaused.Synchronize(lockObj).Do(paused => gateIsOpen = !paused && gateIsOpen), (number, paused) => (number, paused) ) .Where(tuple => !tuple.paused && gateIsOpen) .Select(tuple => tuple.number); }); } /// /// Create a one way list binding from the viewmodel to the view. /// The view list property will be automatically updated to reflect /// the viewmodel source list property. /// /// The type of the view /// The type of the viewmodel /// The type of the data stored in the list /// The type of the target property in the view /// The view used for the binding /// A dummy viewmodel parameter, used to infer the viewmodel type /// The source property in the viewmodel that contains the list /// The target property in the view to bind the list to. /// An object that when disposed, disconnects the binding. public static IDisposable BindList( this TView self, TViewModel vmDummy, Expression>> vmProperty, Expression> viewProperty ) where TView: class, IViewFor where TViewModel: class { IDisposable lastBinding = null; return // Get latest viewmodel self.WhenAnyValue(v => v.ViewModel) .Where(vm => vm != null) // Get latest non-null list from viewmodel property .Select(vm => vm.WhenAnyValue(vmProperty)) .Switch() .Where(sourceList => sourceList != null) // Clean up last list binding .Do(p => lastBinding?.Dispose()) // Create new list binding .Select(sourceList => { lastBinding = sourceList.Connect().Bind(out var list).Subscribe(); return list; }) // When the observable is disposed, dispose the list binding too .Finally(() => { lastBinding?.Dispose(); }) // Bind the new bindable list to the view property .BindTo(self, viewProperty); } /// /// Takes an observable of T values and returns an observable of tuples of T values containing the latest value and the previous value. /// The first item in the source observable produces a tuple with the previous value set to default(T). /// /// The type of object in the observable /// The source observable /// The resulting observable public static IObservable<(T OldValue, T NewValue)> PairWithPreviousValue(this IObservable obs) { return obs.Scan((oldValue: default(T), newValue: default(T)), (pair, newVal) => (pair.newValue, newVal)); } } }