|
4 | 4 | using System.Drawing;
|
5 | 5 | using System.Drawing.Drawing2D;
|
6 | 6 | using System.Windows.Forms.VisualStyles;
|
| 7 | +using static System.Windows.Forms.DarkModeButtonColors; |
7 | 8 |
|
8 | 9 | namespace System.Windows.Forms;
|
9 | 10 |
|
10 | 11 | /// <summary>
|
11 |
| -/// Provides methods for rendering a button with Flat FlatStyle in dark mode. |
| 12 | +/// Flat‑style button renderer that, for the moment, mimics the Win32/Dark‑mode |
| 13 | +/// renderer bit‑for‑bit so that we can switch implementations without any |
| 14 | +/// visual delta. Once the design team decides on a new look we can diverge |
| 15 | +/// again. |
12 | 16 | /// </summary>
|
13 |
| -internal class FlatButtonDarkModeRenderer : ButtonDarkModeRendererBase |
| 17 | +internal sealed class FlatButtonDarkModeRenderer : ButtonDarkModeRendererBase |
14 | 18 | {
|
15 |
| - // UI constants |
16 |
| - private const int FocusIndicatorInflate = -2; |
17 |
| - private const int BorderThicknessDefault = 2; |
18 |
| - private const int BorderThicknessNormal = 1; |
19 |
| - private const int FlatEdgeRoundingAngle = 8; |
| 19 | + private const int FocusIndicatorInflate = -3; |
| 20 | + private const int CornerRadius = 6; |
| 21 | + private static readonly Size s_corner = new(CornerRadius, CornerRadius); |
20 | 22 |
|
21 |
| - private static readonly Size s_flatEdgeRoundingAngleSize = |
22 |
| - new(FlatEdgeRoundingAngle, FlatEdgeRoundingAngle); |
| 23 | + private protected override Padding PaddingCore { get; } = new(0); |
23 | 24 |
|
24 |
| - private protected override Padding PaddingCore { get; } = new Padding(0); |
25 |
| - |
26 |
| - /// <summary> |
27 |
| - /// Draws button background with flat styling (no rounded corners). |
28 |
| - /// </summary> |
29 |
| - public override Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) |
| 25 | + public override Rectangle DrawButtonBackground( |
| 26 | + Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) |
30 | 27 | {
|
31 |
| - // Get appropriate background color based on state |
32 |
| - Color backColor = GetBackgroundColor(state, isDefault); |
33 |
| - |
34 |
| - // Fill the background using cached brush |
35 |
| - using var brush = backColor.GetCachedSolidBrushScope(); |
36 |
| - graphics.FillRectangle(brush, bounds); |
| 28 | + // fill background |
| 29 | + using var back = GetBackgroundColor(state, isDefault).GetCachedSolidBrushScope(); |
| 30 | + graphics.FillRectangle(back, bounds); |
37 | 31 |
|
38 |
| - // Draw border if needed |
| 32 | + // draw border identical to Win32 |
39 | 33 | DrawButtonBorder(graphics, bounds, state, isDefault);
|
40 | 34 |
|
41 |
| - // Return content bounds (area inside the button for text/image) |
42 |
| - return bounds; |
| 35 | + // return inner content area (border + 1 px system padding) |
| 36 | + return Rectangle.Inflate(bounds, -3, -3); |
43 | 37 | }
|
44 | 38 |
|
45 |
| - /// <summary> |
46 |
| - /// Draws a focus rectangle with dotted lines inside the button. |
47 |
| - /// </summary> |
48 |
| - public override void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault) |
| 39 | + public override void DrawFocusIndicator(Graphics g, Rectangle contentBounds, bool isDefault) |
49 | 40 | {
|
50 |
| - // Create a slightly smaller rectangle for the focus indicator |
51 |
| - Rectangle focusRect = Rectangle.Inflate(contentBounds, FocusIndicatorInflate, FocusIndicatorInflate); |
| 41 | + Rectangle focus = Rectangle.Inflate(contentBounds, FocusIndicatorInflate, FocusIndicatorInflate); |
52 | 42 |
|
53 |
| - // Create dotted pen with appropriate color |
54 |
| - Color focusColor = isDefault |
55 |
| - ? DarkModeButtonColors.DefaultFocusIndicatorColor |
56 |
| - : DarkModeButtonColors.FocusIndicatorColor; |
57 |
| - |
58 |
| - // Custom pen needed for DashStyle - can't use cached version |
59 |
| - using var focusPen = new Pen(focusColor) |
60 |
| - { |
61 |
| - DashStyle = DashStyle.Dot |
62 |
| - }; |
| 43 | + Color focusBackColor = isDefault |
| 44 | + ? DefaultColors.AcceptFocusIndicatorBackColor |
| 45 | + : DefaultColors.FocusIndicatorBackColor; |
63 | 46 |
|
64 |
| - // Draw the focus rectangle (no rounded corners) |
65 |
| - graphics.DrawRectangle(focusPen, focusRect); |
| 47 | + ControlPaint.DrawFocusRectangle( |
| 48 | + g, |
| 49 | + focus, |
| 50 | + DefaultColors.FocusBorderColor, |
| 51 | + focusBackColor); |
66 | 52 | }
|
67 | 53 |
|
68 |
| - /// <summary> |
69 |
| - /// Gets the text color appropriate for the button state and type. |
70 |
| - /// </summary> |
71 | 54 | public override Color GetTextColor(PushButtonState state, bool isDefault) =>
|
72 | 55 | state == PushButtonState.Disabled
|
73 |
| - ? DarkModeButtonColors.DisabledTextColor |
| 56 | + ? DefaultColors.DisabledTextColor |
74 | 57 | : isDefault
|
75 |
| - ? DarkModeButtonColors.DefaultTextColor |
76 |
| - : DarkModeButtonColors.NormalTextColor; |
| 58 | + ? DefaultColors.AcceptButtonTextColor |
| 59 | + : DefaultColors.NormalTextColor; |
77 | 60 |
|
78 |
| - /// <summary> |
79 |
| - /// Gets the background color appropriate for the button state and type. |
80 |
| - /// </summary> |
81 | 61 | private static Color GetBackgroundColor(PushButtonState state, bool isDefault) =>
|
82 | 62 | isDefault
|
83 | 63 | ? state switch
|
84 | 64 | {
|
85 |
| - PushButtonState.Normal => DarkModeButtonColors.DefaultBackgroundColor, |
86 |
| - PushButtonState.Hot => DarkModeButtonColors.DefaultHoverBackgroundColor, |
87 |
| - PushButtonState.Pressed => DarkModeButtonColors.DefaultPressedBackgroundColor, |
88 |
| - PushButtonState.Disabled => DarkModeButtonColors.DefaultDisabledBackgroundColor, |
89 |
| - _ => DarkModeButtonColors.DefaultBackgroundColor |
| 65 | + PushButtonState.Normal => DefaultColors.StandardBackColor, |
| 66 | + PushButtonState.Hot => DefaultColors.HoverBackColor, |
| 67 | + PushButtonState.Pressed => DefaultColors.PressedBackColor, |
| 68 | + PushButtonState.Disabled => DefaultColors.DisabledBackColor, |
| 69 | + _ => DefaultColors.StandardBackColor |
90 | 70 | }
|
91 | 71 | : state switch
|
92 | 72 | {
|
93 |
| - PushButtonState.Normal => DarkModeButtonColors.NormalBackgroundColor, |
94 |
| - PushButtonState.Hot => DarkModeButtonColors.HoverBackgroundColor, |
95 |
| - PushButtonState.Pressed => DarkModeButtonColors.PressedBackgroundColor, |
96 |
| - PushButtonState.Disabled => DarkModeButtonColors.DisabledBackgroundColor, |
97 |
| - _ => DarkModeButtonColors.NormalBackgroundColor |
| 73 | + PushButtonState.Normal => DefaultColors.StandardBackColor, |
| 74 | + PushButtonState.Hot => DefaultColors.HoverBackColor, |
| 75 | + PushButtonState.Pressed => DefaultColors.PressedBackColor, |
| 76 | + PushButtonState.Disabled => DefaultColors.DisabledBackColor, |
| 77 | + _ => DefaultColors.StandardBackColor |
98 | 78 | };
|
99 | 79 |
|
100 |
| - /// <summary> |
101 |
| - /// Draws the button border based on the current state. |
102 |
| - /// </summary> |
103 |
| - private static void DrawButtonBorder(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) |
| 80 | + private static void DrawButtonBorder(Graphics g, Rectangle bounds, PushButtonState state, bool isDefault) |
104 | 81 | {
|
105 |
| - // For flat style, we need to create a GraphicsPath for the rectangle |
106 |
| - using GraphicsPath path = new(); |
107 |
| - path.AddRoundedRectangle(bounds, s_flatEdgeRoundingAngleSize); |
108 |
| - |
109 |
| - // Skip border drawing for disabled state |
110 |
| - if (state == PushButtonState.Disabled) |
111 |
| - { |
112 |
| - IButtonRenderer.DrawButtonBorder( |
113 |
| - graphics, |
114 |
| - path, |
115 |
| - DarkModeButtonColors.DisabledBorderLightColor, |
116 |
| - 1); |
117 |
| - |
118 |
| - return; |
119 |
| - } |
| 82 | + g.SmoothingMode = SmoothingMode.AntiAlias; |
120 | 83 |
|
121 |
| - Color borderColor; |
| 84 | + // Win32 draws its stroke fully *inside* the control → inset by 1 px |
| 85 | + Rectangle outer = Rectangle.Inflate(bounds, -1, -1); |
122 | 86 |
|
123 |
| - int thickness = isDefault |
124 |
| - ? BorderThicknessDefault |
125 |
| - : BorderThicknessNormal; |
| 87 | + DrawSingleBorder(g, outer, GetBorderColor(state)); |
126 | 88 |
|
127 |
| - // For pressed state, draw a darker border |
128 |
| - if (state == PushButtonState.Pressed) |
| 89 | + // Default button gets a second 1‑px border one pixel further inside |
| 90 | + if (isDefault) |
129 | 91 | {
|
130 |
| - borderColor = isDefault |
131 |
| - ? DarkModeButtonColors.PressedSingleBorderColor |
132 |
| - : DarkModeButtonColors.DefaultFocusIndicatorColor; |
133 |
| - |
134 |
| - IButtonRenderer.DrawButtonBorder(graphics, path, borderColor, thickness); |
135 |
| - |
136 |
| - return; |
| 92 | + Rectangle inner = Rectangle.Inflate(outer, -1, -1); |
| 93 | + DrawSingleBorder(g, inner, DefaultColors.AcceptFocusIndicatorBackColor); |
137 | 94 | }
|
| 95 | + } |
138 | 96 |
|
139 |
| - // For other states, draw a border with appropriate thickness |
140 |
| - // For pressed state, draw a darker border |
141 |
| - borderColor = isDefault |
142 |
| - ? DarkModeButtonColors.DefaultSingleBorderColor |
143 |
| - : DarkModeButtonColors.SingleBorderColor; |
| 97 | + private static void DrawSingleBorder(Graphics g, Rectangle rect, Color color) |
| 98 | + { |
| 99 | + g.SmoothingMode = SmoothingMode.AntiAlias; |
144 | 100 |
|
145 |
| - IButtonRenderer.DrawButtonBorder(graphics, path, borderColor, thickness); |
| 101 | + using var path = new GraphicsPath(); |
| 102 | + path.AddRoundedRectangle(rect, s_corner); |
146 | 103 |
|
147 |
| - return; |
| 104 | + // a 1‑px stroke, aligned *inside*, is exactly what Win32 draws |
| 105 | + using var pen = new Pen(color) { Alignment = PenAlignment.Inset }; |
| 106 | + g.DrawPath(pen, path); |
148 | 107 | }
|
| 108 | + |
| 109 | + private static Color GetBorderColor(PushButtonState state) => |
| 110 | + state switch |
| 111 | + { |
| 112 | + PushButtonState.Pressed => DefaultColors.PressedSingleBorderColor, |
| 113 | + PushButtonState.Hot => DefaultColors.SingleBorderColor, |
| 114 | + PushButtonState.Disabled => DefaultColors.DisabledBorderLightColor, |
| 115 | + _ => DefaultColors.SingleBorderColor, |
| 116 | + }; |
149 | 117 | }
|
0 commit comments