From 1eddccbe633ae4477ce1398366b533f4042fc564 Mon Sep 17 00:00:00 2001 From: Allamagoosa <36551512+dmatlock171@users.noreply.github.com> Date: Thu, 20 Aug 2026 17:13:02 -0700 Subject: [PATCH] Bound concurrency by the hard limit and say what the machine will do Taking the alternative offered in review. The spinner's maximum is the flat hard limit now, the same number in both UIs and on every machine, and machine capability is applied only at the point of use. Bounding the control by capability made it lie in two directions at once. Below the stored value, a two-way spinner coerces its display down to its maximum and writes that back, so opening the panel on a smaller machine overwrote an 8 chosen on a larger one. Raising the bound to meet the stored value fixed that but left a stored 8 displaying 8 on a two-core box that will only ever run 2. The two UIs had also drifted apart: Avalonia bound Maximum and ratcheted down, WinForms set it once in the constructor, so within a session you could lower and re-raise in one and not the other. What is left is the gap between what the setting says and what runs, and that is now stated rather than hidden. Chardonnay shows "(2 on this machine)" beside the spinner, in the spare column the settings row already had. Classic's settings table is full at three columns with no width to spare, so it says the same thing in the control's tooltip. --- .../Views/ProcessQueueControl.axaml | 12 ++++++++ .../ProcessQueue/ProcessQueueViewModel.cs | 30 ++++++++++++++----- .../ProcessQueue/ProcessQueueControl.cs | 23 ++++++++++++++ 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml index bbb66db3..07b70824 100644 --- a/Source/LibationAvalonia/Views/ProcessQueueControl.axaml +++ b/Source/LibationAvalonia/Views/ProcessQueueControl.axaml @@ -82,6 +82,18 @@ Maximum="{Binding MaxAllowedConcurrentDownloads}" Increment="1" FormatString="0" /> + + Configuration.MinConcurrentDownloads; /// - /// The spinner's upper bound: what this machine can usefully manage, but never below what is - /// already stored. A spinner whose maximum sits under the stored value coerces its displayed - /// value down to the maximum and, being two-way, writes that back - which would overwrite an 8 - /// chosen on a larger machine with a 2 just for opening the panel on a smaller one. Raising the - /// bound to meet the stored value leaves the setting alone; lowering it is still the user's to do. + /// The spinner's upper bound: the flat hard limit, so both UIs agree and the bound is the same + /// number on every machine. Bounding it by machine capability instead makes the control lie in two + /// directions at once - a spinner whose maximum sits under the stored value coerces its display + /// down and, being two-way, writes that back, so opening the panel on a smaller machine would + /// overwrite an 8 chosen on a larger one; while raising the bound to meet the stored value leaves + /// a stored 8 showing 8 on a two-core box that will only ever run 2. Capability is applied where + /// it actually bites, in , and + /// says so on screen rather than leaving the two to disagree in silence. /// - public int MaxAllowedConcurrentDownloads - => Math.Max(Configuration.MaxAllowedConcurrentDownloads, MaxConcurrentDownloads); + public int MaxAllowedConcurrentDownloads => Configuration.ConcurrentDownloadsHardLimit; + + /// + /// Sits beside the spinner when this machine cannot deliver the number the user chose, and is + /// null when it can. Closes the gap left by bounding the control at the hard limit: the setting + /// keeps saying what was asked for, and this says what will happen. + /// + public string? ConcurrencyHint + => EffectiveConcurrentDownloads < MaxConcurrentDownloads + ? $"({EffectiveConcurrentDownloads} on this machine)" + : null; public string? RunningTime { get => field; set => RaiseAndSetIfChanged(ref field, value); } public bool ProgressBarVisible { get => field; set => RaiseAndSetIfChanged(ref field, value); } public bool AnyCompleted => CompletedCount > 0; diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index 6fc3dd99..eda196f5 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -41,6 +41,9 @@ internal partial class ProcessQueueControl : UserControl autoScrollChk.CheckedChanged += (s, e) => ViewModel.AutoScrollQueue = autoScrollChk.Checked; concurrencyNum.Minimum = ViewModel.MinConcurrentDownloads; + // The flat hard limit, not this machine's capability - the same bound Chardonnay uses. A + // maximum below the stored value would coerce the display down and write it back, losing an 8 + // chosen elsewhere just for opening the panel here. concurrencyNum.Maximum = ViewModel.MaxAllowedConcurrentDownloads; concurrencyNum.ValueChanged += (s, e) => ViewModel.MaxConcurrentDownloads = (int)concurrencyNum.Value; ProcessQueue_PropertyChanged(this, new PropertyChangedEventArgs(null)); @@ -141,8 +144,28 @@ internal partial class ProcessQueueControl : UserControl autoScrollChk.Checked = ViewModel.AutoScrollQueue; if (e.PropertyName is null or nameof(ViewModel.MaxConcurrentDownloads)) concurrencyNum.Value = ViewModel.MaxConcurrentDownloads; + if (e.PropertyName is null or nameof(ViewModel.ConcurrencyHint)) + setConcurrencyHint(); + + // The settings table is full at three columns and the panel has no width to spare, so this + // says what Chardonnay says in its spare column, in a tooltip instead of another label. + void setConcurrencyHint() + { + const string what = "How many books download and decrypt at the same time. 1 downloads one at a time."; + concurrencyHintToolTip.SetToolTip( + concurrencyNum, + ViewModel.ConcurrencyHint is string hint + ? $"{what}\r\nOnly {hint.Trim('(', ')')} - this machine has fewer processors to decrypt them." + : what); + } } + /// + /// Carries . Built here rather than in the + /// designer to keep the generated file out of the diff. + /// + private readonly ToolTip concurrencyHintToolTip = new(); + /// /// View notified the model that a button was clicked ///