Skip to content

Commit 57f4d2f

Browse files
Implement ButtonDarkModeAdapter.
1 parent 69aba2d commit 57f4d2f

File tree

6 files changed

+319
-24
lines changed

6 files changed

+319
-24
lines changed

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/Button.cs

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public AutoSizeMode AutoSizeMode
8181

8282
internal override ButtonBaseAdapter CreateStandardAdapter() => new ButtonStandardAdapter(this);
8383

84+
internal override ButtonBaseAdapter CreateDarkModeAdapter() => new ButtonDarkModeAdapter(this);
85+
8486
internal override Size GetPreferredSizeCore(Size proposedConstraints)
8587
{
8688
if (FlatStyle != FlatStyle.System)

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -956,8 +956,8 @@ public override Size GetPreferredSize(Size proposedSize)
956956

957957
internal override Size GetPreferredSizeCore(Size proposedConstraints)
958958
{
959-
Size preferedSize = Adapter.GetPreferredSizeCore(proposedConstraints);
960-
return LayoutUtils.UnionSizes(preferedSize + Padding.Size, MinimumSize);
959+
Size preferredSize = Adapter.GetPreferredSizeCore(proposedConstraints);
960+
return LayoutUtils.UnionSizes(preferredSize + Padding.Size, MinimumSize);
961961
}
962962

963963
internal ButtonBaseAdapter Adapter

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonBaseAdapter.cs

+6-3
Original file line numberDiff line numberDiff line change
@@ -643,15 +643,18 @@ private ColorOptions CommonRender(IDeviceContext deviceContext) =>
643643
Enabled = Control.Enabled
644644
};
645645

646-
protected ColorOptions PaintRender(IDeviceContext deviceContext) => CommonRender(deviceContext);
646+
protected ColorOptions PaintRender(IDeviceContext deviceContext)
647+
=> CommonRender(deviceContext);
647648

648649
internal static ColorOptions PaintFlatRender(Graphics g, Color foreColor, Color backColor, bool enabled) =>
649650
CommonRender(g, foreColor, backColor, enabled);
650651

651-
protected ColorOptions PaintFlatRender(IDeviceContext deviceContext) => CommonRender(deviceContext);
652+
protected ColorOptions PaintFlatRender(IDeviceContext deviceContext)
653+
=> CommonRender(deviceContext);
652654

653655
internal static ColorOptions PaintPopupRender(Graphics g, Color foreColor, Color backColor, bool enabled) =>
654656
CommonRender(g, foreColor, backColor, enabled);
655657

