ToGuiProperty convenience call

Automatically wires on GUI thread and disposes
This commit is contained in:
Justin Swanson 2020-01-16 22:48:54 -06:00
parent 04b2d13499
commit 8571003cfd
12 changed files with 67 additions and 57 deletions

View File

@ -2,10 +2,12 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using DynamicData;
using DynamicData.Kernel;
using ReactiveUI;
using Wabbajack.Lib;
namespace Wabbajack
{
@ -78,6 +80,29 @@ namespace Wabbajack
return new ChangeSet<TObject, TKey>(changes);
}
public static ObservableAsPropertyHelper<TRet> ToGuiProperty<TRet>(
this IObservable<TRet> source,
ViewModel vm,
string property,
TRet initialValue = default,
bool deferSubscription = false)
{
return source
.ToProperty(vm, property, initialValue, deferSubscription, RxApp.MainThreadScheduler)
.DisposeWith(vm.CompositeDisposable);
}
public static void ToGuiProperty<TRet>(
this IObservable<TRet> source,
ViewModel vm,
string property,
out ObservableAsPropertyHelper<TRet> result,
TRet initialValue = default,
bool deferSubscription = false)
{
source.ToProperty(vm, property, out result, initialValue, deferSubscription, RxApp.MainThreadScheduler)
.DisposeWith(vm.CompositeDisposable);
}
internal static Optional<Change<TObject, TKey>> Reduce<TObject, TKey>(Optional<Change<TObject, TKey>> previous, Change<TObject, TKey> next)
{

View File

@ -147,9 +147,8 @@ namespace Wabbajack
}
})
.DistinctUntilChanged()
.ObserveOnGuiThread()
.StartWith(false)
.ToProperty(this, nameof(Exists));
.ToGuiProperty(this, nameof(Exists));
var passesFilters = Observable.CombineLatest(
this.WhenAny(x => x.TargetPath),
@ -218,12 +217,11 @@ namespace Wabbajack
if (filter.Failed) return filter;
return ErrorResponse.Convert(err);
})
.ObserveOnGuiThread()
.ToProperty(this, nameof(ErrorState));
.ToGuiProperty(this, nameof(ErrorState));
_inError = this.WhenAny(x => x.ErrorState)
.Select(x => !x.Succeeded)
.ToProperty(this, nameof(InError));
.ToGuiProperty(this, nameof(InError));
// Doesn't derive from ErrorState, as we want to bubble non-empty tooltips,
// which is slightly different logic
@ -244,8 +242,7 @@ namespace Wabbajack
if (!string.IsNullOrWhiteSpace(filters)) return filters;
return err?.Reason;
})
.ObserveOnGuiThread()
.ToProperty(this, nameof(ErrorTooltip));
.ToGuiProperty(this, nameof(ErrorTooltip));
}
public ICommand ConstructTypicalPickerCommand()

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
@ -36,7 +36,7 @@ namespace Wabbajack
.ObserveOnGuiThread());
_IsActive = this.ConstructIsActive(mainWindowVM)
.ToProperty(this, nameof(IsActive));
.ToGuiProperty(this, nameof(IsActive));
}
}

View File

@ -115,11 +115,11 @@ namespace Wabbajack
pair.Previous?.Unload();
})
.Select(p => p.Current)
.ToProperty(this, nameof(Compiler));
.ToGuiProperty(this, nameof(Compiler));
// Let sub VM determine what settings we're displaying and when
_currentModlistSettings = this.WhenAny(x => x.Compiler.ModlistSettings)
.ToProperty(this, nameof(CurrentModlistSettings));
.ToGuiProperty(this, nameof(CurrentModlistSettings));
_image = this.WhenAny(x => x.CurrentModlistSettings.ImagePath.TargetPath)
// Throttle so that it only loads image after any sets of swaps have completed
@ -135,12 +135,11 @@ namespace Wabbajack
}
return null;
})
.ToProperty(this, nameof(Image));
.ToGuiProperty(this, nameof(Image));
_compiling = this.WhenAny(x => x.Compiler.ActiveCompilation)
.Select(compilation => compilation != null)
.ObserveOnGuiThread()
.ToProperty(this, nameof(Compiling));
.ToGuiProperty(this, nameof(Compiling));
BackCommand = ReactiveCommand.Create(
execute: () =>
@ -176,7 +175,7 @@ namespace Wabbajack
})
.Switch()
.Debounce(TimeSpan.FromMilliseconds(25))
.ToProperty(this, nameof(PercentCompleted));
.ToGuiProperty(this, nameof(PercentCompleted));
BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: this.WhenAny(x => x.Compiler.CanCompile)
@ -217,8 +216,7 @@ namespace Wabbajack
_ActiveGlobalUserIntervention = activeInterventions.Connect()
.Filter(x => x.CpuID == WorkQueue.UnassignedCpuId)
.QueryWhenChanged(query => query.FirstOrDefault())
.ObserveOnGuiThread()
.ToProperty(this, nameof(ActiveGlobalUserIntervention));
.ToGuiProperty(this, nameof(ActiveGlobalUserIntervention));
CloseWhenCompleteCommand = ReactiveCommand.Create(
canExecute: this.WhenAny(x => x.Completed)
@ -263,12 +261,11 @@ namespace Wabbajack
return "Configuring";
}
})
.ToProperty(this, nameof(ProgressTitle));
.ToGuiProperty(this, nameof(ProgressTitle));
_CurrentCpuCount = this.WhenAny(x => x.Compiler.ActiveCompilation.Queue.CurrentCpuCount)
.Switch()
.ObserveOnGuiThread()
.ToProperty(this, nameof(CurrentCpuCount));
.ToGuiProperty(this, nameof(CurrentCpuCount));
}
}
}

