File: System\Windows\Forms\Controls\Buttons\ButtonInternal\DarkMode\ModernFlatButtonRenderer.cs
Web Access
Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms.VisualStyles;
 
namespace System.Windows.Forms;
 
/// <summary>
///  Renders a modern <see cref="FlatStyle.Flat"/> button with a rectangular body and a single border.
/// </summary>
internal sealed class ModernFlatButtonRenderer : ButtonDarkModeRendererBase
{
    private const int BorderThicknessLogical = 1;
    private const int ContentInsetLogical = 4;
    private const int FocusInsetLogical = 3;
 
    private static readonly Color s_lightBorder = Color.FromArgb(0xD0, 0xD0, 0xD0);
    private static readonly Color s_darkBorder = Color.FromArgb(0x55, 0x55, 0x55);
    private static readonly Color s_lightNormal = Color.FromArgb(0xFB, 0xFB, 0xFB);
    private static readonly Color s_lightDisabled = Color.FromArgb(0xFA, 0xFA, 0xFA);
    private static readonly Color s_darkNormal = Color.FromArgb(0x2D, 0x2D, 0x2D);
    private static readonly Color s_darkDisabled = Color.FromArgb(0x25, 0x25, 0x25);
 
    private static bool IsDark => Application.IsDarkModeEnabled;
 
    private int BorderThickness
        => ScaleBorderThickness(Math.Max(1, Scale(BorderThicknessLogical)));
 
    private protected override Padding PaddingCore => Padding.Empty;
 
    private protected override bool UseModernStateDefaults => true;
 
    internal override Padding GetPreferredSizePadding()
        => new(Math.Max(BorderThickness, Scale(ContentInsetLogical)));
 
    public override Rectangle DrawButtonBackground(
        Graphics graphics,
        Rectangle bounds,
        PushButtonState state,
        bool isDefault,
        bool focused,
        Color backColor)
    {
        GraphicsState? saved = graphics.Save();
        try
        {
            graphics.SmoothingMode = SmoothingMode.Default;
 
            using (var brush = backColor.GetCachedSolidBrushScope())
            {
                graphics.FillRectangle(brush, bounds);
            }
 
            int thickness = BorderThickness;
            if (thickness > 0 && bounds.Width > 0 && bounds.Height > 0)
            {
                Color designBorderColor = isDefault
                    ? (IsDark ? Color.White : SystemColors.Highlight)
                    : (IsDark ? s_darkBorder : s_lightBorder);
 
                using var pen = new Pen(ResolveBorderColor(designBorderColor), thickness)
                {
                    Alignment = PenAlignment.Inset
                };
 
                graphics.DrawRectangle(pen, bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1);
            }
        }
        finally
        {
            if (saved is not null)
            {
                graphics.Restore(saved);
            }
        }
 
        int inset = Math.Max(BorderThickness, Scale(ContentInsetLogical));
        return Rectangle.Inflate(bounds, -inset, -inset);
    }
 
    public override void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault)
    {
        int inset = Scale(FocusInsetLogical);
        Rectangle focusRectangle = Rectangle.Inflate(contentBounds, -inset, -inset);
        if (focusRectangle.Width <= 0 || focusRectangle.Height <= 0)
        {
            return;
        }
 
        Color focusColor = ResolveBorderColor(IsDark ? Color.White : SystemColors.WindowText);
        using var focusPen = new Pen(focusColor) { DashStyle = DashStyle.Dot };
        graphics.DrawRectangle(focusPen, focusRectangle);
    }
 
    public override Color GetTextColor(PushButtonState state, bool isDefault, Color backColor)
    {
        if (state == PushButtonState.Disabled)
        {
            Color preferredForeColor = IsDark
                ? Color.FromArgb(0x88, 0x88, 0x88)
                : Color.FromArgb(0xA0, 0xA0, 0xA0);
 
            return ModernControlColorMath.GetDisabledTextColor(
                preferredForeColor,
                backColor);
        }
 
        return ModernButtonColorMath.GetReadableForeColor(backColor);
    }
 
    public override Color GetBackgroundColor(PushButtonState state, bool isDefault)
    {
        if (state == PushButtonState.Disabled)
        {
            return IsDark ? s_darkDisabled : s_lightDisabled;
        }
 
        if (isDefault)
        {
            return ModernButtonColorMath.GetDefaultButtonColor(state);
        }
 
        Color baseColor = IsDark ? s_darkNormal : s_lightNormal;
 
        return state switch
        {
            PushButtonState.Hot => DeriveHoverColor(baseColor),
            PushButtonState.Pressed => DerivePressedColor(baseColor),
            _ => baseColor
        };
    }
}