656-
protected ColorOptions PaintPopupRender(IDeviceContext deviceContext) => CommonRender(deviceContext);
658+
protected ColorOptions PaintPopupRender(IDeviceContext deviceContext)
659+
=> CommonRender(deviceContext);
657660
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Drawing;
5+
using System.Drawing.Drawing2D;
6+
7+
namespace System.Windows.Forms.ButtonInternal;
8+
9+
internal class ButtonDarkModeAdapter : ButtonBaseAdapter
10+
{
11+
private const int BorderSize = 1;
12+
private const int CornerRadius = 5;
13+
14+
internal ButtonDarkModeAdapter(ButtonBase control) : base(control) { }
15+
16+
private static void PaintBackground(PaintEventArgs e, Rectangle r, Color backColor)
17+
{
18+
// Save original smoothing mode and set to anti-alias for smooth corners
19+
SmoothingMode originalMode = e.Graphics.SmoothingMode;
20+
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
21+
22+
using GraphicsPath path = GetRoundedRectanglePath(r, CornerRadius);
23+
using SolidBrush brush = new(backColor);
24+
e.Graphics.FillPath(brush, path);
25+
26+
// Restore original smoothing mode
27+
e.Graphics.SmoothingMode = originalMode;
28+
}
29+
30+
internal override void PaintUp(PaintEventArgs e, CheckState state)
31+
{
32+
bool isDefault = Control.IsDefault;
33+
ColorData colors = PaintDarkModeRender(e).Calculate();
34+
LayoutData layout = PaintDarkModeLayout(
35+
up: true,
36+
check: state == CheckState.Checked,
37+
borderSize: BorderSize).Layout();
38+
39+
Rectangle r = Control.ClientRectangle;
40+
41+
// Determine background color based on button state
42+
Color backColor = GetBackgroundColor(state, isDefault, normal: true);
43+
44+
PaintBackground(e, r, backColor);
45+
46+
if (isDefault)
47+
{
48+
r.Inflate(-1, -1);
49+
}
50+
51+
PaintImage(e, layout);
52+
PaintField(
53+
e,
54+
layout,
55+
colors,
56+
GetTextColor(isDefault, Control.Enabled),
57+
drawFocus: false);
58+
59+
if (Control.Focused && Control.ShowFocusCues)
60+
{
61+
DrawDarkModeFocus(e, layout.Focus, isDefault);
62+
}
63+
64+
if (!(Control.IsDefault && Control.Focused && (BorderSize == 0)))
65+
{
66+
DrawDarkModeBorder(e, r, isDefault, pressed: false);
67+
}
68+
}
69+
70+
internal override void PaintDown(PaintEventArgs e, CheckState state)
71+
{
72+
bool isDefault = Control.IsDefault;
73+
74+
ColorData colors = PaintDarkModeRender(e).Calculate();
75+
LayoutData layout = PaintDarkModeLayout(
76+
up: false,
77+
check: state == CheckState.Checked,
78+
borderSize: BorderSize).Layout();
79+
80+
Rectangle r = Control.ClientRectangle;
81+
82+
// Determine background color based on button state
83+
Color backColor = GetBackgroundColor(state, isDefault, normal: false);
84+
85+
PaintBackground(e, r, backColor);
86+
87+
if (isDefault)
88+
{
89+
r.Inflate(-1, -1);
90+
}
91+
92+
PaintImage(e, layout);
93+
94+
PaintField(
95+
e,
96+
layout,
97+
colors,
98+
GetTextColor(isDefault, Control.Enabled),
99+
drawFocus: false);
100+
101+
if (Control.Focused && Control.ShowFocusCues)
102+
{
103+
DrawDarkModeFocus(e, layout.Focus, isDefault);
104+
}
105+
106+
if (!(Control.IsDefault && Control.Focused && (BorderSize == 0)))
107+
{
108+
DrawDarkModeBorder(e, r, isDefault, pressed: true);
109+
}
110+
}
111+
112+
internal override void PaintOver(PaintEventArgs e, CheckState state)
113+
{
114+
bool isDefault = Control.IsDefault;
115+
ColorData colors = PaintDarkModeRender(e).Calculate();
116+
117+
LayoutData layout = PaintDarkModeLayout(
118+
up: true,
119+
check: state == CheckState.Checked,
120+
borderSize: BorderSize).Layout();
121+
122+
Rectangle r = Control.ClientRectangle;
123+
124+
// Get hover background color
125+
Color backColor = isDefault
126+
? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultHoverBackgroundColor
127+
: ButtonDarkModeRenderer.DarkModeButtonColors.HoverBackgroundColor;
128+
129+
PaintBackground(e, r, backColor);
130+
131+
if (isDefault)
132+
{
133+
r.Inflate(-1, -1);
134+
}
135+
136+
PaintImage(e, layout);
137+
PaintField(
138+
e,
139+
layout,
140+
colors,
141+
GetTextColor(isDefault, Control.Enabled),
142+
drawFocus: false);
143+
144+
if (Control.Focused && Control.ShowFocusCues)
145+
{
146+
DrawDarkModeFocus(e, layout.Focus, isDefault);
147+
}
148+
149+
if (!(Control.IsDefault && Control.Focused && (BorderSize == 0)))
150+
{
151+
DrawDarkModeBorder(e, r, isDefault, pressed: false);
152+
}
153+
}
154+
155+
protected override LayoutOptions Layout(PaintEventArgs e) =>
156+
PaintDarkModeLayout(up: true, check: false, BorderSize);
157+
158+
private LayoutOptions PaintDarkModeLayout(bool up, bool check, int borderSize)
159+
{
160+
LayoutOptions layout = CommonLayout();
161+
layout.BorderSize = borderSize + (check ? 1 : 0);
162+
layout.PaddingSize = check ? 1 : 2;
163+
layout.FocusOddEvenFixup = false;
164+
layout.TextOffset = !up;
165+
layout.ShadowedText = false; // Dark mode doesn't use shadowed text
166+
167+
return layout;
168+
}
169+
170+
private ColorOptions PaintDarkModeRender(IDeviceContext deviceContext) =>
171+
new(deviceContext, Control.ForeColor, Control.BackColor)
172+
{
173+
Enabled = Control.Enabled
174+
};
175+
176+
private Color GetBackgroundColor(CheckState state, bool isDefault, bool normal)
177+
{
178+
if (!Control.Enabled)
179+
{
180+
return isDefault
181+
? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultDisabledBackgroundColor
182+
: ButtonDarkModeRenderer.DarkModeButtonColors.DisabledBackgroundColor;
183+
}
184+
185+
if (!normal) // Pressed state
186+
{
187+
return isDefault
188+
? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultPressedBackgroundColor
189+
: ButtonDarkModeRenderer.DarkModeButtonColors.PressedBackgroundColor;
190+
}
191+
192+
// Normal state
193+
switch (state)
194+
{
195+
case CheckState.Checked:
196+
return isDefault
197+
? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor
198+
: ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor;
199+
case CheckState.Indeterminate:
200+
Color baseColor = isDefault
201+
? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor
202+
: ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor;
203+
204+
return Color.FromArgb(
205+
baseColor.R + 10,
206+
baseColor.G + 10,
207+
baseColor.B + 10);
208+
default:
209+
return isDefault
210+
? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor
211+
: ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor;
212+
}
213+
}
214+
215+
private static Color GetTextColor(bool isDefault, bool enabled) =>
216+
!enabled
217+
? ButtonDarkModeRenderer.DarkModeButtonColors.DisabledTextColor
218+
: isDefault
219+
? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultTextColor
220+
: ButtonDarkModeRenderer.DarkModeButtonColors.NormalTextColor;
221+
222+
private static void DrawDarkModeBorder(PaintEventArgs e, Rectangle r, bool isDefault, bool pressed)
223+
{
224+
SmoothingMode originalMode = e.Graphics.SmoothingMode;
225+
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
226+
227+
using GraphicsPath path = GetRoundedRectanglePath(r, CornerRadius);
228+
229+
Color borderColor = isDefault
230+
? Color.FromArgb(
231+
red: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.R - 20,
232+
green: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.G - 10,
233+
blue: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.B - 30)
234+
: pressed
235+
? ButtonDarkModeRenderer.DarkModeButtonColors.BottomRightBorderColor
236+
: ButtonDarkModeRenderer.DarkModeButtonColors.SingleBorderColor;
237+
238+
using Pen borderPen = new(borderColor);
239+
e.Graphics.DrawPath(borderPen, path);
240+
241+
// Restore original smoothing mode
242+
e.Graphics.SmoothingMode = originalMode;
243+
}
244+
245+
private static void DrawDarkModeFocus(PaintEventArgs e, Rectangle focusRect, bool isDefault)
246+
{
247+
SmoothingMode originalMode = e.Graphics.SmoothingMode;
248+
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
249+
250+
// Create a slightly smaller rectangle for the focus indicator (2px inside)
251+
Rectangle innerRect = Rectangle.Inflate(focusRect, -2, -2);
252+
253+
// Get appropriate focus color
254+
Color focusColor = isDefault
255+
? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultFocusIndicatorColor
256+
: ButtonDarkModeRenderer.DarkModeButtonColors.FocusIndicatorColor;
257+
258+
using Pen focusPen = new(focusColor)
259+
{
260+
DashStyle = DashStyle.Dot
261+
};
262+
263+
// Draw the focus rectangle with rounded corners
264+
using GraphicsPath focusPath = GetRoundedRectanglePath(innerRect, 3);
265+
e.Graphics.DrawPath(focusPen, focusPath);
266+
267+
// Restore original smoothing mode
268+
e.Graphics.SmoothingMode = originalMode;
269+
}
270+
271+
private static GraphicsPath GetRoundedRectanglePath(Rectangle bounds, int radius)
272+
{
273+
GraphicsPath path = new();
274+
int diameter = radius * 2;
275+
Rectangle arcRect = new(bounds.Location, new Size(diameter, diameter));
276+
277+
// Top left corner
278+
path.AddArc(arcRect, 180, 90);
279+
280+
// Top right corner
281+
arcRect.X = bounds.Right - diameter;
282+
path.AddArc(arcRect, 270, 90);
283+
284+
// Bottom right corner
285+
arcRect.Y = bounds.Bottom - diameter;
286+
path.AddArc(arcRect, 0, 90);
287+
288+
// Bottom left corner
289+
arcRect.X = bounds.Left;
290+
path.AddArc(arcRect, 90, 90);
291+
292+
path.CloseFigure();
293+
return path;
294+
}
295+
}

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonFlatAdapter.cs

