File: System\Windows\Forms\Controls\Buttons\ButtonInternal\DarkMode\PopupButtonDarkModeRenderer.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;
using static System.Windows.Forms.DarkModeButtonColors;
 
namespace System.Windows.Forms;
 
/// <summary>
///  Renders a Classic <see cref="FlatStyle.Popup"/> button in dark mode.
/// </summary>
internal sealed class PopupButtonDarkModeRenderer : ButtonDarkModeRendererBase
{
    private const int CornerRadiusLogical = 5;
    private const int ContentInsetLogical = 3;
    private const int FocusInsetLogical = 2;
    private const int PressOffsetLogical = 1;
 
    private protected override Padding PaddingCore => Padding.Empty;
 
    private protected override bool PaintParentBackground => true;
 
    public override Rectangle DrawButtonBackground(
        Graphics graphics,
        Rectangle bounds,
        PushButtonState state,
        bool isDefault,
        bool focused,
        Color backColor)
    {
        if (bounds.Width <= 0 || bounds.Height <= 0)
        {
            return Rectangle.Empty;
        }
 
        Rectangle bodyBounds = bounds;
        bodyBounds.Width--;
        bodyBounds.Height--;
 
        using GraphicsPath bodyPath = CreateRoundedPath(bodyBounds);
        using (var brush = backColor.GetCachedSolidBrushScope())
        {
            graphics.FillPath(brush, bodyPath);
        }
 
        DrawButtonBorder(
            graphics,
            bodyBounds,
            state,
            isDefault);
 
        int contentInset = Math.Max(1, Scale(ContentInsetLogical));
        Rectangle contentBounds = Rectangle.Inflate(
            bodyBounds,
            -contentInset,
            -contentInset);
 
        if (state == PushButtonState.Pressed)
        {
            int pressOffset = Math.Max(1, Scale(PressOffsetLogical));
            contentBounds.Offset(pressOffset, pressOffset);
        }
 
        return contentBounds;
    }
 
    public override void DrawFocusIndicator(
        Graphics graphics,
        Rectangle contentBounds,
        bool isDefault)
    {
        int focusInset = Math.Max(1, Scale(FocusInsetLogical));
        Rectangle focusBounds = Rectangle.Inflate(
            contentBounds,
            -focusInset,
            -focusInset);
        if (focusBounds.Width <= 0 || focusBounds.Height <= 0)
        {
            return;
        }
 
        Color focusBackColor = isDefault
            ? DefaultColors.AcceptFocusIndicatorBackColor
            : DefaultColors.FocusIndicatorBackColor;
        ControlPaint.DrawFocusRectangle(
            graphics,
            focusBounds,
            DefaultColors.FocusBorderColor,
            focusBackColor);
    }
 
    public override Color GetTextColor(
        PushButtonState state,
        bool isDefault,
        Color backColor)
        => state == PushButtonState.Disabled
            ? DefaultColors.DisabledTextColor
            : DefaultColors.NormalTextColor;
 
    public override Color GetBackgroundColor(
        PushButtonState state,
        bool isDefault)
        => state switch
        {
            PushButtonState.Hot => DefaultColors.HoverBackColor,
            PushButtonState.Pressed => DefaultColors.PressedBackColor,
            PushButtonState.Disabled => DefaultColors.DisabledBackColor,
            _ => DefaultColors.StandardBackColor
        };
 
    private void DrawButtonBorder(
        Graphics graphics,
        Rectangle bounds,
        PushButtonState state,
        bool isDefault)
    {
        int thickness = ScaleBorderThickness(
            Math.Max(1, Scale(1)));
        if (thickness == 0)
        {
            return;
        }
 
        using GraphicsStateScope graphicsStateScope = new(graphics);
        using GraphicsPath borderClip = CreateRoundedPath(bounds);
        graphics.SetClip(borderClip);
 
        Color topLeftColor;
        Color bottomRightColor;
 
        if (FlatAppearance is { BorderColor.IsEmpty: false } appearance)
        {
            topLeftColor = appearance.BorderColor;
            bottomRightColor = appearance.BorderColor;
        }
        else
        {
            (topLeftColor, bottomRightColor) = state switch
            {
                PushButtonState.Pressed => (
                    DefaultColors.ShadowDarkColor,
                    DefaultColors.HighlightBrightColor),
                PushButtonState.Disabled => (
                    DefaultColors.DisabledBorderLightColor,
                    DefaultColors.DisabledBorderDarkColor),
                _ => (
                    DefaultColors.HighlightColor,
                    DefaultColors.ShadowDarkColor)
            };
        }
 
        using var topLeftPen = topLeftColor.GetCachedPenScope(
            thickness);
        using var bottomRightPen = bottomRightColor.GetCachedPenScope(
            thickness);
        graphics.DrawLine(
            topLeftPen,
            bounds.Left,
            bounds.Bottom - 1,
            bounds.Left,
            bounds.Top);
        graphics.DrawLine(
            topLeftPen,
            bounds.Left,
            bounds.Top,
            bounds.Right - 1,
            bounds.Top);
        graphics.DrawLine(
            bottomRightPen,
            bounds.Right - 1,
            bounds.Top,
            bounds.Right - 1,
            bounds.Bottom - 1);
        graphics.DrawLine(
            bottomRightPen,
            bounds.Right - 1,
            bounds.Bottom - 1,
            bounds.Left,
            bounds.Bottom - 1);
 
        if (isDefault && state != PushButtonState.Disabled)
        {
            Rectangle defaultBounds = Rectangle.Inflate(
                bounds,
                -thickness,
                -thickness);
            if (defaultBounds.Width > 0 && defaultBounds.Height > 0)
            {
                using var defaultPen =
                    DefaultColors.AcceptFocusIndicatorBackColor
                        .GetCachedPenScope(thickness);
                graphics.DrawRectangle(defaultPen, defaultBounds);
            }
        }
    }
 
    private GraphicsPath CreateRoundedPath(Rectangle bounds)
    {
        GraphicsPath path = new();
        int radius = Math.Clamp(
            Scale(CornerRadiusLogical),
            1,
            Math.Max(1, Math.Min(bounds.Width, bounds.Height)));
        path.AddRoundedRectangle(
            bounds,
            new Size(radius, radius));
 
        return path;
    }
}