Files
prep-step/PrepStep/UiStile.cs
davideandClaude Sonnet 5 ff4e7a71e0 Trasforma la barra di navigazione in una floatbar e uniforma i bottoni Avanti/Indietro
La barra Avanti/Indietro era un footer bianco a tutta larghezza con una
linea di separazione da 1px. Ora è una card bianca staccata su tutti e
4 i lati dalla finestra (UiStile.FloatBar, angoli arrotondati, bordo
sottile), riusata anche in PrtMancantiForm per Chiudi/Apri cartella raw.

Avanti e Indietro avevano dimensioni diverse (testi di lunghezza
diversa, più il grassetto di Primario che richiede più altezza del
testo normale) ed erano visivamente disomogenei (bordo sottile quasi
invisibile su sfondo bianco per Indietro contro il blu pieno di
Avanti). Aggiunti UiStile.SecondarioPieno (variante piena, senza
bordo, stessa costruzione di Primario ma grigio chiaro) e
UiStile.LarghezzaUguale (stessa larghezza e altezza tra due bottoni,
chiamata PRIMA di arrotondare gli angoli: arrotondare e poi
ridimensionare lasciava un bordo residuo della Region vecchia).

Doc aggiornati nello stesso commit: docs/architettura.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:13:34 +02:00

176 lines
7.7 KiB
C#

