using System.Drawing.Drawing2D; namespace PrepStep; /// 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. internal static class UiStile { /// 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. public static readonly Color ColorePrimario = Color.FromArgb(0, 99, 177); /// 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. 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; } /// 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. 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; } /// 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. 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); } /// 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. 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; } /// 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. 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); } /// 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. 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); } }