File: System\Windows\Forms\Controls\Buttons\ButtonInternal\DarkMode\FlatButtonDarkModeRenderer.cs
Web Access
Project: src\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;
using static System.Windows.Forms.DarkModeButtonColors;
 
namespace System.Windows.Forms;
 
/// <summary>
///  Flat‑style button renderer that, for the moment, mimics the Win32/Dark‑mode
///  renderer bit‑for‑bit so that we can switch implementations without any
///  visual delta.  Once the design team decides on a new look we can diverge
///  again.
/// </summary>
internal sealed class FlatButtonDarkModeRenderer : ButtonDarkModeRendererBase
{
    private const int FocusIndicatorInflate = -3;
    private const int CornerRadius = 6;
    private static readonly Size s_corner = new(CornerRadius, CornerRadius);
 
    private protected override Padding PaddingCore { get; } = new(0);
 
    public override Rectangle DrawButtonBackground(
        Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault)
    {
        // fill background
        using var back = GetBackgroundColor(state, isDefault).GetCachedSolidBrushScope();
        graphics.FillRectangle(back, bounds);
 
        // draw border identical to Win32
        DrawButtonBorder(graphics, bounds, state, isDefault);
 
        // return inner content area (border + 1 px system padding)
        return Rectangle.Inflate(bounds, -3, -3);
    }
 
    public override void DrawFocusIndicator(Graphics g, Rectangle contentBounds, bool isDefault)
    {
        Rectangle focus = Rectangle.Inflate(contentBounds, FocusIndicatorInflate, FocusIndicatorInflate);
 
        Color focusBackColor = isDefault
            ? DefaultColors.AcceptFocusIndicatorBackColor
            : DefaultColors.FocusIndicatorBackColor;
 
        ControlPaint.DrawFocusRectangle(
            g,
            focus,
            DefaultColors.FocusBorderColor,
            focusBackColor);
    }
 
    public override Color GetTextColor(PushButtonState state, bool isDefault) =>
        state == PushButtonState.Disabled
            ? DefaultColors.DisabledTextColor
            : isDefault
                ? DefaultColors.AcceptButtonTextColor
                : DefaultColors.NormalTextColor;
 
    private static Color GetBackgroundColor(PushButtonState state, bool isDefault) =>
        isDefault
            ? state switch
            {
                PushButtonState.Normal => DefaultColors.StandardBackColor,
                PushButtonState.Hot => DefaultColors.HoverBackColor,
                PushButtonState.Pressed => DefaultColors.PressedBackColor,
                PushButtonState.Disabled => DefaultColors.DisabledBackColor,
                _ => DefaultColors.StandardBackColor
            }
            : state switch
            {
                PushButtonState.Normal => DefaultColors.StandardBackColor,
                PushButtonState.Hot => DefaultColors.HoverBackColor,
                PushButtonState.Pressed => DefaultColors.PressedBackColor,
                PushButtonState.Disabled => DefaultColors.DisabledBackColor,
                _ => DefaultColors.StandardBackColor
            };
 
    private static void DrawButtonBorder(Graphics g, Rectangle bounds, PushButtonState state, bool isDefault)
    {
        g.SmoothingMode = SmoothingMode.AntiAlias;
 
        // Win32 draws its stroke fully *inside* the control → inset by 1 px
        Rectangle outer = Rectangle.Inflate(bounds, -1, -1);
 
        DrawSingleBorder(g, outer, GetBorderColor(state));
 
        // Default button gets a second 1‑px border one pixel further inside
        if (isDefault)
        {
            Rectangle inner = Rectangle.Inflate(outer, -1, -1);
            DrawSingleBorder(g, inner, DefaultColors.AcceptFocusIndicatorBackColor);
        }
    }
 
    private static void DrawSingleBorder(Graphics g, Rectangle rect, Color color)
    {
        g.SmoothingMode = SmoothingMode.AntiAlias;
 
        using var path = new GraphicsPath();
        path.AddRoundedRectangle(rect, s_corner);
 
        // a 1‑px stroke, aligned *inside*, is exactly what Win32 draws
        using var pen = new Pen(color) { Alignment = PenAlignment.Inset };
        g.DrawPath(pen, path);
    }
 
    private static Color GetBorderColor(PushButtonState state) =>
        state switch
        {
            PushButtonState.Pressed => DefaultColors.PressedSingleBorderColor,
            PushButtonState.Hot => DefaultColors.SingleBorderColor,
            PushButtonState.Disabled => DefaultColors.DisabledBorderLightColor,
            _ => DefaultColors.SingleBorderColor,
        };
}