Merge pull request #94 from Noggog/SlideshowView

Migrated more slideshow concepts away from AppState
This commit is contained in:
Timothy Baldridge 2019-10-14 14:51:56 -06:00 committed by GitHub
commit fc60eadab3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 379 additions and 345 deletions

View File

@ -25,19 +25,16 @@ using Wabbajack.UI;
using DynamicData;
using DynamicData.Binding;
using System.Reactive;
using System.Text;
namespace Wabbajack
{
public enum TaskMode { INSTALLING, BUILDING }
public class AppState : ViewModel, IDataErrorInfo
{
public const bool GcCollect = true;
private SlideShow _slideShow;
public SlideShow Slideshow { get; }
private string _mo2Folder;
private readonly BitmapImage _wabbajackLogo = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner.png");
public readonly BitmapImage _noneImage = UIUtils.BitmapImageFromResource("Wabbajack.UI.none.jpg");
private readonly Subject<CPUStatus> _statusSubject = new Subject<CPUStatus>();
@ -49,21 +46,12 @@ namespace Wabbajack
private string _ModListPath;
public string ModListPath { get => _ModListPath; private set => this.RaiseAndSetIfChanged(ref _ModListPath, value); }
private TaskMode _Mode;
public TaskMode Mode { get => _Mode; private set => this.RaiseAndSetIfChanged(ref _Mode, value); }
private RunMode _Mode;
public RunMode Mode { get => _Mode; private set => this.RaiseAndSetIfChanged(ref _Mode, value); }
private string _ModListName;
public string ModListName { get => _ModListName; set => this.RaiseAndSetIfChanged(ref _ModListName, value); }
private bool _EnableSlideShow = true;
public bool EnableSlideShow { get => _EnableSlideShow; set => this.RaiseAndSetIfChanged(ref _EnableSlideShow, value); }
private BitmapImage _SplashScreenImage;
public BitmapImage SplashScreenImage { get => _SplashScreenImage; set => this.RaiseAndSetIfChanged(ref _SplashScreenImage, value); }
private BitmapImage _NextIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.next.png");
public BitmapImage NextIcon { get => _NextIcon; set => this.RaiseAndSetIfChanged(ref _NextIcon, value); }
private bool _UIReady;
public bool UIReady { get => _UIReady; set => this.RaiseAndSetIfChanged(ref _UIReady, value); }
@ -78,12 +66,10 @@ namespace Wabbajack
public IReactiveCommand ChangeDownloadPathCommand { get; }
public IReactiveCommand BeginCommand { get; }
public IReactiveCommand ShowReportCommand { get; }
public IReactiveCommand VisitNexusSiteCommand { get; }
public IReactiveCommand OpenReadmeCommand { get; }
public IReactiveCommand OpenModListPropertiesCommand { get; }
public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { });
public AppState(TaskMode mode)
public AppState(RunMode mode)
{
if (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location.ToLower()) == KnownFolders.Downloads.Path.ToLower())
{
@ -102,103 +88,21 @@ namespace Wabbajack
this.ChangePathCommand = ReactiveCommand.Create(ExecuteChangePath);
this.ChangeDownloadPathCommand = ReactiveCommand.Create(ExecuteChangeDownloadPath);
this.ShowReportCommand = ReactiveCommand.Create(ShowReport);
this.VisitNexusSiteCommand = ReactiveCommand.Create(VisitNexusSite);
this.OpenModListPropertiesCommand = ReactiveCommand.Create(OpenModListProperties);
this.OpenModListPropertiesCommand = ReactiveCommand.Create(
execute: OpenModListProperties,
canExecute: this.WhenAny(x => x.UIReady)
.ObserveOnGuiThread());
this.OpenReadmeCommand = ReactiveCommand.Create(
execute: this.OpenReadmeWindow,
canExecute: Observable.CombineLatest(
this.WhenAny(x => x.ModList)
.Select(modList => !string.IsNullOrEmpty(modList?.Readme)),
this.WhenAny(x => x.UIReady),
resultSelector: (modListExists, uiReady) => modListExists && uiReady)
canExecute: this.WhenAny(x => x.ModList)
.Select(modList => !string.IsNullOrEmpty(modList?.Readme))
.ObserveOnGuiThread());
this.BeginCommand = ReactiveCommand.Create(
execute: this.ExecuteBegin,
canExecute: this.WhenAny(x => x.UIReady)
.ObserveOnGuiThread());
// Apply modlist properties when it changes
this.WhenAny(x => x.ModList)
.NotNull()
.Subscribe(modList =>
{
this.SplashScreenModName = modList.Name;
this.SplashScreenAuthorName = modList.Author;
this._nexusSiteURL = modList.Website;
this.SplashScreenSummary = modList.Description;
})
.DisposeWith(this.CompositeDisposable);
_slideShow = new SlideShow(this, true);
// Update splashscreen when modlist changes
Observable.CombineLatest(
this.WhenAny(x => x.ModList),
this.WhenAny(x => x.ModListPath),
this.WhenAny(x => x.EnableSlideShow),
(modList, modListPath, enableSlideShow) => (modList, modListPath, enableSlideShow))
// Do any potential unzipping on a background thread
.ObserveOn(RxApp.TaskpoolScheduler)
.Select(u =>
{
if (u.enableSlideShow
&& u.modList != null
&& u.modListPath != null
&& File.Exists(u.modListPath)
&& !string.IsNullOrEmpty(u.modList.Image)
&& u.modList.Image.Length == 36)
{
try
{
using (var fs = new FileStream(u.modListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(u.modList.Image);
using (var e = entry.Open())
e.CopyTo(ms);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
return image;
}
}
catch (Exception)
{
this.LogMsg("Error loading splash image.");
}
}
return _wabbajackLogo;
})
.ObserveOn(RxApp.MainThreadScheduler)
.StartWith(_wabbajackLogo)
.Subscribe(bitmap => this.SplashScreenImage = bitmap)
.DisposeWith(this.CompositeDisposable);
/// Wire slideshow updates
// Merge all the sources that trigger a slideshow update
Observable.Merge(
// If the natural timer fires
Observable.Interval(TimeSpan.FromSeconds(10)).Unit(),
// If user requests one manually
this.SlideShowNextItemCommand.StartingExecution())
// When enabled, fire an initial signal
.StartWith(Unit.Default)
// Only subscribe to slideshow triggers if enabled and installing
.FilterSwitch(
Observable.CombineLatest(
this.WhenAny(x => x.EnableSlideShow),
this.WhenAny(x => x.Installing),
resultSelector: (enabled, installing) => enabled && installing))
// Don't ever update more than once every half second. ToDo: Update to debounce
.Throttle(TimeSpan.FromMilliseconds(500), RxApp.MainThreadScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => _slideShow.UpdateSlideShowItem())
.DisposeWith(this.CompositeDisposable);
this.Slideshow = new SlideShow(this);
// Initialize work queue
WorkQueue.Init(
@ -235,18 +139,22 @@ namespace Wabbajack
private void ExecuteChangePath()
{
if (Mode == TaskMode.INSTALLING)
switch (this.Mode)
{
var folder = UIUtils.ShowFolderSelectionDialog("Select Installation directory");
if (folder == null) return;
Location = folder;
if (DownloadLocation == null)
DownloadLocation = Path.Combine(Location, "downloads");
}
else
{
var folder = UIUtils.ShowFolderSelectionDialog("Select Your MO2 profile directory");
Location = folder;
case RunMode.Compile:
Location = UIUtils.ShowFolderSelectionDialog("Select Your MO2 profile directory");
break;
case RunMode.Install:
var folder = UIUtils.ShowFolderSelectionDialog("Select Installation directory");
if (folder == null) return;
Location = folder;
if (DownloadLocation == null)
{
DownloadLocation = Path.Combine(Location, "downloads");
}
break;
default:
throw new NotImplementedException();
}
}
@ -263,15 +171,6 @@ namespace Wabbajack
Process.Start(file);
}
public string _nexusSiteURL = null;
private void VisitNexusSite()
{
if (_nexusSiteURL != null && _nexusSiteURL.StartsWith("https://"))
{
Process.Start(_nexusSiteURL);
}
}
private ModlistPropertiesWindow modlistPropertiesWindow;
public string newImagePath;
public string readmePath;
@ -299,39 +198,25 @@ namespace Wabbajack
private void OpenReadmeWindow()
{
if (!UIReady || string.IsNullOrEmpty(this.ModList.Readme)) return;
var text = "";
if (string.IsNullOrEmpty(this.ModList.Readme)) return;
using (var fs = new FileStream(this.ModListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(this.ModList.Readme);
using (var e = entry.Open())
e.CopyTo(ms);
ms.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(ms))
{
string line;
while ((line = sr.ReadLine()) != null)
text += line+Environment.NewLine;
e.CopyTo(ms);
}
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms))
{
var viewer = new TextViewer(reader.ReadToEnd(), this.ModListName);
viewer.Show();
}
}
var viewer = new TextViewer(text, this.ModListName);
viewer.Show();
}
private string _SplashScreenModName = "Wabbajack";
public string SplashScreenModName { get => _SplashScreenModName; set => this.RaiseAndSetIfChanged(ref _SplashScreenModName, value); }
private string _SplashScreenAuthorName = "Halgari & the Wabbajack Team";
public string SplashScreenAuthorName { get => _SplashScreenAuthorName; set => this.RaiseAndSetIfChanged(ref _SplashScreenAuthorName, value); }
private string _SplashScreenSummary;
public string SplashScreenSummary { get => _SplashScreenSummary; set => this.RaiseAndSetIfChanged(ref _SplashScreenSummary, value); }
private bool _splashShowNSFW = false;
public bool SplashShowNSFW { get => _splashShowNSFW; set => this.RaiseAndSetIfChanged(ref _splashShowNSFW, value); }
public string Error => "Error";
public string this[string columnName] => Validate(columnName);
@ -348,15 +233,15 @@ namespace Wabbajack
}
else switch (Mode)
{
case TaskMode.BUILDING when Location != null && Directory.Exists(Location) && File.Exists(Path.Combine(Location, "modlist.txt")):
case RunMode.Compile when Location != null && Directory.Exists(Location) && File.Exists(Path.Combine(Location, "modlist.txt")):
Location = Path.Combine(Location, "modlist.txt");
validationMessage = null;
ConfigureForBuild();
break;
case TaskMode.INSTALLING when Location != null && Directory.Exists(Location) && !Directory.EnumerateFileSystemEntries(Location).Any():
case RunMode.Install when Location != null && Directory.Exists(Location) && !Directory.EnumerateFileSystemEntries(Location).Any():
validationMessage = null;
break;
case TaskMode.INSTALLING when Location != null && Directory.Exists(Location) && Directory.EnumerateFileSystemEntries(Location).Any():
case RunMode.Install when Location != null && Directory.Exists(Location) && Directory.EnumerateFileSystemEntries(Location).Any():
validationMessage = "You have selected a non-empty directory. Installing the modlist here might result in a broken install!";
break;
default:
@ -390,7 +275,7 @@ namespace Wabbajack
var profile_name = Path.GetFileName(profile_folder);
this.ModListName = profile_name;
Mode = TaskMode.BUILDING;
this.Mode = RunMode.Compile;
var tmp_compiler = new Compiler(mo2folder);
DownloadLocation = tmp_compiler.MO2DownloadsFolder;
@ -402,12 +287,12 @@ namespace Wabbajack
{
this.ModList = modlist;
this.ModListPath = source;
Mode = TaskMode.INSTALLING;
this.Mode = RunMode.Install;
ModListName = this.ModList.Name;
HTMLReport = this.ModList.ReportHTML;
Location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
_slideShow.SlideShowElements = modlist.Archives
this.Slideshow.SlideShowElements = modlist.Archives
.Select(m => m.State)
.OfType<NexusDownloader.State>()
.Select(m =>
@ -416,13 +301,13 @@ namespace Wabbajack
m.Adult,m.NexusURL,m.SlideShowPic)).ToList();
_slideShow.PreloadSlideShow();
this.Slideshow.PreloadSlideShow();
}
private void ExecuteBegin()
{
UIReady = false;
if (Mode == TaskMode.INSTALLING)
if (this.Mode == RunMode.Install)
{
this.Installing = true;
var installer = new Installer(this.ModListPath, this.ModList, Location)
@ -459,11 +344,11 @@ namespace Wabbajack
var compiler = new Compiler(_mo2Folder)
{
MO2Profile = ModListName,
ModListName = ChangedProperties ? SplashScreenModName : null,
ModListAuthor = ChangedProperties ? SplashScreenAuthorName : null,
ModListDescription = ChangedProperties ? SplashScreenSummary : null,
ModListName = ChangedProperties ? this.Slideshow.ModName : null,
ModListAuthor = ChangedProperties ? this.Slideshow.AuthorName : null,
ModListDescription = ChangedProperties ? this.Slideshow.Summary : null,
ModListImage = ChangedProperties ? newImagePath : null,
ModListWebsite = ChangedProperties ? _nexusSiteURL : null,
ModListWebsite = ChangedProperties ? this.Slideshow.NexusSiteURL : null,
ModListReadme = ChangedProperties ? readmePath : null
};
var th = new Thread(() =>

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wabbajack
{
public enum RunMode
{
Compile,
Install
}
}

View File

@ -277,11 +277,11 @@ namespace Wabbajack.NexusApi
}
public static IEnumerable<UI.Slide> CachedSlideShow
public static IEnumerable<Slide> CachedSlideShow
{
get
{
if (!Directory.Exists(Consts.NexusCacheDirectory)) return new UI.Slide[] { };
if (!Directory.Exists(Consts.NexusCacheDirectory)) return new Slide[] { };
return Directory.EnumerateFiles(Consts.NexusCacheDirectory)
.Where(f => f.EndsWith(".json"))
@ -299,7 +299,7 @@ namespace Wabbajack.NexusApi
})
.Where(m => m != null)
.Where(m => m._internal_version == CACHED_VERSION_NUMBER && m.picture_url != null)
.Select(m => new UI.Slide(m.name,m.mod_id,m.summary,m.author,m.contains_adult_content,GetModURL(m.game_name,m.mod_id),m.picture_url));
.Select(m => new Slide(m.name,m.mod_id,m.summary,m.author,m.contains_adult_content,GetModURL(m.game_name,m.mod_id),m.picture_url));
}
}

