From 81b9055da837d5b24bd189c1b769c20a895c264c Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Sun, 12 Oct 2025 22:39:40 +0200 Subject: [PATCH 1/4] use a virtualized FlowLayoutPanel for the rcheevos achievments form some logic fixes remove updateCooldown variable implement resizing --- .../RCheevos.Achievements.cs | 15 ++- .../RCheevos.Leaderboards.cs | 2 +- .../RetroAchievements/RCheevos.cs | 14 +-- .../RCheevosAchievementForm.cs | 67 +++++----- .../RCheevosAchievementListForm.Designer.cs | 27 +++- .../RCheevosAchievementListForm.cs | 118 +++++++++++++----- 6 files changed, 161 insertions(+), 82 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Achievements.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Achievements.cs index 9b1853564de..7a3b848dd75 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Achievements.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Achievements.cs @@ -6,9 +6,7 @@ namespace BizHawk.Client.EmuHawk { public partial class RCheevos { -#if false private readonly RCheevosAchievementListForm _cheevoListForm = new(); -#endif private sealed class CheevoUnlockRequest : RCheevoHttpRequest { @@ -97,6 +95,17 @@ public void LoadImages(IList requests) requests.Add(_badgeLockedRequest); } + public int OrderByKey(Func getCheevoProgress) + { + var ret = 0; + ret += IsHardcoreUnlocked ? 3 : 0; + ret += IsSoftcoreUnlocked ? 2 : 0; + ret += IsPrimed ? 1 : 0; + ret += string.IsNullOrEmpty(getCheevoProgress(ID)) ? 0 : 1; + ret += IsOfficial ? 0 : -10; + return ret; + } + public Cheevo(in LibRCheevos.rc_api_achievement_definition_t cheevo, Func allowUnofficialCheevos) { ID = cheevo.id; @@ -218,4 +227,4 @@ private void OneShotActivateActiveModeCheevos() ActivateCheevos(HardcoreMode); } } -} \ No newline at end of file +} diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Leaderboards.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Leaderboards.cs index 5b1d1a06ced..61f6a8d1960 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Leaderboards.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Leaderboards.cs @@ -89,4 +89,4 @@ public LBoard(in LBoard lboard) } } } -} \ No newline at end of file +} diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs index 7b2434218a0..64f34f1bb1d 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs @@ -188,15 +188,15 @@ private void BuildMenu(ToolStripItemCollection raDropDownItems) _gameInfoForm.Show(); }; raDropDownItems.Add(viewGameInfoItem); -#if false var viewCheevoListItem = new ToolStripMenuItem("View Achievement List"); viewCheevoListItem.Click += (_, _) => { - _cheevoListForm.OnFrameAdvance(HardcoreMode, true); + _cheevoListForm.OnFrameAdvance(HardcoreMode); _cheevoListForm.Show(); }; raDropDownItems.Add(viewCheevoListItem); +#if false var viewLboardListItem = new ToolStripMenuItem("View Leaderboard List"); viewLboardListItem.Click += (_, _) => { @@ -266,8 +266,8 @@ public override void Dispose() _runtime = IntPtr.Zero; Stop(); _gameInfoForm.Dispose(); -#if false _cheevoListForm.Dispose(); +#if false _lboardListForm.Dispose(); #endif _mainForm.QuicksaveLoad -= QuickLoadCallback; @@ -454,8 +454,8 @@ public override void Restart() _lib.rc_runtime_validate_addresses(_runtime, _eventcb, _validatecb); _gameInfoForm.Restart(_gameData.Title, _gameData.TotalCheevoPoints(HardcoreMode), CurrentRichPresence ?? "N/A"); + _cheevoListForm.Restart(_gameData.GameID == 0 ? Array.Empty() : _gameData.CheevoEnumerable, GetCheevoProgress, () => HardcoreMode); #if false - _cheevoListForm.Restart(_gameData.GameID == 0 ? Array.Empty() : _gameData.CheevoEnumerable, GetCheevoProgress); _lboardListForm.Restart(_gameData.GameID == 0 ? Array.Empty() : _gameData.LBoardEnumerable); #endif @@ -705,13 +705,13 @@ public override void OnFrameAdvance() CurrentLboard is null ? "N/A" : $"{CurrentLboard.Description} ({CurrentLboard.Score})", CurrentRichPresence ?? "N/A"); } -#if false - if (_cheevoListForm.IsShown) + if (_cheevoListForm.Visible) { _cheevoListForm.OnFrameAdvance(HardcoreMode); } - if (_lboardListForm.IsShown) +#if false + if (_lboardListForm.Visible) { _lboardListForm.OnFrameAdvance(); } diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementForm.cs index e17ea086734..278dcd54aa5 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementForm.cs @@ -9,40 +9,58 @@ namespace BizHawk.Client.EmuHawk /// public partial class RCheevosAchievementForm : Form { - public int OrderByKey() - { - var ret = 0; - ret += hcUnlockedCheckBox.Checked ? 3 : 0; - ret += scUnlockedCheckBox.Checked ? 2 : 0; - ret += primedCheckBox.Checked ? 1 : 0; - ret += string.IsNullOrEmpty(progressBox.Text) ? 0 : 1; - ret += unofficialCheckBox.Checked ? -10 : 0; - return ret; - } - private Bitmap _unlockedBadge, _lockedBadge; - private readonly RCheevos.Cheevo _cheevo; + private RCheevos.Cheevo _cheevo; private readonly Func _getCheevoProgress; - public RCheevosAchievementForm(RCheevos.Cheevo cheevo, Func getCheevoProgress) + public RCheevosAchievementForm(Func getCheevoProgress) { InitializeComponent(); + _getCheevoProgress = getCheevoProgress; + TopLevel = false; + Show(); + } + + public bool UpdateCheevo(RCheevos.Cheevo cheevo, bool isHardcodeMode) + { + bool updated = _cheevo != cheevo; + + _cheevo = cheevo; + titleBox.Text = cheevo.Title; descriptionBox.Text = cheevo.Description; pointsBox.Text = cheevo.Points.ToString(); - progressBox.Text = getCheevoProgress(cheevo.ID); unofficialCheckBox.Checked = !cheevo.IsOfficial; + + // badges are lazy loaded so we need to make sure they are updated even when _cheevo == cheevo + if (updated) + { + _unlockedBadge = null; + _lockedBadge = null; + cheevoBadgeBox.Image = null; + } + _unlockedBadge ??= UpscaleBadge(cheevo.BadgeUnlocked); + _lockedBadge ??= UpscaleBadge(cheevo.BadgeLocked); + + var badge = _cheevo.IsUnlocked(isHardcodeMode) ? _unlockedBadge : _lockedBadge; + + if (cheevoBadgeBox.Image != badge) + { + cheevoBadgeBox.Image = badge; + updated = true; + } + progressBox.Text = _getCheevoProgress(cheevo.ID); hcUnlockedCheckBox.Checked = cheevo.IsHardcoreUnlocked; primedCheckBox.Checked = cheevo.IsPrimed; scUnlockedCheckBox.Checked = cheevo.IsSoftcoreUnlocked; - _cheevo = cheevo; - _getCheevoProgress = getCheevoProgress; - TopLevel = false; - Show(); + + return updated; } private static Bitmap UpscaleBadge(Bitmap src) { + if (src is null) return null; + var ret = new Bitmap(120, 120); using var g = Graphics.FromImage(ret); g.InterpolationMode = InterpolationMode.NearestNeighbor; @@ -53,18 +71,6 @@ private static Bitmap UpscaleBadge(Bitmap src) public void OnFrameAdvance(bool hardcore) { - var unlockedBadge = _cheevo.BadgeUnlocked; - if (_unlockedBadge is null && unlockedBadge is not null) - { - _unlockedBadge = UpscaleBadge(unlockedBadge); - } - - var lockedBadge = _cheevo.BadgeLocked; - if (_lockedBadge is null && lockedBadge is not null) - { - _lockedBadge = UpscaleBadge(lockedBadge); - } - var badge = _cheevo.IsUnlocked(hardcore) ? _unlockedBadge : _lockedBadge; if (cheevoBadgeBox.Image != badge) @@ -72,7 +78,6 @@ public void OnFrameAdvance(bool hardcore) cheevoBadgeBox.Image = badge; } - pointsBox.Text = _cheevo.Points.ToString(); progressBox.Text = _getCheevoProgress(_cheevo.ID); hcUnlockedCheckBox.Checked = _cheevo.IsHardcoreUnlocked; primedCheckBox.Checked = _cheevo.IsPrimed; diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs index d671e02f943..f76d5860f90 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs @@ -28,27 +28,40 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.vScrollBar1 = new System.Windows.Forms.VScrollBar(); + this.flowLayoutPanel1 = new BizHawk.Client.EmuHawk.VirtualizedFlowLayoutPanel(); this.SuspendLayout(); // + // vScrollBar1 + // + this.vScrollBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.vScrollBar1.Location = new System.Drawing.Point(537, 12); + this.vScrollBar1.Name = "vScrollBar1"; + this.vScrollBar1.Size = new System.Drawing.Size(17, 567); + this.vScrollBar1.SmallChange = 5; + this.vScrollBar1.TabIndex = 1; + this.vScrollBar1.ValueChanged += new System.EventHandler(this.vScrollBar1_ValueChanged); + // // flowLayoutPanel1 // this.flowLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.flowLayoutPanel1.AutoScroll = true; this.flowLayoutPanel1.Location = new System.Drawing.Point(12, 12); this.flowLayoutPanel1.MinimumSize = new System.Drawing.Size(544, 0); this.flowLayoutPanel1.Name = "flowLayoutPanel1"; this.flowLayoutPanel1.Size = new System.Drawing.Size(544, 567); this.flowLayoutPanel1.TabIndex = 0; + this.flowLayoutPanel1.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.flowLayoutPanel1_MouseWheel); // // RCheevosAchievementListForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly; this.ClientSize = new System.Drawing.Size(568, 591); + this.Controls.Add(this.vScrollBar1); this.Controls.Add(this.flowLayoutPanel1); this.MaximizeBox = false; this.MinimizeBox = false; @@ -57,12 +70,14 @@ private void InitializeComponent() this.ShowIcon = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Achievement List"; + this.SizeChanged += new System.EventHandler(this.RCheevosAchievementListForm_SizeChanged); this.ResumeLayout(false); } #endregion - private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private VirtualizedFlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.VScrollBar vScrollBar1; } -} \ No newline at end of file +} diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs index 9dc2db82eb7..c55a1516a9b 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Windows.Forms; +using BizHawk.Common.NumberExtensions; + namespace BizHawk.Client.EmuHawk { /// @@ -9,18 +12,20 @@ namespace BizHawk.Client.EmuHawk /// public partial class RCheevosAchievementListForm : Form { - public bool IsShown { get; private set; } - + private RCheevos.Cheevo[] _cheevos; private RCheevosAchievementForm[] _cheevoForms; - private int _updateCooldown; + private Func _getCheevoProgress; + private Func _isHardcodeMode; + + private readonly int _controlHeight; public RCheevosAchievementListForm() { InitializeComponent(); FormClosing += RCheevosAchievementListForm_FormClosing; - Shown += (_, _) => IsShown = true; _cheevoForms = Array.Empty(); - _updateCooldown = 5; // only update every 5 frames / 12 fps (as this is rather expensive to update) + using var temp = new RCheevosAchievementForm(null); + _controlHeight = temp.Height + temp.Margin.Bottom + temp.Margin.Top; } private void DisposeCheevoForms() @@ -31,51 +36,96 @@ private void DisposeCheevoForms() } } - public void Restart(IEnumerable cheevos, Func getCheevoProgress) + public void Restart(IEnumerable cheevos, Func getCheevoProgress, Func isHardcodeMode) + { + _cheevos = cheevos.ToArray(); + _getCheevoProgress = getCheevoProgress; + _isHardcodeMode = isHardcodeMode; + + RCheevosAchievementListForm_SizeChanged(this, EventArgs.Empty); + vScrollBar1.Value = 0; + vScrollBar1.Maximum = _controlHeight * _cheevos.Length; + } + + public void OnFrameAdvance(bool hardcore) { - flowLayoutPanel1.Controls.Clear(); - DisposeCheevoForms(); - var cheevoForms = new List(); - foreach (var cheevo in cheevos) + _cheevos = _cheevos.OrderByDescending(f => f.OrderByKey(_getCheevoProgress)).ToArray(); + + UpdateForms(); + + foreach (var form in _cheevoForms) { - cheevoForms.Add(new(cheevo, getCheevoProgress)); + form.OnFrameAdvance(hardcore); } - _cheevoForms = cheevoForms.OrderByDescending(f => f.OrderByKey()).ToArray(); - flowLayoutPanel1.Controls.AddRange(_cheevoForms); } - public void OnFrameAdvance(bool hardcore, bool forceUpdate = false) + private void RCheevosAchievementListForm_FormClosing(object sender, FormClosingEventArgs e) { - _updateCooldown--; - if (_updateCooldown == 0 || forceUpdate) + Hide(); + e.Cancel = true; + } + + private void UpdateForms() + { + int firstIndex = vScrollBar1.Value / _controlHeight; + int indexOffset = vScrollBar1.Value % _controlHeight; + while (firstIndex > _cheevos.Length - _cheevoForms.Length) + { + firstIndex--; + indexOffset += _controlHeight; + } + flowLayoutPanel1.SuspendDrawing(); + flowLayoutPanel1.SuspendLayout(); + bool refresh = flowLayoutPanel1.AutoScrollPosition.Y != -indexOffset; + for (int i = 0; i < _cheevoForms.Length; i++) + { + refresh |= _cheevoForms[i].UpdateCheevo(_cheevos[firstIndex + i], _isHardcodeMode()); + } + flowLayoutPanel1.AutoScrollPosition = new Point(0, indexOffset); + flowLayoutPanel1.ResumeLayout(); + flowLayoutPanel1.ResumeDrawing(); + if (refresh) { - _updateCooldown = 5; + Refresh(); + } + } - foreach (var form in _cheevoForms) - { - form.OnFrameAdvance(hardcore); - } + private void vScrollBar1_ValueChanged(object sender, EventArgs e) => UpdateForms(); + + public void flowLayoutPanel1_MouseWheel(object sender, MouseEventArgs e) + { + vScrollBar1.Value = (vScrollBar1.Value - e.Delta).Clamp(vScrollBar1.Minimum, vScrollBar1.Maximum - vScrollBar1.LargeChange + 1); + } - var reorderedForms = _cheevoForms.OrderByDescending(f => f.OrderByKey()).ToArray(); + private int DisplayedItems() + { + return Math.Min((int) Math.Ceiling((double) flowLayoutPanel1.Height / _controlHeight) + 1, _cheevos.Length); + } - for (var i = 0; i < _cheevoForms.Length; i++) + private void RCheevosAchievementListForm_SizeChanged(object sender, EventArgs e) + { + vScrollBar1.LargeChange = vScrollBar1.Size.Height; + if (flowLayoutPanel1.Controls.Count != DisplayedItems()) + { + flowLayoutPanel1.Controls.Clear(); + DisposeCheevoForms(); + _cheevoForms = new RCheevosAchievementForm[DisplayedItems()]; + for (int i = 0; i < DisplayedItems(); i++) { - if (_cheevoForms[i] != reorderedForms[i]) - { - flowLayoutPanel1.Controls.SetChildIndex(reorderedForms[i], i); - } + _cheevoForms[i] = new RCheevosAchievementForm(_getCheevoProgress); } - - _cheevoForms = reorderedForms; + flowLayoutPanel1.Controls.AddRange(_cheevoForms); } + + UpdateForms(); } + } - private void RCheevosAchievementListForm_FormClosing(object sender, FormClosingEventArgs e) + public class VirtualizedFlowLayoutPanel : FlowLayoutPanel + { + protected override void OnMouseWheel(MouseEventArgs e) { - Hide(); - e.Cancel = true; - IsShown = false; + (Parent as RCheevosAchievementListForm)?.flowLayoutPanel1_MouseWheel(this, e); } } } - From 17706ff416024e0ae6d5ab9af3d5e0d9205e3efd Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:38:39 +0200 Subject: [PATCH 2/4] move more logic into virtual FLP class --- .../RCheevosAchievementListForm.Designer.cs | 2 -- .../RCheevosAchievementListForm.cs | 28 ++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs index f76d5860f90..93408bc1306 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.Designer.cs @@ -39,7 +39,6 @@ private void InitializeComponent() this.vScrollBar1.Location = new System.Drawing.Point(537, 12); this.vScrollBar1.Name = "vScrollBar1"; this.vScrollBar1.Size = new System.Drawing.Size(17, 567); - this.vScrollBar1.SmallChange = 5; this.vScrollBar1.TabIndex = 1; this.vScrollBar1.ValueChanged += new System.EventHandler(this.vScrollBar1_ValueChanged); // @@ -54,7 +53,6 @@ private void InitializeComponent() this.flowLayoutPanel1.Name = "flowLayoutPanel1"; this.flowLayoutPanel1.Size = new System.Drawing.Size(544, 567); this.flowLayoutPanel1.TabIndex = 0; - this.flowLayoutPanel1.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.flowLayoutPanel1_MouseWheel); // // RCheevosAchievementListForm // diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs index c55a1516a9b..c761f925b50 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs @@ -26,6 +26,7 @@ public RCheevosAchievementListForm() _cheevoForms = Array.Empty(); using var temp = new RCheevosAchievementForm(null); _controlHeight = temp.Height + temp.Margin.Bottom + temp.Margin.Top; + flowLayoutPanel1.BoundScrollBar = vScrollBar1; } private void DisposeCheevoForms() @@ -92,11 +93,6 @@ private void UpdateForms() private void vScrollBar1_ValueChanged(object sender, EventArgs e) => UpdateForms(); - public void flowLayoutPanel1_MouseWheel(object sender, MouseEventArgs e) - { - vScrollBar1.Value = (vScrollBar1.Value - e.Delta).Clamp(vScrollBar1.Minimum, vScrollBar1.Maximum - vScrollBar1.LargeChange + 1); - } - private int DisplayedItems() { return Math.Min((int) Math.Ceiling((double) flowLayoutPanel1.Height / _controlHeight) + 1, _cheevos.Length); @@ -104,7 +100,6 @@ private int DisplayedItems() private void RCheevosAchievementListForm_SizeChanged(object sender, EventArgs e) { - vScrollBar1.LargeChange = vScrollBar1.Size.Height; if (flowLayoutPanel1.Controls.Count != DisplayedItems()) { flowLayoutPanel1.Controls.Clear(); @@ -123,9 +118,28 @@ private void RCheevosAchievementListForm_SizeChanged(object sender, EventArgs e) public class VirtualizedFlowLayoutPanel : FlowLayoutPanel { + private VScrollBar _boundScrollBar; + public VScrollBar BoundScrollBar + { + get => _boundScrollBar; + set + { + _boundScrollBar = value; + _boundScrollBar.SmallChange = 5; + _boundScrollBar.LargeChange = this.Height; + } + } + + protected override void OnSizeChanged(EventArgs e) + { + if (_boundScrollBar is not null) + _boundScrollBar.LargeChange = this.Height; + base.OnSizeChanged(e); + } + protected override void OnMouseWheel(MouseEventArgs e) { - (Parent as RCheevosAchievementListForm)?.flowLayoutPanel1_MouseWheel(this, e); + BoundScrollBar.Value = (BoundScrollBar.Value - e.Delta).Clamp(BoundScrollBar.Minimum, BoundScrollBar.Maximum - BoundScrollBar.LargeChange + 1); } } } From 8be651233b2f88b1062a813afd72d62f4137e413 Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:47:28 +0200 Subject: [PATCH 3/4] move class into its own file some questionable nullable annotations whatever --- .../RCheevosAchievementListForm.cs | 29 ----------------- .../FLPs/VirtualizedFlowLayoutPanel.cs | 32 +++++++++++++++++++ 2 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 src/BizHawk.WinForms.Controls/FLPs/VirtualizedFlowLayoutPanel.cs diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs index c761f925b50..e372dba4198 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Windows.Forms; -using BizHawk.Common.NumberExtensions; - namespace BizHawk.Client.EmuHawk { /// @@ -115,31 +113,4 @@ private void RCheevosAchievementListForm_SizeChanged(object sender, EventArgs e) UpdateForms(); } } - - public class VirtualizedFlowLayoutPanel : FlowLayoutPanel - { - private VScrollBar _boundScrollBar; - public VScrollBar BoundScrollBar - { - get => _boundScrollBar; - set - { - _boundScrollBar = value; - _boundScrollBar.SmallChange = 5; - _boundScrollBar.LargeChange = this.Height; - } - } - - protected override void OnSizeChanged(EventArgs e) - { - if (_boundScrollBar is not null) - _boundScrollBar.LargeChange = this.Height; - base.OnSizeChanged(e); - } - - protected override void OnMouseWheel(MouseEventArgs e) - { - BoundScrollBar.Value = (BoundScrollBar.Value - e.Delta).Clamp(BoundScrollBar.Minimum, BoundScrollBar.Maximum - BoundScrollBar.LargeChange + 1); - } - } } diff --git a/src/BizHawk.WinForms.Controls/FLPs/VirtualizedFlowLayoutPanel.cs b/src/BizHawk.WinForms.Controls/FLPs/VirtualizedFlowLayoutPanel.cs new file mode 100644 index 00000000000..f5626b348d9 --- /dev/null +++ b/src/BizHawk.WinForms.Controls/FLPs/VirtualizedFlowLayoutPanel.cs @@ -0,0 +1,32 @@ +using System.Windows.Forms; + +using BizHawk.Common.NumberExtensions; + +namespace BizHawk.Client.EmuHawk; + +public class VirtualizedFlowLayoutPanel : FlowLayoutPanel +{ + private VScrollBar? _boundScrollBar; + public VScrollBar BoundScrollBar + { + get => _boundScrollBar!; + set + { + _boundScrollBar = value; + _boundScrollBar.SmallChange = 5; + _boundScrollBar.LargeChange = this.Height; + } + } + + protected override void OnSizeChanged(EventArgs e) + { + if (_boundScrollBar is not null) + _boundScrollBar.LargeChange = this.Height; + base.OnSizeChanged(e); + } + + protected override void OnMouseWheel(MouseEventArgs e) + { + BoundScrollBar.Value = (BoundScrollBar.Value - e.Delta).Clamp(BoundScrollBar.Minimum, BoundScrollBar.Maximum - BoundScrollBar.LargeChange + 1); + } +} From 0ef1930179dd8412da50c78b424ab3312639d6cf Mon Sep 17 00:00:00 2001 From: Morilli <35152647+Morilli@users.noreply.github.com> Date: Mon, 17 Nov 2025 01:43:36 +0100 Subject: [PATCH 4/4] convert the leaderboard form as well --- .../RCheevos.Leaderboards.cs | 2 - .../RetroAchievements/RCheevos.cs | 10 +-- .../RCheevosAchievementListForm.cs | 3 +- .../RCheevosLeaderboardForm.cs | 26 +++++-- .../RCheevosLeaderboardListForm.Designer.cs | 24 ++++-- .../RCheevosLeaderboardListForm.cs | 75 +++++++++++++++---- .../FLPs/VirtualizedFlowLayoutPanel.cs | 5 +- 7 files changed, 107 insertions(+), 38 deletions(-) diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Leaderboards.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Leaderboards.cs index 61f6a8d1960..012118493d2 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Leaderboards.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Leaderboards.cs @@ -4,9 +4,7 @@ namespace BizHawk.Client.EmuHawk { public partial class RCheevos { -#if false private readonly RCheevosLeaderboardListForm _lboardListForm = new(); -#endif private sealed class LboardTriggerRequest : RCheevoHttpRequest { diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs index 64f34f1bb1d..c33f1bac3a9 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.cs @@ -196,15 +196,13 @@ private void BuildMenu(ToolStripItemCollection raDropDownItems) }; raDropDownItems.Add(viewCheevoListItem); -#if false var viewLboardListItem = new ToolStripMenuItem("View Leaderboard List"); viewLboardListItem.Click += (_, _) => { - _lboardListForm.OnFrameAdvance(true); + _lboardListForm.OnFrameAdvance(); _lboardListForm.Show(); }; raDropDownItems.Add(viewLboardListItem); -#endif } protected override void HandleHardcoreModeDisable(string reason) @@ -267,9 +265,7 @@ public override void Dispose() Stop(); _gameInfoForm.Dispose(); _cheevoListForm.Dispose(); -#if false _lboardListForm.Dispose(); -#endif _mainForm.QuicksaveLoad -= QuickLoadCallback; } @@ -455,9 +451,7 @@ public override void Restart() _gameInfoForm.Restart(_gameData.Title, _gameData.TotalCheevoPoints(HardcoreMode), CurrentRichPresence ?? "N/A"); _cheevoListForm.Restart(_gameData.GameID == 0 ? Array.Empty() : _gameData.CheevoEnumerable, GetCheevoProgress, () => HardcoreMode); -#if false _lboardListForm.Restart(_gameData.GameID == 0 ? Array.Empty() : _gameData.LBoardEnumerable); -#endif Update(); @@ -710,12 +704,10 @@ public override void OnFrameAdvance() _cheevoListForm.OnFrameAdvance(HardcoreMode); } -#if false if (_lboardListForm.Visible) { _lboardListForm.OnFrameAdvance(); } -#endif } } } diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs index e372dba4198..195b3d3823e 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosAchievementListForm.cs @@ -11,7 +11,7 @@ namespace BizHawk.Client.EmuHawk public partial class RCheevosAchievementListForm : Form { private RCheevos.Cheevo[] _cheevos; - private RCheevosAchievementForm[] _cheevoForms; + private RCheevosAchievementForm[] _cheevoForms = [ ]; private Func _getCheevoProgress; private Func _isHardcodeMode; @@ -21,7 +21,6 @@ public RCheevosAchievementListForm() { InitializeComponent(); FormClosing += RCheevosAchievementListForm_FormClosing; - _cheevoForms = Array.Empty(); using var temp = new RCheevosAchievementForm(null); _controlHeight = temp.Height + temp.Margin.Bottom + temp.Margin.Top; flowLayoutPanel1.BoundScrollBar = vScrollBar1; diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardForm.cs index ff963d6bc13..4c32af2d0e8 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardForm.cs @@ -7,18 +7,32 @@ namespace BizHawk.Client.EmuHawk /// public partial class RCheevosLeaderboardForm : Form { - private readonly RCheevos.LBoard _lboard; + private RCheevos.LBoard _lboard; - public RCheevosLeaderboardForm(RCheevos.LBoard lboard) + public RCheevosLeaderboardForm() { InitializeComponent(); + TopLevel = false; + Show(); + } + + public bool UpdateLBoard(RCheevos.LBoard lboard) + { + bool updated = _lboard != lboard; + + _lboard = lboard; + titleBox.Text = lboard.Title; descriptionBox.Text = lboard.Description; - scoreBox.Text = lboard.Score; lowerIsBetterBox.Checked = lboard.LowerIsBetter; - _lboard = lboard; - TopLevel = false; - Show(); + + if (scoreBox.Text != _lboard.Score) + { + scoreBox.Text = _lboard.Score; + updated = true; + } + + return updated; } public void OnFrameAdvance() diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.Designer.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.Designer.cs index 77c887dff68..f868cbb8732 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.Designer.cs @@ -28,14 +28,15 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.flowLayoutPanel1 = new BizHawk.Client.EmuHawk.VirtualizedFlowLayoutPanel(); + this.vScrollBar1 = new System.Windows.Forms.VScrollBar(); this.SuspendLayout(); // // flowLayoutPanel1 // this.flowLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.flowLayoutPanel1.AutoScroll = true; this.flowLayoutPanel1.Location = new System.Drawing.Point(12, 12); this.flowLayoutPanel1.MinimumSize = new System.Drawing.Size(544, 0); @@ -43,11 +44,22 @@ private void InitializeComponent() this.flowLayoutPanel1.Size = new System.Drawing.Size(544, 567); this.flowLayoutPanel1.TabIndex = 0; // + // vScrollBar1 + // + this.vScrollBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Right))); + this.vScrollBar1.Location = new System.Drawing.Point(536, 12); + this.vScrollBar1.Name = "vScrollBar1"; + this.vScrollBar1.Size = new System.Drawing.Size(17, 567); + this.vScrollBar1.TabIndex = 2; + this.vScrollBar1.ValueChanged += new System.EventHandler(this.vScrollBar1_ValueChanged); + // // RCheevosLeaderboardListForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(568, 591); + this.Controls.Add(this.vScrollBar1); this.Controls.Add(this.flowLayoutPanel1); this.MaximizeBox = false; this.MinimizeBox = false; @@ -56,12 +68,14 @@ private void InitializeComponent() this.ShowIcon = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Leaderboard List"; + this.SizeChanged += new System.EventHandler(this.RCheevosLeaderboardListForm_SizeChanged); this.ResumeLayout(false); } #endregion - private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private VirtualizedFlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.VScrollBar vScrollBar1; } -} \ No newline at end of file +} diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.cs index c7fd886ede2..8267ce411c7 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevosLeaderboardListForm.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Windows.Forms; @@ -9,18 +10,18 @@ namespace BizHawk.Client.EmuHawk /// public partial class RCheevosLeaderboardListForm : Form { - public bool IsShown { get; private set; } + private RCheevos.LBoard[] _lboards; + private RCheevosLeaderboardForm[] _lboardForms = [ ]; - private RCheevosLeaderboardForm[] _lboardForms; - private int _updateCooldown; + private readonly int _controlHeight; public RCheevosLeaderboardListForm() { InitializeComponent(); FormClosing += RCheevosLeaderboardListForm_FormClosing; - Shown += (_, _) => IsShown = true; - _lboardForms = Array.Empty(); - _updateCooldown = 5; // only update every 5 frames / 12 fps (as this is rather expensive to update) + using var temp = new RCheevosLeaderboardForm(); + _controlHeight = temp.Height + temp.Margin.Bottom + temp.Margin.Top; + flowLayoutPanel1.BoundScrollBar = vScrollBar1; } private void DisposeLboardForms() @@ -33,16 +34,16 @@ private void DisposeLboardForms() public void Restart(IEnumerable lboards) { + _lboards = lboards.ToArray(); flowLayoutPanel1.Controls.Clear(); - DisposeLboardForms(); - _lboardForms = lboards.Select(lboard => new RCheevosLeaderboardForm(lboard)).ToArray(); - flowLayoutPanel1.Controls.AddRange(_lboardForms); + + RCheevosLeaderboardListForm_SizeChanged(this, EventArgs.Empty); + vScrollBar1.Value = 0; + vScrollBar1.Maximum = _controlHeight * _lboards.Length; } - public void OnFrameAdvance(bool forceUpdate = false) + public void OnFrameAdvance() { - if (--_updateCooldown > 0 && !forceUpdate) return; - _updateCooldown = 5; foreach (var lb in _lboardForms) { lb.OnFrameAdvance(); @@ -53,7 +54,55 @@ private void RCheevosLeaderboardListForm_FormClosing(object sender, FormClosingE { Hide(); e.Cancel = true; - IsShown = false; + } + + private void UpdateForms() + { + int firstIndex = vScrollBar1.Value / _controlHeight; + int indexOffset = vScrollBar1.Value % _controlHeight; + while (firstIndex > _lboards.Length - _lboardForms.Length) + { + firstIndex--; + indexOffset += _controlHeight; + } + flowLayoutPanel1.SuspendDrawing(); + flowLayoutPanel1.SuspendLayout(); + bool refresh = flowLayoutPanel1.AutoScrollPosition.Y != -indexOffset; + for (int i = 0; i < _lboardForms.Length; i++) + { + refresh |= _lboardForms[i].UpdateLBoard(_lboards[firstIndex + i]); + } + flowLayoutPanel1.AutoScrollPosition = new Point(0, indexOffset); + flowLayoutPanel1.ResumeLayout(); + flowLayoutPanel1.ResumeDrawing(); + if (refresh) + { + Refresh(); + } + } + + private void vScrollBar1_ValueChanged(object sender, EventArgs e) => UpdateForms(); + + private int DisplayedItems() + { + return Math.Min((int) Math.Ceiling((double) flowLayoutPanel1.Height / _controlHeight) + 1, _lboards.Length); + } + + private void RCheevosLeaderboardListForm_SizeChanged(object sender, EventArgs e) + { + if (flowLayoutPanel1.Controls.Count != DisplayedItems()) + { + flowLayoutPanel1.Controls.Clear(); + DisposeLboardForms(); + _lboardForms = new RCheevosLeaderboardForm[DisplayedItems()]; + for (int i = 0; i < DisplayedItems(); i++) + { + _lboardForms[i] = new RCheevosLeaderboardForm(); + } + flowLayoutPanel1.Controls.AddRange(_lboardForms); + } + + UpdateForms(); } } } diff --git a/src/BizHawk.WinForms.Controls/FLPs/VirtualizedFlowLayoutPanel.cs b/src/BizHawk.WinForms.Controls/FLPs/VirtualizedFlowLayoutPanel.cs index f5626b348d9..697152fa281 100644 --- a/src/BizHawk.WinForms.Controls/FLPs/VirtualizedFlowLayoutPanel.cs +++ b/src/BizHawk.WinForms.Controls/FLPs/VirtualizedFlowLayoutPanel.cs @@ -1,4 +1,5 @@ -using System.Windows.Forms; +using System.ComponentModel; +using System.Windows.Forms; using BizHawk.Common.NumberExtensions; @@ -7,6 +8,8 @@ namespace BizHawk.Client.EmuHawk; public class VirtualizedFlowLayoutPanel : FlowLayoutPanel { private VScrollBar? _boundScrollBar; + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public VScrollBar BoundScrollBar { get => _boundScrollBar!;