diff --git a/Wabbajack.Common/FileExtractor.cs b/Wabbajack.Common/FileExtractor.cs index a4e2139e..1ff158bc 100644 --- a/Wabbajack.Common/FileExtractor.cs +++ b/Wabbajack.Common/FileExtractor.cs @@ -79,8 +79,8 @@ namespace Wabbajack.Common if (line.Length <= 4 || line[3] != '%') continue; - int.TryParse(line.Substring(0, 3), out var percent); - Utils.Status($"Extracting {name} - {line.Trim()}", percent); + int.TryParse(line.Substring(0, 3), out var percentInt); + Utils.Status($"Extracting {name} - {line.Trim()}", Percent.FactoryPutInRange(percentInt / 100d)); } } catch (Exception e) @@ -176,8 +176,8 @@ namespace Wabbajack.Common if (line.Length <= 4 || line[3] != '%') continue; - int.TryParse(line.Substring(0, 3), out var percent); - Utils.Status($"Extracting {name} - {line.Trim()}", percent); + int.TryParse(line.Substring(0, 3), out var percentInt); + Utils.Status($"Extracting {name} - {line.Trim()}", Percent.FactoryPutInRange(percentInt / 100d)); } } catch (Exception) @@ -188,7 +188,7 @@ namespace Wabbajack.Common if (p.ExitCode == 0) { - Utils.Status($"Extracting {name} - 100%", 100, alsoLog: true); + Utils.Status($"Extracting {name} - 100%", Percent.One, alsoLog: true); return; } Utils.Error(new _7zipReturnError(p.ExitCode, source, dest, p.StandardOutput.ReadToEnd())); diff --git a/Wabbajack.Common/StatusFileStream.cs b/Wabbajack.Common/StatusFileStream.cs index 0d566c4e..df2c70be 100644 --- a/Wabbajack.Common/StatusFileStream.cs +++ b/Wabbajack.Common/StatusFileStream.cs @@ -44,9 +44,9 @@ namespace Wabbajack.Common } if (_queue != null) - _queue.Report(_message, (int) (_inner.Position * 100 / _inner.Length)); + _queue.Report(_message, Percent.FactoryPutInRange(_inner.Position, _inner.Length)); else - Utils.Status(_message, (int) (_inner.Position * 100 / _inner.Length)); + Utils.Status(_message, Percent.FactoryPutInRange(_inner.Position, _inner.Length)); } public override void Write(byte[] buffer, int offset, int count) diff --git a/Wabbajack.Common/StatusUpdate.cs b/Wabbajack.Common/StatusUpdate.cs index 0ccba2a3..943b4008 100644 --- a/Wabbajack.Common/StatusUpdate.cs +++ b/Wabbajack.Common/StatusUpdate.cs @@ -15,8 +15,8 @@ namespace Wabbajack.Common private Subject _maxStep = new Subject(); public IObservable MaxStep => _maxStep; - private Subject _progress = new Subject(); - public IObservable Progress => _progress; + private Subject _progress = new Subject(); + public IObservable Progress => _progress; private int _internalCurrentStep; private int _internalMaxStep; @@ -37,24 +37,24 @@ namespace Wabbajack.Common Utils.Log(name); _step.OnNext(_internalCurrentStep); _stepName.OnNext(name); - MakeUpdate(0.0f); + MakeUpdate(Percent.Zero); } - private float OverAllStatus(float sub_status) + private Percent OverAllStatus(Percent sub_status) { var per_step = 1.0f / _internalMaxStep; var macro = _internalCurrentStep * per_step; - return macro + (per_step * sub_status); + return Percent.FactoryPutInRange(macro + (per_step * sub_status)); } - public void MakeUpdate(float progress) + public void MakeUpdate(Percent progress) { _progress.OnNext(OverAllStatus(progress)); } public void MakeUpdate(int max, int curr) { - MakeUpdate((float)curr / (max == 0 ? 1 : max)); + MakeUpdate(Percent.FactoryPutInRange(curr, max == 0 ? 1 : max)); } } diff --git a/Wabbajack.Common/Util/Percent.cs b/Wabbajack.Common/Util/Percent.cs new file mode 100644 index 00000000..4326d3ef --- /dev/null +++ b/Wabbajack.Common/Util/Percent.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Wabbajack.Common +{ + public struct Percent : IComparable, IEquatable + { + public static readonly Percent One = new Percent(1); + public static readonly Percent Zero = new Percent(0); + + public readonly double Value; + public Percent Inverse => new Percent(1 - this.Value, check: false); + + private Percent(double d, bool check) + { + if (!check || InRange(d)) + { + this.Value = d; + } + else + { + throw new ArgumentException("Element out of range: " + d); + } + } + + public Percent(double d) + : this(d, check: true) + { + } + + public Percent(int i) + { + if (i < 0) + { + Value = 0; + } + else if (i > 100) + { + Value = 1; + } + else + { + Value = i / 100d; + } + } + + public static bool InRange(double d) + { + return d >= 0 || d <= 1; + } + + public static Percent operator +(Percent c1, Percent c2) + { + return new Percent(c1.Value + c2.Value); + } + + public static Percent operator *(Percent c1, Percent c2) + { + return new Percent(c1.Value * c2.Value); + } + + public static Percent operator -(Percent c1, Percent c2) + { + return new Percent(c1.Value - c2.Value); + } + + public static Percent operator /(Percent c1, Percent c2) + { + return new Percent(c1.Value / c2.Value); + } + + public static implicit operator double(Percent c1) + { + return c1.Value; + } + + public static Percent FactoryPutInRange(double d) + { + if (double.IsNaN(d) || double.IsInfinity(d)) + { + throw new ArgumentException(); + } + if (d < 0) + { + return Percent.Zero; + } + else if (d > 1) + { + return Percent.One; + } + return new Percent(d, check: false); + } + + public static Percent FactoryPutInRange(int cur, int max) + { + return FactoryPutInRange(1.0d * cur / max); + } + + public static Percent FactoryPutInRange(long cur, long max) + { + return FactoryPutInRange(1.0d * cur / max); + } + + public static Percent AverageFromPercents(params Percent[] ps) + { + double percent = 0; + foreach (var p in ps) + { + percent += p.Value; + } + return new Percent(percent / ps.Length, check: false); + } + + public static Percent MultFromPercents(params Percent[] ps) + { + double percent = 1; + foreach (var p in ps) + { + percent *= p.Value; + } + return new Percent(percent, check: false); + } + + public override bool Equals(object obj) + { + if (!(obj is Percent rhs)) return false; + return Equals(rhs); + } + + public bool Equals(Percent other) + { + return this.Value == other.Value; + } + + public override int GetHashCode() + { + return this.Value.GetHashCode(); + } + + public override string ToString() + { + return ToString(0); + } + + public string ToString(string format) + { + return $"{(Value * 100).ToString(format)}%"; + } + + public string ToString(byte numDigits) + { + switch (numDigits) + { + case 0: + return ToString("n0"); + case 1: + return ToString("n1"); + case 2: + return ToString("n2"); + case 3: + return ToString("n3"); + case 4: + return ToString("n4"); + case 5: + return ToString("n5"); + case 6: + return ToString("n6"); + default: + throw new NotImplementedException(); + } + } + + public int CompareTo(object obj) + { + if (obj is Percent rhs) + { + return this.Value.CompareTo(rhs.Value); + } + return 0; + } + + public static bool TryParse(string str, out Percent p) + { + if (double.TryParse(str, out double d)) + { + if (InRange(d)) + { + p = new Percent(d); + return true; + } + } + p = default(Percent); + return false; + } + } +} diff --git a/Wabbajack.Common/Utils.cs b/Wabbajack.Common/Utils.cs index 8652c647..bebb4d30 100644 --- a/Wabbajack.Common/Utils.cs +++ b/Wabbajack.Common/Utils.cs @@ -126,7 +126,7 @@ namespace Wabbajack.Common } } - public static void Status(string msg, int progress = 0, bool alsoLog = false) + public static void Status(string msg, Percent progress, bool alsoLog = false) { WorkQueue.AsyncLocalCurrentQueue.Value?.Report(msg, progress); if (alsoLog) @@ -135,6 +135,11 @@ namespace Wabbajack.Common } } + public static void Status(string msg, bool alsoLog = false) + { + Status(msg, Percent.Zero, alsoLog: alsoLog); + } + public static void CatchAndLog(Action a) { try @@ -262,7 +267,7 @@ namespace Wabbajack.Common if (read == 0) break; totalRead += read; ostream.Write(buffer, 0, read); - Status(status, (int) (totalRead * 100 / maxSize)); + Status(status, Percent.FactoryPutInRange(totalRead, maxSize)); } } public static string xxHash(this byte[] data, bool nullOnIOError = false) @@ -764,7 +769,7 @@ namespace Wabbajack.Common var lst = coll.ToList(); lst.DoIndexed((idx, i) => { - Status(msg, idx * 100 / lst.Count); + Status(msg, Percent.FactoryPutInRange(idx, lst.Count)); f(i); }); } diff --git a/Wabbajack.Common/WorkQueue.cs b/Wabbajack.Common/WorkQueue.cs index 233d06f3..1ae1335e 100644 --- a/Wabbajack.Common/WorkQueue.cs +++ b/Wabbajack.Common/WorkQueue.cs @@ -116,7 +116,7 @@ namespace Wabbajack.Common { while (true) { - Report("Waiting", 0, false); + Report("Waiting", Percent.Zero, false); if (_shutdown.IsCancellationRequested) return; @@ -150,7 +150,7 @@ namespace Wabbajack.Common { Utils.Error($"Could not remove thread from workpool with CPU ID {cpuID}"); } - Report("Shutting down", 0, false); + Report("Shutting down", Percent.Zero, false); _cpuCountSubj.OnNext((_tasks.Count, DesiredNumWorkers)); return; } @@ -165,13 +165,12 @@ namespace Wabbajack.Common } } - public void Report(string msg, int progress, bool isWorking = true) + public void Report(string msg, Percent progress, bool isWorking = true) { _Status.OnNext( new CPUStatus { - Progress = progress, - ProgressPercent = progress / 100f, + ProgressPercent = progress, Msg = msg, ID = _cpuId.Value, IsWorking = isWorking @@ -193,8 +192,7 @@ namespace Wabbajack.Common public class CPUStatus { - public int Progress { get; internal set; } - public float ProgressPercent { get; internal set; } + public Percent ProgressPercent { get; internal set; } public string Msg { get; internal set; } public int ID { get; internal set; } public bool IsWorking { get; internal set; } diff --git a/Wabbajack.Lib/ABatchProcessor.cs b/Wabbajack.Lib/ABatchProcessor.cs index 7f8481d7..8e808af8 100644 --- a/Wabbajack.Lib/ABatchProcessor.cs +++ b/Wabbajack.Lib/ABatchProcessor.cs @@ -19,12 +19,12 @@ namespace Wabbajack.Lib protected StatusUpdateTracker UpdateTracker { get; private set; } - private Subject _percentCompleted { get; } = new Subject(); + private Subject _percentCompleted { get; } = new Subject(); /// /// The current progress of the entire processing system on a scale of 0.0 to 1.0 /// - public IObservable PercentCompleted => _percentCompleted; + public IObservable PercentCompleted => _percentCompleted; private Subject _textStatus { get; } = new Subject(); @@ -51,7 +51,7 @@ namespace Wabbajack.Lib // WorkQueue settings public BehaviorSubject ManualCoreLimit = new BehaviorSubject(true); public BehaviorSubject MaxCores = new BehaviorSubject(byte.MaxValue); - public BehaviorSubject TargetUsagePercent = new BehaviorSubject(1.0d); + public BehaviorSubject TargetUsagePercent = new BehaviorSubject(Percent.One); protected void ConfigureProcessor(int steps, IObservable numThreads = null) { diff --git a/Wabbajack.Lib/ACompiler.cs b/Wabbajack.Lib/ACompiler.cs index a2586c5f..49283eac 100644 --- a/Wabbajack.Lib/ACompiler.cs +++ b/Wabbajack.Lib/ACompiler.cs @@ -54,7 +54,7 @@ namespace Wabbajack.Lib public void Status(string msg) { - Queue.Report(msg, 0); + Queue.Report(msg, Percent.Zero); } public static void Error(string msg) diff --git a/Wabbajack.Lib/AInstaller.cs b/Wabbajack.Lib/AInstaller.cs index 3ddebb13..29e870fa 100644 --- a/Wabbajack.Lib/AInstaller.cs +++ b/Wabbajack.Lib/AInstaller.cs @@ -47,7 +47,7 @@ namespace Wabbajack.Lib public void Status(string msg) { - Queue.Report(msg, 0); + Queue.Report(msg, Percent.Zero); } public void Error(string msg) @@ -183,7 +183,7 @@ namespace Wabbajack.Lib await vFiles.GroupBy(f => f.FromFile) .PDoIndexed(queue, (idx, group) => { - Utils.Status("Installing files", idx * 100 / vFiles.Count); + Utils.Status("Installing files", Percent.FactoryPutInRange(idx, vFiles.Count)); var firstDest = Path.Combine(OutputFolder, group.First().To); CopyFile(group.Key.StagedPath, firstDest, true); diff --git a/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs b/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs index 60ff4239..a2e637c9 100644 --- a/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs +++ b/Wabbajack.Lib/Downloaders/AbstractIPS4Downloader.cs @@ -124,7 +124,7 @@ namespace Wabbajack.Lib.Downloaders var secs = times.Download - times.CurrentTime; for (int x = 0; x < secs; x++) { - Utils.Status($"Waiting for {secs} at the request of {downloader.SiteName}", x * 100 / secs); + Utils.Status($"Waiting for {secs} at the request of {downloader.SiteName}", Percent.FactoryPutInRange(x, secs)); await Task.Delay(1000); } Utils.Status("Retrying download"); diff --git a/Wabbajack.Lib/Downloaders/BethesdaNetDownloader.cs b/Wabbajack.Lib/Downloaders/BethesdaNetDownloader.cs index 76d1d54b..59696f10 100644 --- a/Wabbajack.Lib/Downloaders/BethesdaNetDownloader.cs +++ b/Wabbajack.Lib/Downloaders/BethesdaNetDownloader.cs @@ -133,7 +133,7 @@ namespace Wabbajack.Lib.Downloaders var max_chunks = info.depot_list[0].file_list[0].chunk_count; foreach (var chunk in info.depot_list[0].file_list[0].chunk_list.OrderBy(c => c.index)) { - Utils.Status($"Downloading {a.Name}", chunk.index * 100 / max_chunks); + Utils.Status($"Downloading {a.Name}", Percent.FactoryPutInRange(chunk.index, max_chunks)); var got = await client.GetAsync( $"https://content.cdp.bethesda.net/{collected.CDPProductId}/{collected.CDPPropertiesId}/{chunk.sha}"); var data = await got.Content.ReadAsByteArrayAsync(); diff --git a/Wabbajack.Lib/Downloaders/HTTPDownloader.cs b/Wabbajack.Lib/Downloaders/HTTPDownloader.cs index f0538e99..860a6848 100644 --- a/Wabbajack.Lib/Downloaders/HTTPDownloader.cs +++ b/Wabbajack.Lib/Downloaders/HTTPDownloader.cs @@ -101,7 +101,7 @@ namespace Wabbajack.Lib.Downloaders long totalRead = 0; var bufferSize = 1024 * 32; - Utils.Status($"Starting Download {a?.Name ?? Url}", 0); + Utils.Status($"Starting Download {a?.Name ?? Url}", Percent.Zero); var response = await client.GetAsync(Url, HttpCompletionOption.ResponseHeadersRead); TOP: @@ -179,7 +179,7 @@ TOP: read_this_cycle += read; if (read == 0) break; - Utils.Status($"Downloading {a.Name}", (int)(totalRead * 100 / contentSize)); + Utils.Status($"Downloading {a.Name}", Percent.FactoryPutInRange(totalRead, contentSize)); fs.Write(buffer, 0, read); totalRead += read; diff --git a/Wabbajack.Lib/FileUploader/AuthorAPI.cs b/Wabbajack.Lib/FileUploader/AuthorAPI.cs index 553b3049..9f145bca 100644 --- a/Wabbajack.Lib/FileUploader/AuthorAPI.cs +++ b/Wabbajack.Lib/FileUploader/AuthorAPI.cs @@ -61,7 +61,7 @@ namespace Wabbajack.Lib.FileUploader long sent = 0; using (var iqueue = new WorkQueue(MAX_CONNECTIONS)) { - iqueue.Report("Starting Upload", 1); + iqueue.Report("Starting Upload", Percent.One); await Blocks(fsize) .PMap(iqueue, async block_idx => { diff --git a/Wabbajack.Lib/IBatchProcessor.cs b/Wabbajack.Lib/IBatchProcessor.cs index 9b44d70f..ebf4531f 100644 --- a/Wabbajack.Lib/IBatchProcessor.cs +++ b/Wabbajack.Lib/IBatchProcessor.cs @@ -18,7 +18,7 @@ namespace Wabbajack.Lib /// /// The current progress of the entire processing system on a scale of 0.0 to 1.0 /// - IObservable PercentCompleted { get; } + IObservable PercentCompleted { get; } /// /// The current status of the processor as a text string diff --git a/Wabbajack/Converters/ConverterRegistration.cs b/Wabbajack/Converters/ConverterRegistration.cs index bb406c07..3231f868 100644 --- a/Wabbajack/Converters/ConverterRegistration.cs +++ b/Wabbajack/Converters/ConverterRegistration.cs @@ -20,6 +20,10 @@ namespace Wabbajack new IntDownCastConverter(), typeof(IBindingTypeConverter) ); + Locator.CurrentMutable.RegisterConstant( + new PercentToDoubleConverter(), + typeof(IBindingTypeConverter) + ); } } } diff --git a/Wabbajack/Converters/PercentJsonConverter.cs b/Wabbajack/Converters/PercentJsonConverter.cs new file mode 100644 index 00000000..bfdc154b --- /dev/null +++ b/Wabbajack/Converters/PercentJsonConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Newtonsoft.Json; +using Wabbajack.Common; + +namespace Wabbajack +{ + public class PercentJsonConverter : JsonConverter + { + public override Percent ReadJson(JsonReader reader, Type objectType, [AllowNull] Percent existingValue, bool hasExistingValue, JsonSerializer serializer) + { + double d = (double)reader.Value; + return Percent.FactoryPutInRange(d); + } + + public override void WriteJson(JsonWriter writer, [AllowNull] Percent value, JsonSerializer serializer) + { + writer.WriteValue(value.Value); + } + } +} diff --git a/Wabbajack/Converters/PercentToDoubleConverter.cs b/Wabbajack/Converters/PercentToDoubleConverter.cs new file mode 100644 index 00000000..850519dd --- /dev/null +++ b/Wabbajack/Converters/PercentToDoubleConverter.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using ReactiveUI; +using Wabbajack.Common; + +namespace Wabbajack +{ + public class PercentToDoubleConverter : IBindingTypeConverter + { + public int GetAffinityForObjects(Type fromType, Type toType) + { + if (toType == typeof(double)) return 1; + if (toType == typeof(double?)) return 1; + if (toType == typeof(Percent)) return 1; + if (toType == typeof(Percent?)) return 1; + return 0; + } + + public bool TryConvert(object from, Type toType, object conversionHint, out object result) + { + if (toType == typeof(double)) + { + if (from is Percent p) + { + result = p.Value; + return true; + } + result = 0d; + return false; + } + if (toType == typeof(double?)) + { + if (from is Percent p) + { + result = p.Value; + return true; + } + if (from == null) + { + result = default(double?); + return true; + } + result = default(double?); + return false; + } + if (toType == typeof(Percent)) + { + if (from is double d) + { + result = Percent.FactoryPutInRange(d); + return true; + } + result = Percent.Zero; + return false; + } + if (toType == typeof(Percent?)) + { + if (from is double d) + { + result = Percent.FactoryPutInRange(d); + return true; + } + if (from == null) + { + result = default(Percent?); + return true; + } + result = Percent.Zero; + return false; + } + + result = null; + return false; + } + } +} diff --git a/Wabbajack/Settings.cs b/Wabbajack/Settings.cs index 14cba302..66b42622 100644 --- a/Wabbajack/Settings.cs +++ b/Wabbajack/Settings.cs @@ -82,8 +82,8 @@ namespace Wabbajack private byte _MaxCores = byte.MaxValue; public byte MaxCores { get => _MaxCores; set => this.RaiseAndSetIfChanged(ref _MaxCores, value); } - private double _TargetUsage = 1.0d; - public double TargetUsage { get => _TargetUsage; set => this.RaiseAndSetIfChanged(ref _TargetUsage, value); } + private Percent _TargetUsage = Percent.One; + public Percent TargetUsage { get => _TargetUsage; set => this.RaiseAndSetIfChanged(ref _TargetUsage, value); } public void AttachToBatchProcessor(ABatchProcessor processor) { diff --git a/Wabbajack/Util/UIUtils.cs b/Wabbajack/Util/UIUtils.cs index 229d2082..1235593f 100644 --- a/Wabbajack/Util/UIUtils.cs +++ b/Wabbajack/Util/UIUtils.cs @@ -65,6 +65,7 @@ namespace Wabbajack .ToObservableChangeSet(x => x.ID) .Batch(TimeSpan.FromMilliseconds(50), RxApp.TaskpoolScheduler) .EnsureUniqueChanges() + .ObserveOnGuiThread() .TransformAndCache( onAdded: (key, cpu) => new CPUDisplayVM(cpu), onUpdated: (change, vm) => vm.AbsorbStatus(change.Current)) @@ -72,7 +73,6 @@ namespace Wabbajack .AutoRefresh(x => x.StartTime) .Filter(i => i.IsWorking && i.ID != WorkQueue.UnassignedCpuId) .Sort(SortExpressionComparer.Ascending(s => s.StartTime)) - .ObserveOnGuiThread() .Bind(list) .Subscribe(); } diff --git a/Wabbajack/View Models/CPUDisplayVM.cs b/Wabbajack/View Models/CPUDisplayVM.cs index 4afbbbb0..559d95c3 100644 --- a/Wabbajack/View Models/CPUDisplayVM.cs +++ b/Wabbajack/View Models/CPUDisplayVM.cs @@ -20,7 +20,7 @@ namespace Wabbajack [Reactive] public string Msg { get; set; } [Reactive] - public float ProgressPercent { get; set; } + public Percent ProgressPercent { get; set; } public CPUDisplayVM() { diff --git a/Wabbajack/View Models/Compilers/CompilerVM.cs b/Wabbajack/View Models/Compilers/CompilerVM.cs index 3e91b6a3..96df5a4f 100644 --- a/Wabbajack/View Models/Compilers/CompilerVM.cs +++ b/Wabbajack/View Models/Compilers/CompilerVM.cs @@ -40,8 +40,8 @@ namespace Wabbajack private readonly ObservableAsPropertyHelper _compiling; public bool Compiling => _compiling.Value; - private readonly ObservableAsPropertyHelper _percentCompleted; - public float PercentCompleted => _percentCompleted.Value; + private readonly ObservableAsPropertyHelper _percentCompleted; + public Percent PercentCompleted => _percentCompleted.Value; public ObservableCollectionExtended StatusList { get; } = new ObservableCollectionExtended(); @@ -169,9 +169,9 @@ namespace Wabbajack { if (compiler == null) { - return Observable.Return(completed != null ? 1f : 0f); + return Observable.Return(completed != null ? Percent.One : Percent.Zero); } - return compiler.PercentCompleted.StartWith(0); + return compiler.PercentCompleted.StartWith(Percent.Zero); }) .Switch() .Debounce(TimeSpan.FromMilliseconds(25), RxApp.MainThreadScheduler) diff --git a/Wabbajack/View Models/Installers/InstallerVM.cs b/Wabbajack/View Models/Installers/InstallerVM.cs index 779f0695..c4600081 100644 --- a/Wabbajack/View Models/Installers/InstallerVM.cs +++ b/Wabbajack/View Models/Installers/InstallerVM.cs @@ -68,8 +68,8 @@ namespace Wabbajack private readonly ObservableAsPropertyHelper _modListName; public string ModListName => _modListName.Value; - private readonly ObservableAsPropertyHelper _percentCompleted; - public float PercentCompleted => _percentCompleted.Value; + private readonly ObservableAsPropertyHelper _percentCompleted; + public Percent PercentCompleted => _percentCompleted.Value; public ObservableCollectionExtended StatusList { get; } = new ObservableCollectionExtended(); public ObservableCollectionExtended Log => MWVM.Log; @@ -238,9 +238,9 @@ namespace Wabbajack { if (installer == null) { - return Observable.Return(completed != null ? 1f : 0f); + return Observable.Return(completed != null ? Percent.One : Percent.Zero); } - return installer.PercentCompleted.StartWith(0f); + return installer.PercentCompleted.StartWith(Percent.Zero); }) .Switch() .Debounce(TimeSpan.FromMilliseconds(25), RxApp.MainThreadScheduler) diff --git a/Wabbajack/View Models/ModListMetadataVM.cs b/Wabbajack/View Models/ModListMetadataVM.cs index 48fd19b3..4d4dc7c7 100644 --- a/Wabbajack/View Models/ModListMetadataVM.cs +++ b/Wabbajack/View Models/ModListMetadataVM.cs @@ -34,7 +34,7 @@ namespace Wabbajack public string Location { get; } [Reactive] - public double ProgressPercent { get; private set; } + public Percent ProgressPercent { get; private set; } [Reactive] public bool IsBroken { get; private set; } @@ -144,7 +144,7 @@ namespace Wabbajack private async Task Download() { - ProgressPercent = 0d; + ProgressPercent = Percent.Zero; using (var queue = new WorkQueue(1)) using (queue.Status.Select(i => i.ProgressPercent) .Subscribe(percent => ProgressPercent = percent)) diff --git a/Wabbajack/Views/Common/CpuLineView.xaml b/Wabbajack/Views/Common/CpuLineView.xaml new file mode 100644 index 00000000..129159e6 --- /dev/null +++ b/Wabbajack/Views/Common/CpuLineView.xaml @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/Wabbajack/Views/Common/CpuLineView.xaml.cs b/Wabbajack/Views/Common/CpuLineView.xaml.cs new file mode 100644 index 00000000..a1314ad1 --- /dev/null +++ b/Wabbajack/Views/Common/CpuLineView.xaml.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Text; +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; +using ReactiveUI; + +namespace Wabbajack +{ + /// + /// Interaction logic for CpuLineView.xaml + /// + public partial class CpuLineView : ReactiveUserControl + { + public CpuLineView() + { + InitializeComponent(); + this.WhenActivated(dispose => + { + this.WhenAny(x => x.ViewModel.ProgressPercent) + .Select(x => x.Value) + .BindToStrict(this, x => x.BackgroundProgressBar.Value) + .DisposeWith(dispose); + this.WhenAny(x => x.ViewModel.ProgressPercent) + .Select(x => x.Value) + .BindToStrict(this, x => x.BackgroundProgressBar.Opacity) + .DisposeWith(dispose); + this.WhenAny(x => x.ViewModel.ProgressPercent) + .Select(x => x.Value) + .BindToStrict(this, x => x.ThinProgressBar.Value) + .DisposeWith(dispose); + + this.WhenAny(x => x.ViewModel.Msg) + .BindToStrict(this, x => x.Text.Text) + .DisposeWith(dispose); + this.WhenAny(x => x.ViewModel.Msg) + .BindToStrict(this, x => x.Text.ToolTip) + .DisposeWith(dispose); + }); + } + } +} diff --git a/Wabbajack/Views/Common/CpuView.xaml b/Wabbajack/Views/Common/CpuView.xaml index 6a13e65c..4a1756db 100644 --- a/Wabbajack/Views/Common/CpuView.xaml +++ b/Wabbajack/Views/Common/CpuView.xaml @@ -4,14 +4,13 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Wabbajack" - xmlns:mahapps="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="450" d:DesignWidth="800" x:TypeArguments="local:ICpuStatusVM" mc:Ignorable="d"> - + @@ -25,29 +24,7 @@ ScrollViewer.HorizontalScrollBarVisibility="Disabled"> - - - - - - - + diff --git a/Wabbajack/Views/Common/CpuView.xaml.cs b/Wabbajack/Views/Common/CpuView.xaml.cs index 72cf54c7..2e1eb6b6 100644 --- a/Wabbajack/Views/Common/CpuView.xaml.cs +++ b/Wabbajack/Views/Common/CpuView.xaml.cs @@ -18,6 +18,7 @@ using ReactiveUI.Fody.Helpers; using Wabbajack.Lib; using System.Windows.Controls.Primitives; using System.Reactive.Linq; +using Wabbajack.Common; namespace Wabbajack { @@ -26,13 +27,13 @@ namespace Wabbajack /// public partial class CpuView : UserControlRx { - public double ProgressPercent + public Percent ProgressPercent { - get => (double)GetValue(ProgressPercentProperty); + get => (Percent)GetValue(ProgressPercentProperty); set => SetValue(ProgressPercentProperty, value); } - public static readonly DependencyProperty ProgressPercentProperty = DependencyProperty.Register(nameof(ProgressPercent), typeof(double), typeof(CpuView), - new FrameworkPropertyMetadata(default(double))); + public static readonly DependencyProperty ProgressPercentProperty = DependencyProperty.Register(nameof(ProgressPercent), typeof(Percent), typeof(CpuView), + new FrameworkPropertyMetadata(default(Percent), WireNotifyPropertyChanged)); public MainSettings SettingsHook { @@ -55,28 +56,32 @@ namespace Wabbajack this.WhenAny(x => x.SettingsHook.Performance.Manual) .StartWith(true), resultSelector: (over, manual) => over && !manual) - .Subscribe(showing => - { - SettingsBar.Visibility = showing ? Visibility.Visible : Visibility.Collapsed; - }) + .Select(showing => showing ? Visibility.Visible : Visibility.Collapsed) + .BindToStrict(this, x => x.SettingsBar.Visibility) + .DisposeWith(disposable); + + this.WhenAny(x => x.ViewModel.StatusList) + .BindToStrict(this, x => x.CpuListControl.ItemsSource) .DisposeWith(disposable); - this.OneWayBindStrict(this.ViewModel, x => x.StatusList, x => x.CpuListControl.ItemsSource) + this.Bind(this.ViewModel, x => x.MWVM.Settings.Performance.TargetUsage, x => x.TargetPercentageSlider.Value) .DisposeWith(disposable); - this.BindStrict(this.ViewModel, x => x.MWVM.Settings.Performance.TargetUsage, x => x.TargetPercentageSlider.Value) - .DisposeWith(disposable); - - this.OneWayBindStrict(this.ViewModel, x => x.MWVM.Settings.Performance.TargetUsage, x => x.PercentageText.Text, x => $"{x.ToString("f2")}%") + this.WhenAny(x => x.ViewModel.MWVM.Settings.Performance.TargetUsage) + .Select(p => p.ToString(0)) + .BindToStrict(this, x => x.PercentageText.Text) .DisposeWith(disposable); this.WhenAny(x => x.ViewModel.CurrentCpuCount) .DistinctUntilChanged() - .ObserveOnGuiThread() - .Subscribe(x => - { - this.CpuCountText.Text = $"{x.CurrentCPUs} / {x.DesiredCPUs}"; - }) + .Select(x => $"{x.CurrentCPUs} / {x.DesiredCPUs}") + .BindToStrict(this, x => x.CpuCountText.Text) + .DisposeWith(disposable); + + // Progress + this.WhenAny(x => x.ProgressPercent) + .Select(p => p.Value) + .BindToStrict(this, x => x.HeatedBorderRect.Opacity) .DisposeWith(disposable); }); } diff --git a/Wabbajack/Views/Common/TopProgressView.xaml b/Wabbajack/Views/Common/TopProgressView.xaml index a47aa8d3..26c4e01f 100644 --- a/Wabbajack/Views/Common/TopProgressView.xaml +++ b/Wabbajack/Views/Common/TopProgressView.xaml @@ -1,4 +1,4 @@ - + IsHitTestVisible="False"> - - - + Maximum="1"> @@ -69,9 +59,7 @@ Grid.ColumnSpan="4" Background="#AA121212" BorderThickness="0" - Maximum="1" - Opacity="{Binding ProgressOpacityPercent, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" - Value="{Binding ProgressPercent, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"> + Maximum="1"> @@ -85,8 +73,7 @@ Grid.ColumnSpan="4" Background="Transparent" BorderThickness="0" - Maximum="1" - Value="{Binding ProgressPercent, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"> + Maximum="1"> @@ -95,6 +82,7 @@ + TextAlignment="Right" /> + Width="50" /> + FontWeight="Black" /> + Maximum="1"> @@ -143,8 +128,7 @@ Background="Transparent" BorderBrush="Transparent" Foreground="{StaticResource SecondaryBrush}" - Maximum="1" - Value="{Binding ProgressPercent, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"> + Maximum="1"> @@ -158,8 +142,7 @@ Background="Transparent" BorderBrush="Transparent" Foreground="{StaticResource SecondaryBrush}" - Maximum="1" - Value="{Binding ProgressPercent, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" /> + Maximum="1" /> + Maximum="1"> diff --git a/Wabbajack/Views/Common/TopProgressView.xaml.cs b/Wabbajack/Views/Common/TopProgressView.xaml.cs index 8a150a5f..bfe0b43e 100644 --- a/Wabbajack/Views/Common/TopProgressView.xaml.cs +++ b/Wabbajack/Views/Common/TopProgressView.xaml.cs @@ -1,4 +1,4 @@ -using System.Reactive.Linq; +using System.Reactive.Linq; using System.Windows; using System.Windows.Controls; using ReactiveUI; @@ -28,7 +28,7 @@ namespace Wabbajack set => SetValue(TitleProperty, value); } public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(TopProgressView), - new FrameworkPropertyMetadata(default(string))); + new FrameworkPropertyMetadata(default(string), WireNotifyPropertyChanged)); public string StatePrefixTitle { @@ -36,7 +36,7 @@ namespace Wabbajack set => SetValue(StatePrefixTitleProperty, value); } public static readonly DependencyProperty StatePrefixTitleProperty = DependencyProperty.Register(nameof(StatePrefixTitle), typeof(string), typeof(TopProgressView), - new FrameworkPropertyMetadata(default(string))); + new FrameworkPropertyMetadata(default(string), WireNotifyPropertyChanged)); public bool OverhangShadow { @@ -44,7 +44,7 @@ namespace Wabbajack set => SetValue(OverhangShadowProperty, value); } public static readonly DependencyProperty OverhangShadowProperty = DependencyProperty.Register(nameof(OverhangShadow), typeof(bool), typeof(TopProgressView), - new FrameworkPropertyMetadata(true)); + new FrameworkPropertyMetadata(true, WireNotifyPropertyChanged)); public bool ShadowMargin { @@ -52,10 +52,7 @@ namespace Wabbajack set => SetValue(ShadowMarginProperty, value); } public static readonly DependencyProperty ShadowMarginProperty = DependencyProperty.Register(nameof(ShadowMargin), typeof(bool), typeof(TopProgressView), - new FrameworkPropertyMetadata(true)); - - [Reactive] - public double ProgressOpacityPercent { get; private set; } + new FrameworkPropertyMetadata(true, WireNotifyPropertyChanged)); public TopProgressView() { @@ -63,11 +60,53 @@ namespace Wabbajack this.WhenActivated(dispose => { this.WhenAny(x => x.ProgressPercent) - .Select(x => - { - return 0.3 + x * 0.7; - }) - .Subscribe(x => ProgressOpacityPercent = x) + .Select(x => 0.3 + x * 0.7) + .BindToStrict(this, x => x.LargeProgressBar.Opacity) + .DisposeWith(dispose); + this.WhenAny(x => x.ProgressPercent) + .BindToStrict(this, x => x.LargeProgressBar.Value) + .DisposeWith(dispose); + this.WhenAny(x => x.ProgressPercent) + .BindToStrict(this, x => x.BottomProgressBarDarkGlow.Value) + .DisposeWith(dispose); + this.WhenAny(x => x.ProgressPercent) + .BindToStrict(this, x => x.LargeProgressBarTopGlow.Value) + .DisposeWith(dispose); + this.WhenAny(x => x.ProgressPercent) + .BindToStrict(this, x => x.BottomProgressBarBrightGlow1.Value) + .DisposeWith(dispose); + this.WhenAny(x => x.ProgressPercent) + .BindToStrict(this, x => x.BottomProgressBarBrightGlow2.Value) + .DisposeWith(dispose); + this.WhenAny(x => x.ProgressPercent) + .BindToStrict(this, x => x.BottomProgressBar.Value) + .DisposeWith(dispose); + this.WhenAny(x => x.ProgressPercent) + .BindToStrict(this, x => x.BottomProgressBarHighlight.Value) + .DisposeWith(dispose); + + this.WhenAny(x => x.OverhangShadow) + .Select(x => x ? Visibility.Visible : Visibility.Collapsed) + .BindToStrict(this, x => x.OverhangShadowRect.Visibility) + .DisposeWith(dispose); + this.WhenAny(x => x.ShadowMargin) + .DistinctUntilChanged() + .Select(x => x ? new Thickness(6, 0, 6, 0) : new Thickness(0)) + .BindToStrict(this, x => x.OverhangShadowRect.Margin) + .DisposeWith(dispose); + this.WhenAny(x => x.Title) + .BindToStrict(this, x => x.TitleText.Text) + .DisposeWith(dispose); + this.WhenAny(x => x.StatePrefixTitle) + .Select(x => x == null ? Visibility.Visible : Visibility.Collapsed) + .BindToStrict(this, x => x.PrefixSpacerRect.Visibility) + .DisposeWith(dispose); + this.WhenAny(x => x.StatePrefixTitle) + .Select(x => x == null ? Visibility.Collapsed : Visibility.Visible) + .BindToStrict(this, x => x.StatePrefixText.Visibility) + .DisposeWith(dispose); + this.WhenAny(x => x.StatePrefixTitle) + .BindToStrict(this, x => x.StatePrefixText.Text) .DisposeWith(dispose); }); } diff --git a/Wabbajack/Views/MainWindow.xaml.cs b/Wabbajack/Views/MainWindow.xaml.cs index 49184838..da5f95db 100644 --- a/Wabbajack/Views/MainWindow.xaml.cs +++ b/Wabbajack/Views/MainWindow.xaml.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Threading.Tasks; using System.Windows; using MahApps.Metro.Controls; +using Newtonsoft.Json; using Wabbajack.Common; using Wabbajack.Lib.LibCefHelpers; using Application = System.Windows.Application; @@ -48,6 +50,13 @@ namespace Wabbajack }).FireAndForget(); // Load settings + JsonConvert.DefaultSettings = () => new JsonSerializerSettings + { + Converters = new List + { + new PercentJsonConverter() + } + }; if (CLIArguments.NoSettings || !MainSettings.TryLoadTypicalSettings(out var settings)) { _settings = new MainSettings(); diff --git a/Wabbajack/Views/Settings/PerformanceSettingsView.xaml.cs b/Wabbajack/Views/Settings/PerformanceSettingsView.xaml.cs index 4c785ba5..4e208c15 100644 --- a/Wabbajack/Views/Settings/PerformanceSettingsView.xaml.cs +++ b/Wabbajack/Views/Settings/PerformanceSettingsView.xaml.cs @@ -15,6 +15,7 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using ReactiveUI; +using Wabbajack.Common; namespace Wabbajack { @@ -65,7 +66,7 @@ namespace Wabbajack .DisposeWith(disposable); this.BindStrict(this.ViewModel, x => x.TargetUsage, x => x.TargetUsageSpinner.Value) .DisposeWith(disposable); - this.BindStrict(this.ViewModel, x => x.TargetUsage, x => x.TargetUsageSlider.Value) + this.Bind(this.ViewModel, x => x.TargetUsage, x => x.TargetUsageSlider.Value) .DisposeWith(disposable); }); }