View File

@ -1146,7 +1146,7 @@
</Style>
<Style TargetType="{x:Type Button}">
<Style TargetType="{x:Type Button}" x:Key="MainButtonStyle" >
<Setter Property="FocusVisualStyle" Value="{StaticResource ButtonFocusVisual}"/>
<Setter Property="Background" Value="{StaticResource ButtonBackground}"/>
<Setter Property="BorderBrush" Value="{StaticResource ButtonBorder}"/>
@ -1187,6 +1187,7 @@
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource MainButtonStyle}" TargetType="{x:Type Button}" />
<!-- ToggleButton-->
<Style TargetType="{x:Type ToggleButton}">

View File

@ -45,127 +45,13 @@
<TextBlock FontSize="16" Text="{Binding ModListName}" />
</StackPanel>
<!-- Properties -->
<Grid
Name="PropertyCompilerGrid"
<local:SlideshowView
x:Name="Slideshow"
Grid.Row="1"
Grid.Column="0"
Margin="0,0,2,4">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Margin="0,0,0,4"
Source="{Binding SplashScreenImage}"
Stretch="Fill" />
<Button
Grid.Row="1"
Height="30"
Command="{Binding OpenModListPropertiesCommand}"
IsEnabled="{Binding UIReady}">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="Modlist Properties" />
</Button>
</Grid>
<Grid
Name="PropertyInstallerGrid"
Grid.Row="1"
Grid.Column="0"
Margin="0,0,2,4">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Source="{Binding SplashScreenImage}"
Stretch="Fill" />
<Button
Grid.Row="1"
Height="30"
Command="{Binding OpenReadmeCommand}">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="Open README" />
</Button>
</Grid>
<!-- End Properties -->
<!-- Slideshow -->
<Grid
Grid.Row="1"
Grid.Column="1"
Margin="2,0,0,4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
FontSize="30"
FontWeight="Bold"
Text="{Binding SplashScreenModName}" />
<TextBlock
Grid.Row="1"
FontSize="15"
FontWeight="Bold"
Text="{Binding SplashScreenAuthorName}" />
<TextBlock
Grid.Row="2"
FontSize="15"
FontWeight="Bold"
Text="{Binding SplashScreenSummary}"
TextWrapping="Wrap" />
<Grid Grid.Row="3" VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<CheckBox
Name="EnableSlideShow"
Grid.Column="0"
Margin="0,10,0,0"
IsChecked="{Binding EnableSlideShow}">
Enable the Slideshow
</CheckBox>
<CheckBox
Name="ShowNSFWContent"
Grid.Column="1"
Margin="4,10,0,0"
IsChecked="{Binding SplashShowNSFW}">
Show NSFW Mods in the Slideshow
</CheckBox>
<Button
Grid.Column="2"
Height="30"
HorizontalAlignment="Right"
Command="{Binding SlideShowNextItemCommand}"
ToolTip="Skip to next slide">
<DockPanel>
<Image Source="{Binding NextIcon}" Stretch="Fill" />
</DockPanel>
</Button>
</Grid>
<Button
Grid.Row="4"
Height="30"
Command="{Binding VisitNexusSiteCommand}">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="View Nexus Site" />
</Button>
</Grid>
<!-- End Slideshow -->
Grid.ColumnSpan="2"
Margin="0,0,0,4"
DataContext="{Binding Slideshow}" />
<ProgressBar
Grid.Row="2"

