public SlideShow(InstallerVM appState) { Installer = appState; // 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 SlideShowNextItemCommand.StartingExecution(), // 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 SlideShowNextItemCommand.StartingExecution() .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) // Only subscribe to slideshow triggers if enabled and installing .FlowSwitch( Observable.CombineLatest( this.WhenAny(x => x.Enable), this.WhenAny(x => x.Installer.Installing), resultSelector: (enabled, installing) => enabled && installing)) // Block spam .Debounce(TimeSpan.FromMilliseconds(250), RxApp.MainThreadScheduler) .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 => { if (modList?.SourceModList?.Archives == null) { return(Observable.Empty <NexusDownloader.State>() .ToObservableChangeSet(x => x.ModID)); } return(modList.SourceModList.Archives .Select(m => m.State) .OfType <NexusDownloader.State>() // Shuffle it .Shuffle(_random) .AsObservableChangeSet(x => x.ModID)); }) // Switch to the new list after every ModList change .Switch() .Transform(nexus => new ModVM(nexus)) .DisposeMany() // Filter out any NSFW slides if we don't want them .AutoRefreshOnObservable(slide => this.WhenAny(x => x.ShowNSFW)) .Filter(slide => !slide.IsNSFW || ShowNSFW) .RefCount(); // Find target mod to display by combining dynamic list with currently desired index _targetMod = Observable.CombineLatest( modVMs.QueryWhenChanged(), selectedIndex, resultSelector: (query, selected) => { var index = selected % (query.Count == 0 ? 1 : query.Count); return(query.Items.ElementAtOrDefault(index)); }) .StartWith(default(ModVM)) .ToGuiProperty(this, nameof(TargetMod)); // Mark interest and materialize image of target mod _image = this.WhenAny(x => x.TargetMod) // 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() .ToGuiProperty(this, nameof(Image)); VisitNexusSiteCommand = ReactiveCommand.Create( execute: () => { Process.Start(TargetMod.ModURL); return(Unit.Default); }, canExecute: this.WhenAny(x => x.TargetMod.ModURL) .Select(x => x?.StartsWith("https://") ?? false) .ObserveOnGuiThread()); // Preload upcoming images var list = Observable.CombineLatest( modVMs.QueryWhenChanged(), selectedIndex, resultSelector: (query, selected) => { // Retrieve the mods that should be preloaded var index = selected % (query.Count == 0 ? 1 : query.Count); var amountToTake = Math.Min(query.Count - index, PreloadAmount); return(query.Items.Skip(index).Take(amountToTake).ToObservable()); }) .Select(i => i.ToObservableChangeSet()) .Switch() .Transform(mod => mod.ImageObservable.Subscribe()) .DisposeMany() .AsObservableList(); }
public SlideShow(InstallerVM appState) { Installer = appState; // 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 SlideShowNextItemCommand.StartingExecution(), // 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 SlideShowNextItemCommand.StartingExecution() .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) // 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 => { if (modList?.SourceModList?.Archives == null) { return(Observable.Empty <ModVM>() .ToObservableChangeSet(x => x.ModID)); } return(modList.SourceModList.Archives .Select(m => m.State) .OfType <NexusDownloader.State>() .Select(nexus => new ModVM(nexus)) // Shuffle it .Shuffle(_random) .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)) .Filter(slide => !slide.IsNSFW || ShowNSFW) .RefCount(); // Find target mod to display by combining dynamic list with currently desired index _targetMod = Observable.CombineLatest( modVMs.QueryWhenChanged(), selectedIndex, resultSelector: (query, selected) => query.Items.ElementAtOrDefault(selected % (query.Count == 0 ? 1 : query.Count))) .StartWith(default(ModVM)) .ObserveOn(RxApp.MainThreadScheduler) .ToProperty(this, nameof(TargetMod)); // Mark interest and materialize image of target mod _image = this.WhenAny(x => x.TargetMod) // 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() .ToProperty(this, nameof(Image)); VisitNexusSiteCommand = ReactiveCommand.Create( execute: () => Process.Start(TargetMod.ModURL), canExecute: this.WhenAny(x => x.TargetMod.ModURL) .Select(x => x?.StartsWith("https://") ?? false) .ObserveOnGuiThread()); // 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. // 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. }