View File

@ -68,7 +68,7 @@ namespace Wabbajack
return null;
}
})
.ToProperty(this, nameof(Mo2Folder));
.ToGuiProperty(this, nameof(Mo2Folder));
_moProfile = this.WhenAny(x => x.ModListLocation.TargetPath)
.Select(loc =>
{
@ -82,7 +82,7 @@ namespace Wabbajack
return null;
}
})
.ToProperty(this, nameof(MOProfile));
.ToGuiProperty(this, nameof(MOProfile));
// Wire missing Mo2Folder to signal error state for ModList Location
ModListLocation.AdditionalError = this.WhenAny(x => x.Mo2Folder)
@ -116,9 +116,7 @@ namespace Wabbajack
pair.Current?.Init();
})
.Select(x => x.Current)
// Save to property
.ObserveOnGuiThread()
.ToProperty(this, nameof(ModlistSettings));
.ToGuiProperty(this, nameof(ModlistSettings));
CanCompile = Observable.CombineLatest(
this.WhenAny(x => x.ModListLocation.InError),

View File

@ -94,9 +94,7 @@ namespace Wabbajack
current?.Init();
})
.Select(x => x.Current)
// Save to property
.ObserveOnGuiThread()
.ToProperty(this, nameof(ModlistSettings));
.ToGuiProperty(this, nameof(ModlistSettings));
CanCompile = Observable.CombineLatest(
this.WhenAny(x => x.GameLocation.InError),

View File

@ -1,4 +1,4 @@
using Syroot.Windows.IO;
using Syroot.Windows.IO;
using System;
using ReactiveUI;
using System.Diagnostics;
@ -147,7 +147,7 @@ namespace Wabbajack
pair.Previous?.Unload();
})
.Select(p => p.Current)
.ToProperty(this, nameof(Installer));
.ToGuiProperty(this, nameof(Installer));
// Load settings
MWVM.Settings.SaveSignal
@ -158,7 +158,7 @@ namespace Wabbajack
.DisposeWith(CompositeDisposable);
_IsActive = this.ConstructIsActive(MWVM)
.ToProperty(this, nameof(IsActive));
.ToGuiProperty(this, nameof(IsActive));
// Active path represents the path to currently have loaded
// If we're not actively showing, then "unload" the active path
@ -188,7 +188,7 @@ namespace Wabbajack
.DisposeOld()
.ObserveOnGuiThread()
.StartWith(default(ModListVM))
.ToProperty(this, nameof(ModList));
.ToGuiProperty(this, nameof(ModList));
// Force GC collect when modlist changes, just to make sure we clean up any loose large items immediately
this.WhenAny(x => x.ModList)
@ -205,17 +205,16 @@ namespace Wabbajack
// When the resulting modlist comes in, mark it as done
this.WhenAny(x => x.ModList)
.Select(_ => false))
.ToProperty(this, nameof(LoadingModlist));
.ToGuiProperty(this, nameof(LoadingModlist));
_htmlReport = this.WhenAny(x => x.ModList)
.Select(modList => modList?.ReportHTML)
.ToProperty(this, nameof(HTMLReport));
.ToGuiProperty(this, nameof(HTMLReport));
_installing = this.WhenAny(x => x.Installer.ActiveInstallation)
.Select(i => i != null)
.ObserveOnGuiThread()
.ToProperty(this, nameof(Installing));
.ToGuiProperty(this, nameof(Installing));
_TargetManager = this.WhenAny(x => x.ModList)
.Select(modList => modList?.ModManager)
.ToProperty(this, nameof(TargetManager));
.ToGuiProperty(this, nameof(TargetManager));
// Add additional error check on ModList
ModListLocation.AdditionalError = this.WhenAny(x => x.ModList)
@ -254,7 +253,7 @@ namespace Wabbajack
})
.Switch()
.Debounce(TimeSpan.FromMilliseconds(25))
.ToProperty(this, nameof(PercentCompleted));
.ToGuiProperty(this, nameof(PercentCompleted));
Slideshow = new SlideShow(this);
@ -280,7 +279,7 @@ namespace Wabbajack
return installing ? slideshow : modList;
})
.Select<BitmapImage, ImageSource>(x => x)
.ToProperty(this, nameof(Image));
.ToGuiProperty(this, nameof(Image));
_titleText = Observable.CombineLatest(
this.WhenAny(x => x.ModList)
.Select(modList => modList?.Name ?? string.Empty),
@ -288,7 +287,7 @@ namespace Wabbajack
.StartWith(default(string)),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)
.ToProperty(this, nameof(TitleText));
.ToGuiProperty(this, nameof(TitleText));
_authorText = Observable.CombineLatest(
this.WhenAny(x => x.ModList)
.Select(modList => modList?.Author ?? string.Empty),
@ -296,7 +295,7 @@ namespace Wabbajack
.StartWith(default(string)),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)
.ToProperty(this, nameof(AuthorText));
.ToGuiProperty(this, nameof(AuthorText));
_description = Observable.CombineLatest(
this.WhenAny(x => x.ModList)
.Select(modList => modList?.Description ?? string.Empty),
@ -304,7 +303,7 @@ namespace Wabbajack
.StartWith(default(string)),
this.WhenAny(x => x.Installing),
resultSelector: (modList, mod, installing) => installing ? mod : modList)
.ToProperty(this, nameof(Description));
.ToGuiProperty(this, nameof(Description));
_modListName = Observable.CombineLatest(
this.WhenAny(x => x.ModList.Error)
.Select(x => x != null),
@ -315,7 +314,7 @@ namespace Wabbajack
if (err) return "Corrupted Modlist";
return name;
})
.ToProperty(this, nameof(ModListName));
.ToGuiProperty(this, nameof(ModListName));
// Define commands
ShowReportCommand = ReactiveCommand.Create(ShowReport);
@ -350,7 +349,7 @@ namespace Wabbajack
return "Configuring";
}
})
.ToProperty(this, nameof(ProgressTitle));
.ToGuiProperty(this, nameof(ProgressTitle));
UIUtils.BindCpuStatus(
this.WhenAny(x => x.Installer.ActiveInstallation)
@ -405,8 +404,7 @@ namespace Wabbajack
_ActiveGlobalUserIntervention = activeInterventions.Connect()
.Filter(x => x.CpuID == WorkQueue.UnassignedCpuId)
.QueryWhenChanged(query => query.FirstOrDefault())
.ObserveOnGuiThread()
.ToProperty(this, nameof(ActiveGlobalUserIntervention));
.ToGuiProperty(this, nameof(ActiveGlobalUserIntervention));
CloseWhenCompleteCommand = ReactiveCommand.Create(
canExecute: this.WhenAny(x => x.Completed)
@ -429,8 +427,7 @@ namespace Wabbajack
_CurrentCpuCount = this.WhenAny(x => x.Installer.ActiveInstallation.Queue.CurrentCpuCount)
.Switch()
.ObserveOnGuiThread()
.ToProperty(this, nameof(CurrentCpuCount));
.ToGuiProperty(this, nameof(CurrentCpuCount));
}
private void ShowReport()

View File

@ -88,7 +88,7 @@ namespace Wabbajack
// Load settings
_CurrentSettings = installerVM.WhenAny(x => x.ModListLocation.TargetPath)
.Select(path => path == null ? null : installerVM.MWVM.Settings.Installer.Mo2ModlistSettings.TryCreate(path))
.ToProperty(this, nameof(CurrentSettings));
.ToGuiProperty(this, nameof(CurrentSettings));
this.WhenAny(x => x.CurrentSettings)
.Pairwise()
.Subscribe(settingsPair =>

View File

@ -39,7 +39,7 @@ namespace Wabbajack
Parent = installerVM;
_TargetGame = installerVM.WhenAny(x => x.ModList.SourceModList.GameType)
.ToProperty(this, nameof(TargetGame));
.ToGuiProperty(this, nameof(TargetGame));
CanInstall = Observable.CombineLatest(
this.WhenAny(x => x.TargetGame)

View File

@ -119,11 +119,11 @@ namespace Wabbajack
return true;
}
})
.ToProperty(this, nameof(Exists));
.ToGuiProperty(this, nameof(Exists));
_Image = Observable.Return(Metadata.Links.ImageUri)
.DownloadBitmapImage((ex) => Utils.Log($"Error downloading modlist image {Metadata.Title}"))
.ToProperty(this, nameof(Image));
.ToGuiProperty(this, nameof(Image));
}
private Task Download()

View File

@ -36,8 +36,7 @@ namespace Wabbajack
{
Login = login;
_MetaInfo = (login.MetaInfo ?? Observable.Return(""))
.ObserveOnGuiThread()
.ToProperty(this, nameof(MetaInfo));
.ToGuiProperty(this, nameof(MetaInfo));
}
}
}

View File

@ -109,15 +109,14 @@ namespace Wabbajack
return query.Items.ElementAtOrDefault(index);
})
.StartWith(default(ModVM))
.ObserveOnGuiThread()
.ToProperty(this, nameof(TargetMod));
.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()
.ToProperty(this, nameof(Image));
.ToGuiProperty(this, nameof(Image));
VisitNexusSiteCommand = ReactiveCommand.Create(
execute: () => Process.Start(TargetMod.ModURL),