View File

@ -16,19 +16,13 @@ namespace Wabbajack
{
private AppState _state;
public enum RunMode
{
Compile,
Install
}
public MainWindow(RunMode mode, string source)
{
var args = Environment.GetCommandLineArgs();
InitializeComponent();
var context = new AppState(TaskMode.BUILDING);
var context = new AppState(RunMode.Install);
context.LogMsg($"Wabbajack Build - {ThisAssembly.Git.Sha}");
SetupHandlers(context);
DataContext = context;
@ -36,19 +30,6 @@ namespace Wabbajack
Utils.SetLoggerFn(s => context.LogMsg(s));
Utils.SetStatusFn((msg, progress) => WorkQueue.Report(msg, progress));
_state._nexusSiteURL = "https://github.com/wabbajack-tools/wabbajack";
if (mode == RunMode.Compile)
{
PropertyCompilerGrid.Visibility = Visibility.Visible;
PropertyInstallerGrid.Visibility = Visibility.Hidden;
}
else
{
PropertyCompilerGrid.Visibility = Visibility.Hidden;
PropertyInstallerGrid.Visibility = Visibility.Visible;
}
new Thread(() =>
{
if (mode == RunMode.Compile)

View File

@ -7,8 +7,7 @@
mc:Ignorable="d"
Title="Wabbajack (Modlist Properties)" Height="600" Width="900"
Style="{StaticResource {x:Type Window}}" Icon="Icons/wabbajack.ico" WindowStyle="ToolWindow"
ResizeMode="NoResize"
Closing="Window_Closing">
ResizeMode="NoResize">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="300"/>

View File

@ -22,11 +22,6 @@ namespace Wabbajack
state = _state;
}
private void Window_Closing(object sender, CancelEventArgs e)
{
//Hide();
}
private void SetSplashScreen_Click(object sender, RoutedEventArgs e)
{
var file = UIUtils.OpenFileDialog("Banner image|*.png");
@ -45,13 +40,13 @@ namespace Wabbajack
{
BitmapImage splashScreen = new BitmapImage(new Uri(newBannerFile));
state.newImagePath = newBannerFile;
state.SplashScreenImage = splashScreen;
state.Slideshow.Image = splashScreen;
}
state.SplashScreenModName = ModlistNameProperty.Text;
state.SplashScreenSummary = ModlistDescriptionProperty.Text;
state.SplashScreenAuthorName = ModlistAuthorProperty.Text;
state._nexusSiteURL = ModlistWebsiteProperty.Text;
state.Slideshow.ModName = ModlistNameProperty.Text;
state.Slideshow.Summary = ModlistDescriptionProperty.Text;
state.Slideshow.AuthorName = ModlistAuthorProperty.Text;
state.Slideshow.NexusSiteURL = ModlistWebsiteProperty.Text;
state.readmePath = ModlistReadmeProperty.Text;
state.ChangedProperties = true;

View File

@ -1,14 +1,20 @@
using System;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.NexusApi;
namespace Wabbajack.UI
namespace Wabbajack
{
public class Slide
{
@ -34,29 +40,145 @@ namespace Wabbajack.UI
}
internal class SlideShow
public class SlideShow : ViewModel
{
private readonly Random _random;
private Slide _lastSlide;
private const bool UseSync = false;
private const int MaxCacheSize = 10;
private readonly AppState _appState;
public SlideShow(AppState appState, bool checkCache)
public List<Slide> SlideShowElements { get; set; }
public Dictionary<string, Slide> CachedSlides { get; }
public Queue<Slide> SlidesQueue { get; }
public AppState AppState { get; }
public BitmapImage NextIcon { get; } = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.next.png");
public BitmapImage WabbajackLogo { get; } = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner.png");
private bool _ShowNSFW;
public bool ShowNSFW { get => _ShowNSFW; set => this.RaiseAndSetIfChanged(ref _ShowNSFW, value); }
private bool _GCAfterUpdating = true;
public bool GCAfterUpdating { get => _GCAfterUpdating; set => this.RaiseAndSetIfChanged(ref _GCAfterUpdating, value); }
private bool _Enable = true;
public bool Enable { get => _Enable; set => this.RaiseAndSetIfChanged(ref _Enable, value); }
private BitmapImage _Image;
public BitmapImage Image { get => _Image; set => this.RaiseAndSetIfChanged(ref _Image, value); }
private string _ModName = "Wabbajack";
public string ModName { get => _ModName; set => this.RaiseAndSetIfChanged(ref _ModName, value); }
private string _AuthorName = "Halgari & the Wabbajack Team";
public string AuthorName { get => _AuthorName; set => this.RaiseAndSetIfChanged(ref _AuthorName, value); }
private string _Summary;
public string Summary { get => _Summary; set => this.RaiseAndSetIfChanged(ref _Summary, value); }
private string _NexusSiteURL = "https://github.com/wabbajack-tools/wabbajack";
public string NexusSiteURL { get => _NexusSiteURL; set => this.RaiseAndSetIfChanged(ref _NexusSiteURL, value); }
public IReactiveCommand SlideShowNextItemCommand { get; } = ReactiveCommand.Create(() => { });
public IReactiveCommand VisitNexusSiteCommand { get; }
public SlideShow(AppState appState)
{
SlideShowElements = new List<Slide>();
SlideShowElements = NexusApiClient.CachedSlideShow.ToList();
CachedSlides = new Dictionary<string, Slide>();
SlidesQueue = new Queue<Slide>();
_random = new Random();
_appState = appState;
AppState = appState;
if (!checkCache) return;
IEnumerable<Slide> files = NexusApiClient.CachedSlideShow;
IEnumerable<Slide> enumerable = files.ToList();
if (enumerable.Any())
{
SlideShowElements = enumerable.ToList();
}
this.VisitNexusSiteCommand = ReactiveCommand.Create(
execute: () => Process.Start(this.NexusSiteURL),
canExecute: this.WhenAny(x => x.NexusSiteURL)
.Select(x => x?.StartsWith("https://") ?? false)
.ObserveOnGuiThread());
// Apply modlist properties when it changes
this.WhenAny(x => x.AppState.ModList)
.NotNull()
.Subscribe(modList =>
{
this.NexusSiteURL = modList.Website;
this.ModName = modList.Name;
this.AuthorName = modList.Author;
this.Summary = modList.Description;
})
.DisposeWith(this.CompositeDisposable);
// Update splashscreen when modlist changes
Observable.CombineLatest(
(this).WhenAny(x => x.AppState.ModList),
(this).WhenAny(x => x.AppState.ModListPath),
(this).WhenAny(x => x.Enable),
(modList, modListPath, enabled) => (modList, modListPath, enabled))
// Do any potential unzipping on a background thread
.ObserveOn(RxApp.TaskpoolScheduler)
.Select(u =>
{
if (u.enabled
&& u.modList != null
&& u.modListPath != null
&& File.Exists(u.modListPath)
&& !string.IsNullOrEmpty(u.modList.Image)
&& u.modList.Image.Length == 36)
{
try
{
using (var fs = new FileStream(u.modListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(u.modList.Image);
using (var e = entry.Open())
e.CopyTo(ms);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
return image;
}
}
catch (Exception)
{
this.AppState.LogMsg("Error loading splash image.");
}
}
return this.WabbajackLogo;
})
.ObserveOn(RxApp.MainThreadScheduler)
.StartWith(this.WabbajackLogo)
.Subscribe(bitmap => this.Image = bitmap)
.DisposeWith(this.CompositeDisposable);
/// Wire slideshow updates
// Merge all the sources that trigger a slideshow update
Observable.Merge(
// If the natural timer fires
Observable.Interval(TimeSpan.FromSeconds(10)).Unit(),
// If user requests one manually
this.SlideShowNextItemCommand.StartingExecution())
// When 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.AppState.Installing),
resultSelector: (enabled, installing) => enabled && installing))
// Don't ever update more than once every half second. ToDo: Update to debounce
.Throttle(TimeSpan.FromMilliseconds(500), RxApp.MainThreadScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => this.UpdateSlideShowItem())
.DisposeWith(this.CompositeDisposable);
}
public void PreloadSlideShow()
@ -89,23 +211,23 @@ namespace Wabbajack.UI
//if (SlidesQueue.Contains(randomSlide)) continue;
CachedSlides.Remove(randomSlide.ModID);
if (AppState.GcCollect)
if (this.GCAfterUpdating)
GC.Collect();
}
if (!slide.IsNSFW || (slide.IsNSFW && ShowNSFW))
{
_appState.SplashScreenImage = _appState._noneImage;
this.Image = AppState._noneImage;
if (slide.ImageURL != null && slide.Image != null)
{
if (!CachedSlides.ContainsKey(slide.ModID)) return;
_appState.SplashScreenImage = slide.Image;
this.Image = slide.Image;
}
_appState.SplashScreenModName = slide.ModName;
_appState.SplashScreenAuthorName = slide.ModAuthor;
_appState.SplashScreenSummary = slide.ModDescription;
_appState._nexusSiteURL = slide.ModURL;
this.ModName = slide.ModName;
this.AuthorName = slide.ModAuthor;
this.Summary = slide.ModDescription;
this.NexusSiteURL = slide.ModURL;
}
SlidesQueue.Dequeue();
@ -194,12 +316,5 @@ namespace Wabbajack.UI
return result;
}
public bool ShowNSFW { get; set; }
public List<Slide> SlideShowElements { get; set; }
public Dictionary<string, Slide> CachedSlides { get; }
public Queue<Slide> SlidesQueue { get; }
}
}

View File

@ -0,0 +1,122 @@
<UserControl
x:Class="Wabbajack.SlideshowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:SlideShow}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="4" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="1"
Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Source="{Binding Image}"
Stretch="Fill" />
<Button
Grid.Row="1"
FontSize="15"
FontWeight="Bold">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource MainButtonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding AppState.Mode}" Value="{x:Static local:RunMode.Install}">
<Setter Property="Command" Value="{Binding AppState.OpenReadmeCommand}" />
<Setter Property="Content" Value="Open README" />
</DataTrigger>
<DataTrigger Binding="{Binding AppState.Mode}" Value="{x:Static local:RunMode.Compile}">
<Setter Property="Command" Value="{Binding AppState.OpenModListPropertiesCommand}" />
<Setter Property="Content" Value="Modlist Properties" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
<Grid
Grid.Row="1"
Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
FontSize="30"
FontWeight="Bold"
Margin="6,0,0,0"
Text="{Binding ModName}" />
<TextBlock
Grid.Row="1"
FontSize="15"
FontWeight="Bold"
Margin="6,0,0,6"
Text="{Binding AuthorName}" />
<TextBlock
Grid.Row="2"
FontSize="15"
FontWeight="Bold"
Padding="5"
Background="{StaticResource TextBoxBackground}"
Text="{Binding Summary}"
TextWrapping="Wrap" />
<Grid Grid.Row="3" VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<CheckBox
Margin="6,0,0,0"
Grid.Column="0"
VerticalAlignment="Center"
IsChecked="{Binding Enable}">
Enable the Slideshow
</CheckBox>
<CheckBox
Grid.Column="1"
Margin="15,0,0,0"
VerticalAlignment="Center"
IsChecked="{Binding ShowNSFW}">
Show NSFW Mods in the Slideshow
</CheckBox>
<Button
Grid.Column="2"
Height="30"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Command="{Binding SlideShowNextItemCommand}"
ToolTip="Skip to next slide">
<DockPanel>
<Image Source="{Binding NextIcon}" Stretch="Fill" />
</DockPanel>
</Button>
</Grid>
<Button
Grid.Row="4"
Height="30"
Command="{Binding VisitNexusSiteCommand}">
<TextBlock
FontSize="15"
FontWeight="Bold"
Text="View Nexus Site" />
</Button>
</Grid>
</Grid>
</UserControl>

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for SlideshowView.xaml
/// </summary>
public partial class SlideshowView : UserControl
{
public SlideshowView()
{
InitializeComponent();
}
}
}

View File

@ -236,7 +236,11 @@
<Compile Include="Downloaders\MegaDownloader.cs" />
<Compile Include="Downloaders\ModDBDownloader.cs" />
<Compile Include="Downloaders\NexusDownloader.cs" />
<Compile Include="Enums\RunMode.cs" />
<Compile Include="Extensions\ReactiveUIExt.cs" />
<Compile Include="UI\SlideshowView.xaml.cs">
<DependentUpon>SlideshowView.xaml</DependentUpon>
</Compile>
<Compile Include="UI\ModlistPropertiesWindow.xaml.cs">
<DependentUpon>ModlistPropertiesWindow.xaml</DependentUpon>
</Compile>
@ -258,6 +262,10 @@
<Compile Include="Validation\ValidateModlist.cs" />
<Compile Include="ViewModel.cs" />
<Compile Include="zEditIntegration.cs" />
<Page Include="UI\SlideshowView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UI\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>