using System.Drawing.Drawing2D;
namespace PrepStep;
/// <summary>Stile bottoni condiviso tra la form principale e i dialog secondari: piatto con
/// bordo sottile e hover, per un aspetto coerente sia per l'azione primaria di ogni fase
/// (blu pieno) sia per le azioni secondarie, al posto del bottone grigio di default.</summary>
internal static class UiStile
{
/// <summary>Blu del bottone primario, esposto qui per essere riusato anche da elementi
/// non-bottone (es. il colore di selezione delle griglie in Form1) invece di duplicare
/// l'RGB.</summary>
public static readonly Color ColorePrimario = Color.FromArgb(0, 99, 177);
/// <summary>Sfondo/bordo delle card di stato (es. riepilogo Database/cartella di lavoro in
/// fase 1): un grigio-azzurro appena percettibile rispetto al bianco del resto della
/// pagina, sufficiente a separare "stato attuale" da "istruzioni" senza ombre/gradienti.</summary>
public static readonly Color ColoreSfondoCard = Color.FromArgb(247, 249, 251);
public static readonly Color ColoreBordoCard = Color.FromArgb(226, 231, 236);
// 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.
private static void DimensionamentoAutomatico(Button b)
{
b.AutoSize = true;
b.AutoSizeMode = AutoSizeMode.GrowAndShrink;
b.Padding = new Padding(18, 4, 18, 4);
b.MinimumSize = new Size(0, 32);
}
public static void Primario(Button b)
{
DimensionamentoAutomatico(b);
b.FlatStyle = FlatStyle.Flat;
b.FlatAppearance.BorderSize = 0;
b.FlatAppearance.MouseOverBackColor = Color.FromArgb(0, 84, 152);
b.FlatAppearance.MouseDownBackColor = Color.FromArgb(0, 70, 130);
b.BackColor = ColorePrimario;
b.ForeColor = Color.White;
b.Font = new Font(b.Font, FontStyle.Bold);
b.Cursor = Cursors.Hand;
}
public static void Secondario(Button b)
{
DimensionamentoAutomatico(b);
b.FlatStyle = FlatStyle.Flat;
b.FlatAppearance.BorderColor = Color.FromArgb(190, 195, 200);
b.FlatAppearance.BorderSize = 1;
b.FlatAppearance.MouseOverBackColor = Color.FromArgb(230, 238, 245);
b.FlatAppearance.MouseDownBackColor = Color.FromArgb(213, 226, 238);
b.BackColor = Color.White;
b.Cursor = Cursors.Hand;
}
/// <summary>Come Secondario, ma pieno invece che a bordo: sulle card bianche delle floatbar
/// (vedi FloatBar) il bordo sottile di Secondario è quasi invisibile su sfondo bianco, e il
/// bottone risulterebbe visivamente diverso dal Primario pieno accanto — stessa costruzione
/// (nessun bordo, riempimento pieno), solo grigio chiaro invece che blu.</summary>
public static void SecondarioPieno(Button b)
{
DimensionamentoAutomatico(b);
b.FlatStyle = FlatStyle.Flat;
b.FlatAppearance.BorderSize = 0;
b.FlatAppearance.MouseOverBackColor = Color.FromArgb(230, 233, 237);
b.FlatAppearance.MouseDownBackColor = Color.FromArgb(213, 217, 222);
b.BackColor = Color.FromArgb(241, 243, 245);
b.Cursor = Cursors.Hand;
}
/// <summary>Arrotonda gli angoli del controllo (bottone o card). Si aggancia a SizeChanged
/// invece di calcolare la Region una volta sola: con AutoSize la dimensione finale si
/// conosce solo a layout avvenuto (dopo Text/Padding), non subito dopo la creazione del
/// controllo.</summary>
public static void Arrotonda(Control c, int raggio = 6)
{
c.SizeChanged += (_, _) => AggiornaRegione(c, raggio);
AggiornaRegione(c, raggio);
}
private static void AggiornaRegione(Control c, int raggio)
{
if (c.Width <= 0 || c.Height <= 0)
return;
int d = raggio * 2;
var rect = new Rectangle(Point.Empty, c.Size);
using var percorso = new GraphicsPath();
percorso.AddArc(rect.X, rect.Y, d, d, 180, 90);
percorso.AddArc(rect.Right - d, rect.Y, d, d, 270, 90);
percorso.AddArc(rect.Right - d, rect.Bottom - d, d, d, 0, 90);
percorso.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
percorso.CloseFigure();
c.Region?.Dispose();
c.Region = new Region(percorso);
}
/// <summary>Card di stato: sfondo/bordo leggermente distinti dalla pagina, angoli arrotondati,
/// padding interno. Il bordo va disegnato a mano (Paint) perché Region ritaglia il
/// controllo ma non lascia spazio a un Border WinForms nativo sugli angoli arrotondati.</summary>
public static Panel Card()
{
var card = new Panel
{
Dock = DockStyle.Top,
AutoSize = true,
AutoSizeMode = AutoSizeMode.GrowAndShrink,
BackColor = ColoreSfondoCard,
Padding = new Padding(18, 16, 18, 16),
Margin = new Padding(0, 0, 0, 16),
};
card.Paint += (_, e) =>
{
using var penna = new Pen(ColoreBordoCard);
e.Graphics.DrawRectangle(penna, 0, 0, card.Width - 1, card.Height - 1);
};
Arrotonda(card, 8);
return card;
}
/// <summary>Barra di bottoni "flottante" (Avanti/Indietro, Apri cartella raw/Chiudi, ecc.):
/// una card bianca staccata su tutti e 4 i lati dal bordo della finestra — non un footer a
/// tutta larghezza — con angoli arrotondati e bordo sottile, sospesa sopra lo sfondo grigio
/// della pagina invece che ancorata al bordo. Lo sfondo del contenitore esterno deve
/// combaciare con quello di default della Form (nessun BackColor impostato altrove:
/// SystemColors.Control) perché il "distacco" si veda solo nel margine attorno alla card,
/// non in una fascia di colore diverso.</summary>
public static (Panel Contenitore, FlowLayoutPanel Bottoni) FloatBar()
{
// Altezza minima per non tagliare il contenuto: bottone (MinimumSize.Height = 32) +
// padding verticale della riga bottoni (12+12) + padding verticale del contenitore
// esterno (10+16) = 82, con un po' di margine in più.
var contenitore = new Panel
{
Dock = DockStyle.Bottom,
Height = 88,
BackColor = SystemColors.Control,
Padding = new Padding(16, 10, 16, 16),
};
var card = new Panel { Dock = DockStyle.Fill, BackColor = Color.White };
card.Paint += (_, e) =>
{
using var penna = new Pen(ColoreBordoCard);
e.Graphics.DrawRectangle(penna, 0, 0, card.Width - 1, card.Height - 1);
};
Arrotonda(card, 14);
var bottoni = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.RightToLeft,
Padding = new Padding(16, 12, 16, 12),
};
card.Controls.Add(bottoni);
contenitore.Controls.Add(card);
return (contenitore, bottoni);
}
/// <summary>Forza le stesse dimensioni per due bottoni della stessa barra (es. Avanti/
/// Indietro): AutoSize li dimensiona in base al proprio testo, e con testi di lunghezza
/// diversa (o solo uno dei due in grassetto, come Primario/Secondario) risulterebbero
/// disallineati sia in larghezza che in altezza — un dettaglio che un designer non
/// lascerebbe passare. Va chiamato dopo aver impostato Text/Font su entrambi.</summary>
public static void LarghezzaUguale(params Button[] bottoni)
{
int larghezza = bottoni.Max(b => b.PreferredSize.Width);
int altezza = bottoni.Max(b => b.PreferredSize.Height);
foreach (var b in bottoni)
b.MinimumSize = new Size(larghezza, altezza);
}
}