diff --git a/PrepStep/Form1.cs b/PrepStep/Form1.cs index 9e5f1d7..af81858 100644 --- a/PrepStep/Form1.cs +++ b/PrepStep/Form1.cs @@ -91,15 +91,23 @@ public partial class Form1 : Form _lblFase.Font = new Font(Font, FontStyle.Bold); headerPanel.Controls.Add(_lblFase); + // Barra di navigazione come footer separato dal contenuto (stesso pattern di + // ProgressForm): linea sottile in cima + sfondo bianco, invece dei bottoni appoggiati + // direttamente sul contenuto/sulla barra di stato senza alcuna divisione visiva. + // Padding destro = 16, lo stesso usato dai pannelli di ogni fase (Padding(16)): prima + // era 12, quindi "Avanti" sporgeva 4px oltre il margine destro del contenuto sopra. + var navContainer = new Panel { Dock = DockStyle.Bottom, Height = 64, BackColor = Color.White }; + var navSeparatore = new Panel + { + Dock = DockStyle.Top, + Height = 1, + BackColor = Color.FromArgb(220, 220, 220), + }; var navPanel = new FlowLayoutPanel { - Dock = DockStyle.Bottom, - // I bottoni si auto-dimensionano al testo (UiStile): l'altezza qui deve stare - // comoda sopra al loro MinimumSize (32) più il padding, altrimenti vengono - // tagliati sul fondo della barra invece di restarci dentro. - Height = 56, + Dock = DockStyle.Fill, FlowDirection = FlowDirection.RightToLeft, - Padding = new Padding(12, 10, 12, 10), + Padding = new Padding(16, 14, 16, 14), }; _btnAvanti.Text = "Avanti ›"; UiStile.Primario(_btnAvanti); @@ -113,6 +121,8 @@ public partial class Form1 : Form UiStile.Arrotonda(_btnIndietro); _btnIndietro.Click += (_, _) => VaiAllaFase(_faseAttuale - 1); navPanel.Controls.Add(_btnIndietro); + navContainer.Controls.Add(navPanel); + navContainer.Controls.Add(navSeparatore); var statusPanel = new Panel { Dock = DockStyle.Bottom, Height = 28, BackColor = SystemColors.ControlLight }; _lblStatus.Dock = DockStyle.Fill; @@ -133,9 +143,12 @@ public partial class Form1 : Form _faseContainer.Controls.Add(panel); } + // Ordine dei Dock.Bottom: l'ultimo aggiunto è il più esterno (il vero bordo inferiore + // della finestra). La barra di stato va tra contenuto e azioni; i bottoni Avanti/Indietro + // restano il footer più esterno, come in ogni wizard/dialog standard di Windows. Controls.Add(_faseContainer); - Controls.Add(navPanel); Controls.Add(statusPanel); + Controls.Add(navContainer); Controls.Add(headerPanel); DragEnter += (_, e) => @@ -180,7 +193,7 @@ public partial class Form1 : Form private void AggiornaAnteprimaAsm() { _lstAnteprimaAsm.Items.Clear(); - _lstAnteprimaAsm.LargeImageList!.Images.Clear(); + _lstAnteprimaAsm.SmallImageList!.Images.Clear(); if (_ultimaCartellaRaw is null || !Directory.Exists(_ultimaCartellaRaw) || _righeCorrenti is null) return; @@ -204,8 +217,8 @@ public partial class Form1 : Form // ponytail: file irraggiungibile (share offline) -> icona generica, non blocca gli altri } - int indice = _lstAnteprimaAsm.LargeImageList.Images.Count; - _lstAnteprimaAsm.LargeImageList.Images.Add(icona?.ToBitmap() ?? SystemIcons.WinLogo.ToBitmap()); + int indice = _lstAnteprimaAsm.SmallImageList.Images.Count; + _lstAnteprimaAsm.SmallImageList.Images.Add(icona?.ToBitmap() ?? SystemIcons.WinLogo.ToBitmap()); icona?.Dispose(); _lstAnteprimaAsm.Items.Add(new ListViewItem(Path.GetFileName(percorso), indice) { Tag = percorso }); } @@ -515,9 +528,12 @@ public partial class Form1 : Form }; top.Controls.Add(lblIstruzioniCompletezza); + // Primario: è l'azione principale di questa fase, non una scelta secondaria — prima + // era uno stile grigio che si confondeva con lo sfondo. _btnCompletezza.Text = "Verifica completezza assiemi"; _btnCompletezza.Margin = new Padding(0, 0, 0, 12); - UiStile.Secondario(_btnCompletezza); + UiStile.Primario(_btnCompletezza); + UiStile.Arrotonda(_btnCompletezza); _btnCompletezza.Click += (_, _) => VerificaCompletezzaAssiemi(); top.Controls.Add(_btnCompletezza); @@ -530,15 +546,28 @@ public partial class Form1 : Form }; top.Controls.Add(lblAnteprimaAsm); - // Dock=Fill invece di una Size fissa: la lista occupa tutto lo spazio verticale - // rimasto sotto le istruzioni, così si vedono più file .asm senza scroll anche a - // finestra ingrandita, invece dei ~160px fissi di prima. - _lstAnteprimaAsm.View = View.LargeIcon; + // View.Details invece di LargeIcon: con codici Pro-E lunghi e simili tra loro + // ("9244218...", "9244228..."), le icone in griglia troncavano tutti i nomi allo stesso + // modo (illeggibili, indistinguibili) e lasciavano un grande vuoto sotto le poche righe. + // Una lista a riga singola mostra il nome per intero, in linea con lo stile delle altre + // griglie dell'app (_gridImport/_gridRaw), restando comunque trascinabile. + _lstAnteprimaAsm.View = View.Details; _lstAnteprimaAsm.MultiSelect = true; - _lstAnteprimaAsm.LargeImageList = new ImageList { ImageSize = new Size(32, 32) }; + _lstAnteprimaAsm.FullRowSelect = true; + _lstAnteprimaAsm.GridLines = false; + _lstAnteprimaAsm.HeaderStyle = ColumnHeaderStyle.Nonclickable; + _lstAnteprimaAsm.SmallImageList = new ImageList { ImageSize = new Size(20, 20) }; + _lstAnteprimaAsm.Columns.Add("File .asm", -2); _lstAnteprimaAsm.Dock = DockStyle.Fill; _lstAnteprimaAsm.Margin = new Padding(0, 4, 0, 12); - _lstAnteprimaAsm.BorderStyle = BorderStyle.FixedSingle; + _lstAnteprimaAsm.BorderStyle = BorderStyle.None; + // La colonna unica segue sempre la larghezza disponibile (niente scroll orizzontale + // né spazio sprecato a destra quando la finestra viene ingrandita). + _lstAnteprimaAsm.Resize += (_, _) => + { + if (_lstAnteprimaAsm.Columns.Count > 0) + _lstAnteprimaAsm.Columns[0].Width = _lstAnteprimaAsm.ClientSize.Width; + }; _lstAnteprimaAsm.ItemDrag += (_, e) => { var percorsi = _lstAnteprimaAsm.SelectedItems.Cast() diff --git a/PrepStep/ProgressForm.cs b/PrepStep/ProgressForm.cs index 85aca1b..349273e 100644 --- a/PrepStep/ProgressForm.cs +++ b/PrepStep/ProgressForm.cs @@ -2,80 +2,137 @@ namespace PrepStep; public sealed class ProgressForm : Form { + private readonly Label _lblTitolo = new(); private readonly Label _lblCartella = new(); - private readonly ProgressBar _progressBar = new(); + private readonly Panel _barraTraccia = new(); + private readonly Panel _barraRiempimento = new(); + private readonly Label _lblPercentuale = new(); private readonly Label _lblConteggio = new(); private readonly Label _lblDettaglio = new(); private readonly Label _lblEta = new(); private readonly Button _btnAnnulla = new(); + private readonly Panel _separatore = new(); + private readonly Panel _piePagina = new(); private readonly ToolTip _tipCartella = new(); + private const int Margine = 24; + // SystemColors.GrayText (~3.9:1 su bianco) è sotto la soglia WCAG AA 4.5:1 per testo + // normale: usiamo un grigio più scuro, leggibile ma comunque secondario. + private static readonly Color GrigioTesto = Color.FromArgb(90, 90, 90); + public event EventHandler? Annullato; public ProgressForm() { - Text = "Aggiornamento indice in corso..."; + Text = "Aggiornamento indice"; FormBorderStyle = FormBorderStyle.FixedDialog; StartPosition = FormStartPosition.CenterParent; MinimizeBox = false; MaximizeBox = false; ControlBox = false; - ClientSize = new Size(460, 190); + BackColor = Color.White; + ClientSize = new Size(480, 270); + + _lblTitolo.Text = "Aggiornamento indice in corso..."; + _lblTitolo.Location = new Point(Margine, 22); + _lblTitolo.AutoSize = true; + _lblTitolo.Font = new Font(Font.FontFamily, 11f, FontStyle.Bold); _lblCartella.Text = "Preparazione..."; - _lblCartella.Location = new Point(16, 16); + _lblCartella.Location = new Point(Margine, 52); // AutoSize=false + AutoEllipsis: un percorso lungo (share di rete) va troncato con "..." // invece di sforare il bordo del dialog, che è FixedDialog e non si può allargare. // Il testo completo resta disponibile nel tooltip (stesso pattern di Form1._lblImportStatus). _lblCartella.AutoSize = false; _lblCartella.AutoEllipsis = true; - _lblCartella.Size = new Size(428, 20); - _lblCartella.Font = new Font(Font, FontStyle.Bold); + _lblCartella.Size = new Size(ClientSize.Width - Margine * 2, 20); + _lblCartella.ForeColor = GrigioTesto; - _progressBar.Location = new Point(16, 44); - _progressBar.Size = new Size(428, 24); - _progressBar.Minimum = 0; - _progressBar.Maximum = 1000; - _progressBar.Style = ProgressBarStyle.Marquee; + // Barra piatta disegnata a mano (traccia grigia + riempimento) invece della ProgressBar + // nativa: quella di Windows è verde e in stile "a blocchi", stona con il blu del resto + // dell'app (UiStile.Primario). Larghezza = margine destro reale meno lo spazio per + // "100%" in grassetto, così barra+percentuale rispettano lo stesso margine destro del + // resto del contenuto (prima finivano ~25px prima, sbilanciate rispetto al margine + // sinistro di 24px). + _barraTraccia.Location = new Point(Margine, 90); + _barraTraccia.Size = new Size(ClientSize.Width - Margine * 2 - 46, 10); + _barraTraccia.BackColor = Color.FromArgb(230, 230, 230); + _barraRiempimento.Location = Point.Empty; + _barraRiempimento.Size = new Size(0, _barraTraccia.Height); + _barraRiempimento.BackColor = UiStile.ColorePrimario; + _barraTraccia.Controls.Add(_barraRiempimento); - _lblConteggio.Location = new Point(16, 80); + _lblPercentuale.AutoSize = true; + _lblPercentuale.Font = new Font(Font, FontStyle.Bold); + _lblPercentuale.TextAlign = ContentAlignment.MiddleRight; + + _lblConteggio.Location = new Point(Margine, 122); _lblConteggio.AutoSize = true; - _lblDettaglio.Location = new Point(16, 104); + _lblDettaglio.Location = new Point(Margine, 146); _lblDettaglio.AutoSize = true; - _lblDettaglio.ForeColor = SystemColors.GrayText; + _lblDettaglio.ForeColor = GrigioTesto; - _lblEta.Location = new Point(16, 128); + _lblEta.Location = new Point(Margine, 174); _lblEta.AutoSize = true; - _lblEta.Font = new Font(Font, FontStyle.Bold); + + // Footer separato dal contenuto (linea + sfondo leggermente diverso) invece del + // bottone appiccicato al bordo inferiore del dialog: ancorato in basso, quindi la + // spaziatura sopra/sotto resta corretta anche se la ClientSize cambia. Altezza footer + // 60px e ClientSize 270 scelti per lasciare sotto l'ultima riga (eta, Bottom≈189) lo + // stesso margine (~20px) usato sopra il titolo, invece dei ~7px sbilanciati di prima. + _separatore.BackColor = Color.FromArgb(230, 230, 230); + _separatore.Size = new Size(ClientSize.Width, 1); + _separatore.Location = new Point(0, ClientSize.Height - 60); + _separatore.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom; + + _piePagina.BackColor = Color.FromArgb(247, 247, 248); + _piePagina.Size = new Size(ClientSize.Width, 59); + _piePagina.Location = new Point(0, ClientSize.Height - 59); + _piePagina.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom; _btnAnnulla.Text = "Annulla"; - _btnAnnulla.Location = new Point(354, 152); - _btnAnnulla.Size = new Size(90, 28); UiStile.Secondario(_btnAnnulla); + UiStile.Arrotonda(_btnAnnulla); _btnAnnulla.Click += (_, e) => Annullato?.Invoke(this, e); + _piePagina.Controls.Add(_btnAnnulla); Controls.AddRange(new Control[] { - _lblCartella, _progressBar, _lblConteggio, _lblDettaglio, _lblEta, _btnAnnulla + _lblTitolo, _lblCartella, _barraTraccia, _lblPercentuale, + _lblConteggio, _lblDettaglio, _lblEta, _separatore, _piePagina }); + + // Posizionato dopo l'aggiunta ai Controls: con AutoSize il Width vero si conosce + // solo a layout avvenuto, non subito dopo la creazione del bottone. + _btnAnnulla.Location = new Point(_piePagina.Width - Margine - _btnAnnulla.Width, + (_piePagina.Height - _btnAnnulla.Height) / 2); + _btnAnnulla.Anchor = AnchorStyles.Right | AnchorStyles.Top; } public void Aggiorna(ScanProgress p) { - _lblCartella.Text = $"Scansione {p.Cartella}..."; + _lblCartella.Text = $"Scansione: {p.Cartella}"; _tipCartella.SetToolTip(_lblCartella, _lblCartella.Text); if (p.PrevistiTotali > 0) { - _progressBar.Style = ProgressBarStyle.Continuous; double frazione = Math.Min(1.0, (double)p.FattiTotali / p.PrevistiTotali); - _progressBar.Value = (int)(frazione * _progressBar.Maximum); + _barraRiempimento.Width = (int)(frazione * _barraTraccia.Width); + _lblPercentuale.Text = $"{frazione * 100:0}%"; } else { - _progressBar.Style = ProgressBarStyle.Marquee; + // ponytail: prima del conteggio totale (poco probabile: richiede la scansione + // precedente salvata nel DB) la barra resta vuota invece di un'animazione + // indeterminata — se serve, aggiungere un Timer che pulsa _barraRiempimento. + _barraRiempimento.Width = 0; + _lblPercentuale.Text = ""; } + // Riallineato al margine destro reale ogni volta: AutoSize aggiorna Width non appena + // Text cambia, quindi la Location va ricalcolata qui (non solo una volta nel costruttore). + _lblPercentuale.Location = new Point( + ClientSize.Width - Margine - _lblPercentuale.Width, _barraTraccia.Top - 3); _lblConteggio.Text = p.PrevistiTotali > 0 ? $"{p.FattiTotali:N0} / ~{p.PrevistiTotali:N0} file" diff --git a/PrepStep/UiStile.cs b/PrepStep/UiStile.cs index 6397bf6..7fb0c57 100644 --- a/PrepStep/UiStile.cs +++ b/PrepStep/UiStile.cs @@ -7,6 +7,10 @@ namespace PrepStep; /// (blu pieno) sia per le azioni secondarie, al posto del bottone grigio di default. internal static class UiStile { + /// Blu del bottone primario, esposto qui per essere riusato anche da elementi + /// non-bottone (es. la barra di avanzamento di ProgressForm) invece di duplicare l'RGB. + public static readonly Color ColorePrimario = Color.FromArgb(0, 99, 177); + // AutoSize+Padding invece di una Size fissa in pixel: il bottone si dimensiona sempre // al proprio testo (a qualunque lunghezza/font), niente più testo che va a capo e viene // tagliato in verticale quando non ci sta nella larghezza indovinata a mano. @@ -25,7 +29,7 @@ internal static class UiStile b.FlatAppearance.BorderSize = 0; b.FlatAppearance.MouseOverBackColor = Color.FromArgb(0, 84, 152); b.FlatAppearance.MouseDownBackColor = Color.FromArgb(0, 70, 130); - b.BackColor = Color.FromArgb(0, 99, 177); + b.BackColor = ColorePrimario; b.ForeColor = Color.White; b.Font = new Font(b.Font, FontStyle.Bold); b.Cursor = Cursors.Hand;