using System.Drawing.Drawing2D; namespace ClinicBackupSync; sealed class UploadProgressBar : Control { readonly System.Windows.Forms.Timer _timer; readonly Font _percentFont; string _fileName = ""; float _target; float _shown; float _shimmer; float _spin; bool _active; public UploadProgressBar() { SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.ResizeRedraw, true); Height = 58; Font = new Font("Segoe UI", 9.5f); _percentFont = new Font(Font, FontStyle.Bold); BackColor = Color.FromArgb(231, 238, 242); _timer = new System.Windows.Forms.Timer { Interval = 32 }; _timer.Tick += (_, _) => Animate(); } public void ShowUpload(string fileName) { if (!_active || !string.Equals(_fileName, fileName, StringComparison.OrdinalIgnoreCase)) { _fileName = fileName; _target = 0; _shown = 0; _shimmer = 0; } _active = true; Visible = true; _timer.Start(); Invalidate(); } public void SetProgress(string fileName, int percent) { _fileName = fileName; _target = Math.Clamp(percent, 0, 100); _active = true; Visible = true; if (!_timer.Enabled) { _timer.Start(); } Invalidate(); } public void HideIdle() { _active = false; _target = 0; _shown = 0; _timer.Stop(); Visible = false; } protected override void Dispose(bool disposing) { if (disposing) { _timer.Dispose(); _percentFont.Dispose(); } base.Dispose(disposing); } void Animate() { _shown += (_target - _shown) * 0.22f; if (Math.Abs(_target - _shown) < 0.15f) { _shown = _target; } _shimmer = (_shimmer + 0.018f) % 1f; _spin = (_spin + 9f) % 360f; Invalidate(); } protected override void OnPaint(PaintEventArgs e) { var graphics = e.Graphics; graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.Clear(BackColor); if (!_active && !Visible) { return; } var percent = (int)Math.Round(_target); var spinner = new Rectangle(18, 10, 16, 16); using (var pen = new Pen(Color.FromArgb(40, 12, 110, 105), 2.2f)) { graphics.DrawEllipse(pen, spinner); } using (var pen = new Pen(Color.FromArgb(12, 110, 105), 2.2f)) { pen.StartCap = LineCap.Round; pen.EndCap = LineCap.Round; graphics.DrawArc(pen, spinner, _spin, 110); } var name = string.IsNullOrWhiteSpace(_fileName) ? "Uploading" : _fileName; var percentText = $"{percent}%"; var percentSize = TextRenderer.MeasureText(percentText, _percentFont); var nameBounds = new Rectangle(40, 8, Math.Max(40, Width - 56 - percentSize.Width), 20); TextRenderer.DrawText(graphics, name, Font, nameBounds, Color.FromArgb(16, 40, 58), TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis | TextFormatFlags.NoPrefix); TextRenderer.DrawText(graphics, percentText, _percentFont, new Rectangle(Width - percentSize.Width - 16, 8, percentSize.Width, 20), Color.FromArgb(12, 110, 105), TextFormatFlags.Right | TextFormatFlags.VerticalCenter); var track = new Rectangle(18, 34, Math.Max(8, Width - 36), 10); FillRound(graphics, track, Color.FromArgb(214, 224, 228)); if (_shown < 1f) { var chunk = Math.Max(36, track.Width / 5); var travel = track.Width + chunk; var x = track.X + (int)(_shimmer * travel) - chunk; using (var clip = Round(track)) { graphics.SetClip(clip); FillRound(graphics, new Rectangle(x, track.Y, chunk, track.Height), Color.FromArgb(12, 110, 105)); graphics.ResetClip(); } return; } var fillWidth = Math.Max(10, (int)(track.Width * (_shown / 100f))); var fill = new Rectangle(track.X, track.Y, Math.Min(track.Width, fillWidth), track.Height); FillRound(graphics, fill, Color.FromArgb(12, 110, 105)); var shineWidth = Math.Max(24, fill.Width / 3); var shineX = fill.X + (int)((_shimmer * (fill.Width + shineWidth)) - shineWidth); using (var clip = Round(fill)) { graphics.SetClip(clip); using var brush = new LinearGradientBrush( new Rectangle(shineX, fill.Y, shineWidth, fill.Height), Color.FromArgb(0, 255, 255, 255), Color.FromArgb(170, 255, 255, 255), LinearGradientMode.Horizontal); brush.SetBlendTriangularShape(0.5f); graphics.FillRectangle(brush, shineX, fill.Y, shineWidth, fill.Height); graphics.ResetClip(); } } static void FillRound(Graphics graphics, Rectangle bounds, Color color) { using var path = Round(bounds); using var brush = new SolidBrush(color); graphics.FillPath(brush, path); } static GraphicsPath Round(Rectangle bounds) { var radius = bounds.Height; var path = new GraphicsPath(); if (bounds.Width <= radius) { path.AddEllipse(bounds); return path; } path.AddArc(bounds.X, bounds.Y, radius, radius, 90, 180); path.AddArc(bounds.Right - radius, bounds.Y, radius, radius, 270, 180); path.CloseFigure(); return path; } }