wabbajack/Wabbajack/View Models/SlideShow.cs

130 lines
5.7 KiB
C#
Raw Normal View History

2019-11-21 15:45:00 +00:00
using DynamicData;
using ReactiveUI;
2019-11-02 23:23:11 +00:00
using ReactiveUI.Fody.Helpers;
using System;
2019-10-14 01:15:41 +00:00
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Windows.Media.Imaging;
using Wabbajack.Lib;
using Wabbajack.Lib.Downloaders;
2019-10-13 20:24:33 +00:00
namespace Wabbajack
{
public class SlideShow : ViewModel
{
2019-11-03 06:01:19 +00:00
private readonly Random _random = new Random();
public InstallerVM Installer { get; }
2019-11-02 23:23:11 +00:00
[Reactive]
public bool ShowNSFW { get; set; }
2019-11-02 23:23:11 +00:00
[Reactive]
public bool Enable { get; set; } = true;
2019-11-21 15:45:00 +00:00
private readonly ObservableAsPropertyHelper<BitmapImage> _image;
public BitmapImage Image => _image.Value;
2019-11-21 15:45:00 +00:00
private readonly ObservableAsPropertyHelper<ModVM> _targetMod;
public ModVM TargetMod => _targetMod.Value;
2019-10-14 01:15:41 +00:00
public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { });
2019-10-14 01:15:41 +00:00
public IReactiveCommand VisitNexusSiteCommand { get; }
public SlideShow(InstallerVM appState)
{
2019-11-21 15:04:33 +00:00
Installer = appState;
2019-11-03 06:01:19 +00:00
// Wire target slideshow index
var intervalSeconds = 10;
// Compile all the sources that trigger a slideshow update, any of which trigger a counter update
var selectedIndex = Observable.Merge(
// If user requests one manually
2019-11-21 15:04:33 +00:00
SlideShowNextItemCommand.StartingExecution(),
2019-11-03 06:01:19 +00:00
// If the natural timer fires
Observable.Merge(
// Start with an initial timer
Observable.Return(Observable.Interval(TimeSpan.FromSeconds(intervalSeconds))),
// but reset timer if user requests one
2019-11-21 15:04:33 +00:00
SlideShowNextItemCommand.StartingExecution()
2019-11-03 06:01:19 +00:00
.Select(_ => Observable.Interval(TimeSpan.FromSeconds(intervalSeconds))))
// When a new timer comes in, swap to it
.Switch()
.Unit())
// When filter switch enabled, fire an initial signal
.StartWith(Unit.Default)
2019-11-03 06:01:19 +00:00
// Only subscribe to slideshow triggers if enabled and installing
.FilterSwitch(
Observable.CombineLatest(
this.WhenAny(x => x.Enable),
this.WhenAny(x => x.Installer.Installing),
resultSelector: (enabled, installing) => enabled && installing))
// Block spam
.Debounce(TimeSpan.FromMilliseconds(250))
.Scan(
seed: 0,
accumulator: (i, _) => i + 1)
.Publish()
.RefCount();
// Dynamic list changeset of mod VMs to display
var modVMs = this.WhenAny(x => x.Installer.ModList)
// Whenever modlist changes, grab the list of its slides
.Select(modList =>
{
2019-11-03 06:01:19 +00:00
if (modList == null)
2019-10-12 19:15:19 +00:00
{
2019-11-03 06:01:19 +00:00
return Observable.Empty<ModVM>()
.ToObservableChangeSet(x => x.ModID);
2019-10-12 19:15:19 +00:00
}
2019-11-03 06:01:19 +00:00
return modList.SourceModList.Archives
.Select(m => m.State)
.OfType<NexusDownloader.State>()
.Select(nexus => new ModVM(nexus))
// Shuffle it
2019-11-21 15:04:33 +00:00
.Shuffle(_random)
2019-11-03 06:01:19 +00:00
.AsObservableChangeSet(x => x.ModID);
})
// Switch to the new list after every modlist change
.Switch()
// Filter out any NSFW slides if we don't want them
.AutoRefreshOnObservable(slide => this.WhenAny(x => x.ShowNSFW))
2019-11-21 15:04:33 +00:00
.Filter(slide => !slide.IsNSFW || ShowNSFW)
2019-11-03 06:01:19 +00:00
.RefCount();
// Find target mod to display by combining dynamic list with currently desired index
2019-11-21 15:45:00 +00:00
_targetMod = Observable.CombineLatest(
2019-11-03 06:01:19 +00:00
modVMs.QueryWhenChanged(),
selectedIndex,
resultSelector: (query, selected) => query.Items.ElementAtOrDefault(selected % query.Count))
.StartWith(default(ModVM))
.ObserveOn(RxApp.MainThreadScheduler)
2019-11-21 15:04:33 +00:00
.ToProperty(this, nameof(TargetMod));
2019-11-03 06:01:19 +00:00
// Mark interest and materialize image of target mod
2019-11-21 15:45:00 +00:00
_image = this.WhenAny(x => x.TargetMod)
2019-11-03 06:01:19 +00:00
// We want to Switch here, not SelectMany, as we want to hotswap to newest target without waiting on old ones
.Select(x => x?.ImageObservable ?? Observable.Return(default(BitmapImage)))
.Switch()
2019-11-21 15:04:33 +00:00
.ToProperty(this, nameof(Image));
2019-11-21 15:04:33 +00:00
VisitNexusSiteCommand = ReactiveCommand.Create(
execute: () => Process.Start(TargetMod.ModURL),
2019-11-03 06:01:19 +00:00
canExecute: this.WhenAny(x => x.TargetMod.ModURL)
.Select(x => x?.StartsWith("https://") ?? false)
.ObserveOnGuiThread());
2019-11-03 06:01:19 +00:00
// ToDo
// Can maybe add "preload" systems to prep upcoming images
// This would entail subscribing to modVMs, narrowing it down to Top(X) or Page() somehow.
2019-11-03 06:01:19 +00:00
// The result would not be used anywhere, just simply expressing interest in those mods'
// images will implicitly cache them
//
// Page would be really clever to use, but it's not exactly right as its "window" won't follow the current index,
// so at the boundary of a page, the next image won't be cached. Need like a Page() /w an offset parameter, or something.
}
}
2019-11-21 15:04:33 +00:00
}