feat(ui): add pagination to best-fit viewer

Replace single scrollable grid with fixed 5x2 pages (10 items per page).
Add prev/next buttons and page label. Support Left/Right and PageUp/
PageDown keyboard navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 20:29:56 -04:00
parent 183d169cc1
commit 4c1ac418a0
2 changed files with 133 additions and 37 deletions

View File

@@ -14,11 +14,15 @@ namespace OpenNest.Forms
private void InitializeComponent()
{
this.gridPanel = new System.Windows.Forms.TableLayoutPanel();
this.navPanel = new System.Windows.Forms.Panel();
this.btnPrev = new System.Windows.Forms.Button();
this.btnNext = new System.Windows.Forms.Button();
this.lblPage = new System.Windows.Forms.Label();
this.navPanel.SuspendLayout();
this.SuspendLayout();
//
// gridPanel
//
this.gridPanel.AutoScroll = true;
this.gridPanel.ColumnCount = 5;
this.gridPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.gridPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F));
@@ -28,24 +32,72 @@ namespace OpenNest.Forms
this.gridPanel.Dock = System.Windows.Forms.DockStyle.Fill;
this.gridPanel.Location = new System.Drawing.Point(0, 0);
this.gridPanel.Name = "gridPanel";
this.gridPanel.RowCount = 1;
this.gridPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.gridPanel.Size = new System.Drawing.Size(1200, 800);
this.gridPanel.RowCount = 2;
this.gridPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.gridPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.gridPanel.Size = new System.Drawing.Size(1200, 764);
this.gridPanel.TabIndex = 0;
//
// navPanel
//
this.navPanel.Controls.Add(this.btnPrev);
this.navPanel.Controls.Add(this.lblPage);
this.navPanel.Controls.Add(this.btnNext);
this.navPanel.Dock = System.Windows.Forms.DockStyle.Bottom;
this.navPanel.Location = new System.Drawing.Point(0, 764);
this.navPanel.Name = "navPanel";
this.navPanel.Size = new System.Drawing.Size(1200, 36);
this.navPanel.TabIndex = 1;
//
// btnPrev
//
this.btnPrev.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnPrev.Location = new System.Drawing.Point(4, 4);
this.btnPrev.Name = "btnPrev";
this.btnPrev.Size = new System.Drawing.Size(80, 28);
this.btnPrev.TabIndex = 0;
this.btnPrev.Text = "< Prev";
this.btnPrev.Click += new System.EventHandler(this.btnPrev_Click);
//
// lblPage
//
this.lblPage.Dock = System.Windows.Forms.DockStyle.Fill;
this.lblPage.Name = "lblPage";
this.lblPage.Size = new System.Drawing.Size(1200, 36);
this.lblPage.TabIndex = 1;
this.lblPage.Text = "Page 1 / 1";
this.lblPage.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// btnNext
//
this.btnNext.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
this.btnNext.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.btnNext.Location = new System.Drawing.Point(1116, 4);
this.btnNext.Name = "btnNext";
this.btnNext.Size = new System.Drawing.Size(80, 28);
this.btnNext.TabIndex = 2;
this.btnNext.Text = "Next >";
this.btnNext.Click += new System.EventHandler(this.btnNext_Click);
//
// BestFitViewerForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1200, 800);
this.Controls.Add(this.gridPanel);
this.Controls.Add(this.navPanel);
this.KeyPreview = true;
this.Name = "BestFitViewerForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Best-Fit Viewer";
this.navPanel.ResumeLayout(false);
this.ResumeLayout(false);
}
private System.Windows.Forms.TableLayoutPanel gridPanel;
private System.Windows.Forms.Panel navPanel;
private System.Windows.Forms.Button btnPrev;
private System.Windows.Forms.Button btnNext;
private System.Windows.Forms.Label lblPage;
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
@@ -9,7 +10,8 @@ namespace OpenNest.Forms
public partial class BestFitViewerForm : Form
{
private const int Columns = 5;
private const int RowHeight = 300;
private const int Rows = 2;
private const int ItemsPerPage = Columns * Rows;
private const int MaxResults = 50;
private static readonly Color KeptColor = Color.FromArgb(0, 0, 100);
@@ -18,6 +20,14 @@ namespace OpenNest.Forms
private readonly Drawing drawing;
private readonly Plate plate;
private List<BestFitResult> results;
private int totalResults;
private int keptCount;
private double computeSeconds;
private double totalSeconds;
private int currentPage;
private int pageCount;
public BestFitResult SelectedResult { get; private set; }
public BestFitViewerForm(Drawing drawing, Plate plate)
@@ -33,7 +43,8 @@ namespace OpenNest.Forms
Cursor = Cursors.WaitCursor;
try
{
PopulateGrid(drawing, plate);
ComputeResults();
ShowPage(0);
}
finally
{
@@ -48,51 +59,84 @@ namespace OpenNest.Forms
Close();
return true;
}
if (keyData == Keys.Left || keyData == Keys.PageUp)
{
NavigatePage(-1);
return true;
}
if (keyData == Keys.Right || keyData == Keys.PageDown)
{
NavigatePage(1);
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
private void PopulateGrid(Drawing drawing, Plate plate)
private void ComputeResults()
{
var sw = Stopwatch.StartNew();
var results = BestFitCache.GetOrCompute(
var all = BestFitCache.GetOrCompute(
drawing, plate.Size.Width, plate.Size.Length, plate.PartSpacing);
var findMs = sw.ElapsedMilliseconds;
var total = results.Count;
var kept = 0;
computeSeconds = sw.ElapsedMilliseconds / 1000.0;
totalResults = all.Count;
keptCount = 0;
foreach (var r in results)
foreach (var r in all)
{
if (r.Keep) kept++;
if (r.Keep) keptCount++;
}
var count = System.Math.Min(total, MaxResults);
var rows = (int)System.Math.Ceiling(count / (double)Columns);
gridPanel.RowCount = rows;
gridPanel.RowStyles.Clear();
for (var i = 0; i < rows; i++)
gridPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, RowHeight));
gridPanel.SuspendLayout();
try
{
for (var i = 0; i < count; i++)
{
var result = results[i];
var cell = CreateCell(result, drawing, i + 1);
gridPanel.Controls.Add(cell, i % Columns, i / Columns);
}
}
finally
{
gridPanel.ResumeLayout(true);
}
var count = System.Math.Min(totalResults, MaxResults);
results = all.GetRange(0, count);
pageCount = System.Math.Max(1, (int)System.Math.Ceiling(count / (double)ItemsPerPage));
sw.Stop();
Text = string.Format("Best-Fit Viewer — {0} candidates ({1} kept) | Compute: {2:F1}s | Total: {3:F1}s | Showing {4}",
total, kept, findMs / 1000.0, sw.Elapsed.TotalSeconds, count);
totalSeconds = sw.Elapsed.TotalSeconds;
}
private void ShowPage(int page)
{
currentPage = page;
var start = page * ItemsPerPage;
var count = System.Math.Min(ItemsPerPage, results.Count - start);
gridPanel.SuspendLayout();
gridPanel.Controls.Clear();
gridPanel.RowCount = Rows;
gridPanel.RowStyles.Clear();
for (var i = 0; i < Rows; i++)
gridPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100f / Rows));
for (var i = 0; i < count; i++)
{
var result = results[start + i];
var cell = CreateCell(result, drawing, start + i + 1);
gridPanel.Controls.Add(cell, i % Columns, i / Columns);
}
gridPanel.ResumeLayout(true);
btnPrev.Enabled = currentPage > 0;
btnNext.Enabled = currentPage < pageCount - 1;
lblPage.Text = string.Format("Page {0} / {1}", currentPage + 1, pageCount);
Text = string.Format("Best-Fit Viewer — {0} candidates ({1} kept) | Compute: {2:F1}s | Total: {3:F1}s | Showing {4}-{5} of {6}",
totalResults, keptCount, computeSeconds, totalSeconds,
start + 1, start + count, results.Count);
}
private void btnPrev_Click(object sender, System.EventArgs e) => NavigatePage(-1);
private void btnNext_Click(object sender, System.EventArgs e) => NavigatePage(1);
private void NavigatePage(int delta)
{
var newPage = currentPage + delta;
if (newPage >= 0 && newPage < pageCount)
ShowPage(newPage);
}
private BestFitCell CreateCell(BestFitResult result, Drawing drawing, int rank)