using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using DynamicData;
using DynamicData.Kernel;
using ReactiveUI;
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 function that discards events that are null
///
///
///
/// Source events that are not null
public static IObservable NotNull(this IObservable source)
where T : class
{
return source.Where(u => u != null);
}
///
/// Convenience wrapper to observe following calls on the GUI thread.
///
public static IObservable ObserveOnGuiThread(this IObservable source)
{
return source.ObserveOn(RxApp.MainThreadScheduler);
}
///
/// Converts any observable to type Unit. Useful for when you care that a signal occurred,
/// but don't care about what its value is downstream.
///
/// An observable that returns Unit anytime the source signal fires an event.
public static IObservable Unit(this IObservable source)
{
return source.Select(_ => System.Reactive.Unit.Default);
}
///
/// Convenience operator to subscribe to the source observable, only when a second "switch" observable is on.
/// When the switch is on, the source will be subscribed to, and its updates passed through.
/// When the switch is off, the subscription to the source observable will be stopped, and no signal will be published.
///
/// Source observable to subscribe to if on
/// On/Off signal of whether to subscribe to source observable
/// Observable that publishes data from source, if the switch is on.
public static IObservable FilterSwitch(this IObservable source, IObservable filterSwitch)
{
return filterSwitch
.DistinctUntilChanged()
.Select(on =>
{
if (on)
{
return source;
}
else
{
return Observable.Empty();
}
})
.Switch();
}
public static IObservable StartingExecution(this IReactiveCommand cmd)
{
return cmd.IsExecuting
.DistinctUntilChanged()
.Where(x => x)
.Unit();
}
/// Inspiration:
/// http://reactivex.io/documentation/operators/debounce.html
/// https://stackoverflow.com/questions/20034476/how-can-i-use-reactive-extensions-to-throttle-events-using-a-max-window-size
public static IObservable Debounce(this IObservable source, TimeSpan interval, IScheduler scheduler = null)
{
scheduler = scheduler ?? Scheduler.Default;
return Observable.Create(o =>
{
var hasValue = false;
bool throttling = false;
T value = default;
var dueTimeDisposable = new SerialDisposable();
void internalCallback()
{
if (hasValue)
{
// We have another value that came in to fire.
// Reregister for callback
dueTimeDisposable.Disposable = scheduler.Schedule(interval, internalCallback);
o.OnNext(value);
value = default;
hasValue = false;
}
else
{
// Nothing to do, throttle is complete.
throttling = false;
}
}
return source.Subscribe(
onNext: (x) =>
{
if (!throttling)
{
// Fire initial value
o.OnNext(x);
// Mark that we're throttling
throttling = true;
// Register for callback when throttle is complete
dueTimeDisposable.Disposable = scheduler.Schedule(interval, internalCallback);
}
else
{
// In the middle of throttle
// Save value and return
hasValue = true;
value = x;
}
},
onError: o.OnError,
onCompleted: o.OnCompleted);
});
}
/// 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)
{
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)
{
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);
}
internal static Optional> Reduce(Optional> previous, Change next)
{
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
}
}