+13-18
Original file line numberDiff line numberDiff line change
@@ -223,23 +223,15 @@ internal override void PaintOver(PaintEventArgs e, CheckState state)
223223

224224
Rectangle r = Control.ClientRectangle;
225225

226-
Color backColor;
227-
if (!Control.FlatAppearance.MouseOverBackColor.IsEmpty)
228-
{
229-
backColor = Control.FlatAppearance.MouseOverBackColor;
230-
}
231-
else if (!Control.FlatAppearance.CheckedBackColor.IsEmpty)
232-
{
233-
backColor = state is CheckState.Checked or CheckState.Indeterminate
234-
? Control.FlatAppearance.CheckedBackColor.MixColor(colors.LowButtonFace)
235-
: colors.LowButtonFace;
236-
}
237-
else
238-
{
239-
backColor = state is CheckState.Indeterminate
240-
? colors.ButtonFace.MixColor(colors.LowButtonFace)
241-
: colors.LowButtonFace;
242-
}
226+
Color backColor = !Control.FlatAppearance.MouseOverBackColor.IsEmpty
227+
? Control.FlatAppearance.MouseOverBackColor
228+
: !Control.FlatAppearance.CheckedBackColor.IsEmpty
229+
? state is CheckState.Checked or CheckState.Indeterminate
230+
? Control.FlatAppearance.CheckedBackColor.MixColor(colors.LowButtonFace)
231+
: colors.LowButtonFace
232+
: state is CheckState.Indeterminate
233+
? colors.ButtonFace.MixColor(colors.LowButtonFace)
234+
: colors.LowButtonFace;
243235

244236
PaintBackground(e, r, IsHighContrastHighlighted() ? SystemColors.Highlight : backColor);
245237

@@ -249,11 +241,14 @@ internal override void PaintOver(PaintEventArgs e, CheckState state)
249241
}
250242

251243
PaintImage(e, layout);
244+
252245
PaintField(
253246
e,
254247
layout,
255248
colors,
256-
IsHighContrastHighlighted() ? SystemColors.HighlightText : colors.WindowText,
249+
IsHighContrastHighlighted()
250+
? SystemColors.HighlightText
251+
: colors.WindowText,
257252
drawFocus: false);
258253

259254
if (Control.Focused && Control.ShowFocusCues)

src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRenderer.DarkModeButtonColors.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace System.Windows.Forms;
66
internal static partial class ButtonDarkModeRenderer
77
{
88
/// <summary>Cache of colors for different button states</summary>
9-
private static class DarkModeButtonColors
9+
internal static class DarkModeButtonColors
1010
{
1111
// Normal Button (non-default)
1212
/// <summary>Button background color for normal state (#2B2B2B)</summary>

0 commit comments

Comments
 (0)