|
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