c8502bfa创建于 2025年6月25日历史提交
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CFixer
{
    /// <summary>
    /// Handles navigation button highlighting for a set of UI buttons.
    /// </summary>
    public class NavigationHandler
    {
        // List of all buttons managed by the navigation handler
        private readonly List<Button> _buttons;

        // Color used for the background of the active button
        private readonly Color _activeBackgroundColor = Color.FromArgb(180, 150, 200, 240);

        // Color used for the background of inactive buttons (same as form)
        private readonly Color _inactiveBackgroundColor = Color.FromArgb(103, 103, 103);

        // Color used for the border of the active button
        private readonly Color _activeBorderColor = Color.FromArgb(255, 120, 170, 210);

        /// <summary>
        /// Event fired when a navigation button is clicked.
        /// </summary>
        public event Action<Button> NavigationButtonClicked;

        /// <summary>
        /// Initializes the handler with the buttons that should be managed.
        /// </summary>
        /// <param name="buttons">The buttons to include in the navigation group.</param>
        public NavigationHandler(params Button[] buttons)
        {
            _buttons = new List<Button>(buttons);

            foreach (var button in _buttons)
            {
                button.Click += OnButtonClick;
            }
        }

        private void OnButtonClick(object sender, EventArgs e)
        {
            if (sender is Button clickedButton)
            {
                SetActive(clickedButton);
                clickedButton.FindForm().ActiveControl = null;  // Remove focus so no extra border is drawn
                NavigationButtonClicked?.Invoke(clickedButton);
            }
        }

        /// <summary>
        /// Sets the given button as active and updates the appearance of all buttons.
        /// </summary>
        /// <param name="activeButton">The button to mark as active.</param>
        public void SetActive(Button activeButton)
        {
            foreach (var button in _buttons)
            {
                // Skip GitHub button to avoid conflicts with its custom behavior
                if (button.Name.Equals("btnGitHub", StringComparison.OrdinalIgnoreCase))
                    continue;

                bool isActive = button == activeButton;

                if (isActive)
                {
                    button.BackColor = _activeBackgroundColor;
                    button.FlatAppearance.BorderColor = _activeBorderColor;
                    button.FlatAppearance.BorderSize = 1;
                }
                else
                {
                    button.BackColor = _inactiveBackgroundColor;
                    button.FlatAppearance.BorderSize = 0;
                }

                button.ForeColor = Color.WhiteSmoke;
            }
        }

        /// <summary>
        /// Loads and assigns icons to buttons asynchronously based on their names.
        /// </summary>
        /// <param name="iconFolder">The relative folder path where the icon files are located. Defaults to "icons". Each button's icon file name is
        /// derived from the button's name by removing the "btn" prefix, converting the remainder to lowercase, and
        /// appending ".png".</param>
        public async Task LoadNavigationIcons(string iconFolder = "icons")
        {
            string basePath = AppDomain.CurrentDomain.BaseDirectory;
            string fullIconPath = Path.Combine(basePath, iconFolder);
            if (!Directory.Exists(fullIconPath)) return;

            // Create graphics context to get system DPI scaling (default is 96 DPI)
            using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
            {
                float dpiScale = g.DpiX / 96f;
                int defaultIconSize = (int)(32 * dpiScale);     // Scale 32px base size with DPI
                int githubIconSize = (int)(42 * dpiScale);       // Special size for GitHub button

                foreach (var button in _buttons)
                {
                    // Convert button name like "btnHome" > "home.png"
                    string buttonName = button.Name.ToLower();
                    string fileName;

                    if (buttonName == "btngithub")
                        fileName = "github.png";
                    else if (buttonName.StartsWith("btn"))
                        fileName = buttonName.Replace("btn", "") + ".png";
                    else
                        continue;

                    string filePath = Path.Combine(fullIconPath, fileName);

                    if (!File.Exists(filePath))
                        continue;

                    try
                    {
                        // Read file bytes asynchronously off the UI thread
                        byte[] imageData = await Task.Run(() => File.ReadAllBytes(filePath));

                        using (var ms = new MemoryStream(imageData))
                        using (Image original = Image.FromStream(ms))
                        {
                            int iconSize = buttonName == "btngithub" ? githubIconSize : defaultIconSize;

                            Bitmap resized = new Bitmap(iconSize, iconSize);
                            try
                            {
                                using (Graphics gr = Graphics.FromImage(resized))
                                {
                                    gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                                    gr.DrawImage(original, 0, 0, iconSize, iconSize);
                                }

                                // Set button image
                                button.Image = resized;
                                button.ImageAlign = ContentAlignment.TopCenter;
                                button.TextAlign = ContentAlignment.BottomCenter;
                            }
                            catch
                            {
                                resized.Dispose(); // Cleanup if drawing fails
                                throw;
                            }
                        }
                    }
                    catch
                    {
                        // Optional: log or ignore loading errors
                    }
                }
            }
        }
    }
}