// 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; } }