diff --git a/.github/AI-Prompts/GDI-Refactoring/AIP-cache_pens-and_brushes.md b/.github/AI-Prompts/GDI-Refactoring/AIP-cache_pens-and_brushes.md new file mode 100644 index 00000000000..e70e931be01 --- /dev/null +++ b/.github/AI-Prompts/GDI-Refactoring/AIP-cache_pens-and_brushes.md @@ -0,0 +1,80 @@ +# C# Refactor: Pen & Brush to Cached Scope API + +You are an expert C# code transformer. + +**Goal:** Rewrite every creation and usage of `Pen` and `SolidBrush` in the input file to use our `RefCountedCache` (or `PenCache`/`SolidBrushCache`) scoped API instead of direct `new` calls or helper methods returning non-cached objects. + +--- + +## Transformation Rules + +1. **Direct Instantiations** + - **Pens** + - Replace `new Pen(color)` with: + ```csharp + using var pen = color.GetCachedPenScope(); + ``` + - Replace `new Pen(color, width)` with: + ```csharp + using var pen = color.GetCachedPenScope(width); + ``` + - Preserve original variable names, e.g.: + ```csharp + using var borderPen = color.GetCachedPenScope(); + ``` + + - **SolidBrushes** + - Replace `new SolidBrush(color)` with: + ```csharp + using var brush = color.GetCachedSolidBrushScope(); + ``` + - Preserve original variable names. + +2. **Helper Methods** + - **Detection:** + - Identify any methods in the class that return `Pen` or `SolidBrush`. + - If a helper returns a raw `Pen` or `SolidBrush`, transform it to return the corresponding cache‐scope type (`PenCache.Scope` or `SolidBrushCache.Scope`). + + - **Private Helpers:** + - Change the return type to the scope type. + - Update the method body to call the cache API directly (e.g., `color.GetCachedPenScope()`). + + - **Public/Internal Helpers:** + - If the helper is externally visible, create a new class‐scoped helper method (e.g., `GetDarkModePenScope`) returning `PenCache.Scope` or `SolidBrushCache.Scope`, based on the original logic, and leave the original helper untouched. + +3. **Usages in Drawing Calls** + - All `g.Draw...` or `g.Fill...` invocations that previously took a `Pen` or `Brush` should work unchanged with the new scoped objects via the implicit converter. + +4. **Preserve Formatting & Logic** + - Do not alter any logic that is unrelated to Pen/Brush instantiation. + - Maintain existing code style and formatting. + +--- + +## Example + +**Before:** +```csharp +using (var p = new Pen(Color.Red, 2)) +{ + g.DrawEllipse(p, bounds); +} + +private Pen GetCustomBorderPen(Color color) +{ + return new Pen(color); +} +``` + +**After:** +```csharp +using var p = Color.Red.GetCachedPenScope(2); +g.DrawEllipse(p, bounds); + +private PenCache.Scope GetCustomBorderPenScope(Color color) + => color.GetCachedPenScope(); +``` + +--- + +**Task:** Rewrite the entire input file accordingly and output the modified code. diff --git a/Winforms.sln b/Winforms.sln index c1f2af710c0..1f5407423d6 100644 --- a/Winforms.sln +++ b/Winforms.sln @@ -104,8 +104,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "interop", "interop", "{A31B EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DesignSurface", "DesignSurface", "{43E46506-7DF8-4E7A-A579-996CA43041EB}" ProjectSection(SolutionItems) = preProject - src\test\integration\DesignSurface\README.md = src\test\integration\DesignSurface\README.md src\test\integration\DesignSurface\Directory.Build.props = src\test\integration\DesignSurface\Directory.Build.props + src\test\integration\DesignSurface\README.md = src\test\integration\DesignSurface\README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoConsole", "src\test\integration\DesignSurface\DemoConsole\DemoConsole.csproj", "{93310A19-DDCA-4BCD-AEDE-5C5D788DAFB4}" @@ -209,6 +209,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Analyzers", "Analyzers", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Private.Windows.GdiPlus", "src\System.Private.Windows.GdiPlus\System.Private.Windows.GdiPlus.csproj", "{442C867C-51C0-8CE5-F067-DF065008E3DA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AI-Prompts", "AI-Prompts", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + .github\copilot-instructions.md = .github\copilot-instructions.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GDI", "GDI", "{C2AEC915-89F2-4FE1-B03D-C811684D98AF}" + ProjectSection(SolutionItems) = preProject + .github\AI-Prompts\GDI-Refactoring\AIP-cache_pens-and_brushes.md = .github\AI-Prompts\GDI-Refactoring\AIP-cache_pens-and_brushes.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1195,6 +1205,7 @@ Global {656C66A4-59CD-4E14-8AE4-1F5BCEECB553} = {8B4B1E09-B3C7-4044-B223-94EDEC1CAA20} {D4D97D78-D213-45DF-B003-9C4C9F2E5E1C} = {8B4B1E09-B3C7-4044-B223-94EDEC1CAA20} {442C867C-51C0-8CE5-F067-DF065008E3DA} = {77FEDB47-F7F6-490D-AF7C-ABB4A9E0B9D7} + {C2AEC915-89F2-4FE1-B03D-C811684D98AF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7B1B0433-F612-4E5A-BE7E-FCF5B9F6E136} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/Button.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/Button.cs index c193f51e7d6..21ed26f941b 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/Button.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/Button.cs @@ -81,6 +81,8 @@ public AutoSizeMode AutoSizeMode internal override ButtonBaseAdapter CreateStandardAdapter() => new ButtonStandardAdapter(this); + internal override ButtonBaseAdapter CreateDarkModeAdapter() => new ButtonDarkModeAdapter(this); + internal override Size GetPreferredSizeCore(Size proposedConstraints) { if (FlatStyle != FlatStyle.System) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs index 6de777685eb..4eeb408595d 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs @@ -77,11 +77,6 @@ protected ButtonBase() SetExtendedState(ExtendedStates.UserPreferredSizeCache, true); SetStyle(ControlStyles.UserMouse | ControlStyles.UserPaint, OwnerDraw); - -#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - SetStyle(ControlStyles.ApplyThemingImplicitly, true); -#pragma warning restore WFO5001 - SetFlag(FlagUseMnemonic, true); SetFlag(FlagShowToolTip, false); } @@ -617,7 +612,11 @@ internal virtual Rectangle OverChangeRectangle } } - internal bool OwnerDraw => FlatStyle != FlatStyle.System; +#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + internal bool OwnerDraw => + (FlatStyle != FlatStyle.System) + || Application.IsDarkModeEnabled; +#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. bool? ICommandBindingTargetProvider.PreviousEnabledStatus { get; set; } @@ -957,35 +956,44 @@ public override Size GetPreferredSize(Size proposedSize) internal override Size GetPreferredSizeCore(Size proposedConstraints) { - Size preferedSize = Adapter.GetPreferredSizeCore(proposedConstraints); - return LayoutUtils.UnionSizes(preferedSize + Padding.Size, MinimumSize); + Size preferredSize = Adapter.GetPreferredSizeCore(proposedConstraints); + return LayoutUtils.UnionSizes(preferredSize + Padding.Size, MinimumSize); } internal ButtonBaseAdapter Adapter { get { - if (_adapter is null || FlatStyle != _cachedAdapterType) +#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + if (_adapter is null + || FlatStyle != _cachedAdapterType) { - switch (FlatStyle) + if (Application.IsDarkModeEnabled) { - case FlatStyle.Standard: - _adapter = CreateStandardAdapter(); - break; - case FlatStyle.Popup: - _adapter = CreatePopupAdapter(); - break; - case FlatStyle.Flat: - _adapter = CreateFlatAdapter(); - ; - break; - default: - Debug.Fail($"Unsupported FlatStyle: \"{FlatStyle}\""); - break; + _adapter = CreateDarkModeAdapter(); } + else + { + switch (FlatStyle) + { + case FlatStyle.Standard: + _adapter = CreateStandardAdapter(); + break; + case FlatStyle.Popup: + _adapter = CreatePopupAdapter(); + break; + case FlatStyle.Flat: + _adapter = CreateFlatAdapter(); + break; + default: + Debug.Fail($"Unsupported FlatStyle: \"{FlatStyle}\""); + break; + } - _cachedAdapterType = FlatStyle; + _cachedAdapterType = FlatStyle; + } } +#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. return _adapter; } @@ -1009,6 +1017,14 @@ internal virtual ButtonBaseAdapter CreateStandardAdapter() return null; } + internal virtual ButtonBaseAdapter CreateDarkModeAdapter() + { + // When a button-derived class does not have a dedicated DarkMode adapter implementation, + // we're falling back to the standard adapter, to not _force_ the derived class to implement + // a dark mode adapter. + return CreateStandardAdapter(); + } + internal virtual StringFormat CreateStringFormat() { if (Adapter is null) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonBaseAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonBaseAdapter.cs index 750be56adf3..05e2fe098e9 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonBaseAdapter.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonBaseAdapter.cs @@ -643,15 +643,18 @@ private ColorOptions CommonRender(IDeviceContext deviceContext) => Enabled = Control.Enabled }; - protected ColorOptions PaintRender(IDeviceContext deviceContext) => CommonRender(deviceContext); + protected ColorOptions PaintRender(IDeviceContext deviceContext) + => CommonRender(deviceContext); internal static ColorOptions PaintFlatRender(Graphics g, Color foreColor, Color backColor, bool enabled) => CommonRender(g, foreColor, backColor, enabled); - protected ColorOptions PaintFlatRender(IDeviceContext deviceContext) => CommonRender(deviceContext); + protected ColorOptions PaintFlatRender(IDeviceContext deviceContext) + => CommonRender(deviceContext); internal static ColorOptions PaintPopupRender(Graphics g, Color foreColor, Color backColor, bool enabled) => CommonRender(g, foreColor, backColor, enabled); - protected ColorOptions PaintPopupRender(IDeviceContext deviceContext) => CommonRender(deviceContext); + protected ColorOptions PaintPopupRender(IDeviceContext deviceContext) + => CommonRender(deviceContext); } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonDarkModeAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonDarkModeAdapter.cs new file mode 100644 index 00000000000..73552c6ace3 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonDarkModeAdapter.cs @@ -0,0 +1,128 @@ +// 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.Windows.Forms.VisualStyles; + +namespace System.Windows.Forms.ButtonInternal; + +internal class ButtonDarkModeAdapter : ButtonBaseAdapter +{ + internal ButtonDarkModeAdapter(ButtonBase control) : base(control) { } + + internal override void PaintUp(PaintEventArgs e, CheckState state) + { + var smoothingMode = e.Graphics.SmoothingMode; + e.Graphics.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias; + + LayoutData layout = CommonLayout().Layout(); + + ButtonDarkModeRenderer.RenderButton( + e.Graphics, + Control.ClientRectangle, + Control.FlatStyle, + ToPushButtonState(state, Control.Enabled), + Control.IsDefault, + Control.Focused, + Control.ShowFocusCues, + Control.Parent?.BackColor ?? Control.BackColor, + _ => PaintImage(e, layout), + (_, textColor, drawFocus) => PaintField( + e, + layout, + PaintDarkModeRender(e).Calculate(), + textColor, + drawFocus: false) + ); + + e.Graphics.SmoothingMode = smoothingMode; + } + + internal override void PaintDown(PaintEventArgs e, CheckState state) + { + // Set the smoothing mode to AntiAlias for better rendering quality + var smoothingMode = e.Graphics.SmoothingMode; + e.Graphics.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias; + + LayoutData layout = CommonLayout().Layout(); + ButtonDarkModeRenderer.RenderButton( + e.Graphics, + Control.ClientRectangle, + Control.FlatStyle, + PushButtonState.Pressed, + Control.IsDefault, + Control.Focused, + Control.ShowFocusCues, + Control.Parent?.BackColor ?? Control.BackColor, + _ => PaintImage(e, layout), + (_, textColor, drawFocus) => PaintField( + e, + layout, + PaintDarkModeRender(e).Calculate(), + textColor, + drawFocus: false) + ); + + // Restore the original smoothing mode + e.Graphics.SmoothingMode = smoothingMode; + } + + internal override void PaintOver(PaintEventArgs e, CheckState state) + { + // Set the smoothing mode to AntiAlias for better rendering quality + var smoothingMode = e.Graphics.SmoothingMode; + e.Graphics.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias; + + LayoutData layout = CommonLayout().Layout(); + ButtonDarkModeRenderer.RenderButton( + e.Graphics, + Control.ClientRectangle, + Control.FlatStyle, + PushButtonState.Hot, + Control.IsDefault, + Control.Focused, + Control.ShowFocusCues, + Control.Parent?.BackColor ?? Control.BackColor, + _ => PaintImage(e, layout), + (_, textColor, drawFocus) => PaintField( + e, + layout, + PaintDarkModeRender(e).Calculate(), + textColor, + drawFocus: false) + ); + + // Restore the original smoothing mode + e.Graphics.SmoothingMode = smoothingMode; + } + + protected override LayoutOptions Layout(PaintEventArgs e) => CommonLayout(); + + private new LayoutOptions CommonLayout() + { + LayoutOptions layout = base.CommonLayout(); + layout.FocusOddEvenFixup = false; + layout.ShadowedText = false; + + return layout; + } + + private ColorOptions PaintDarkModeRender(IDeviceContext deviceContext) => + new(deviceContext, Control.ForeColor, Control.BackColor) + { + Enabled = Control.Enabled + }; + + private static PushButtonState ToPushButtonState(CheckState state, bool enabled) + { + return !enabled + ? PushButtonState.Disabled + : state switch + { + CheckState.Unchecked => PushButtonState.Normal, + CheckState.Checked => PushButtonState.Pressed, + CheckState.Indeterminate => PushButtonState.Hot, + _ => PushButtonState.Normal + }; + } +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonFlatAdapter.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonFlatAdapter.cs index 994bfa71cb6..9ed354acde2 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonFlatAdapter.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonInternal/ButtonFlatAdapter.cs @@ -223,23 +223,15 @@ internal override void PaintOver(PaintEventArgs e, CheckState state) Rectangle r = Control.ClientRectangle; - Color backColor; - if (!Control.FlatAppearance.MouseOverBackColor.IsEmpty) - { - backColor = Control.FlatAppearance.MouseOverBackColor; - } - else if (!Control.FlatAppearance.CheckedBackColor.IsEmpty) - { - backColor = state is CheckState.Checked or CheckState.Indeterminate - ? Control.FlatAppearance.CheckedBackColor.MixColor(colors.LowButtonFace) - : colors.LowButtonFace; - } - else - { - backColor = state is CheckState.Indeterminate - ? colors.ButtonFace.MixColor(colors.LowButtonFace) - : colors.LowButtonFace; - } + Color backColor = !Control.FlatAppearance.MouseOverBackColor.IsEmpty + ? Control.FlatAppearance.MouseOverBackColor + : !Control.FlatAppearance.CheckedBackColor.IsEmpty + ? state is CheckState.Checked or CheckState.Indeterminate + ? Control.FlatAppearance.CheckedBackColor.MixColor(colors.LowButtonFace) + : colors.LowButtonFace + : state is CheckState.Indeterminate + ? colors.ButtonFace.MixColor(colors.LowButtonFace) + : colors.LowButtonFace; PaintBackground(e, r, IsHighContrastHighlighted() ? SystemColors.Highlight : backColor); @@ -249,11 +241,14 @@ internal override void PaintOver(PaintEventArgs e, CheckState state) } PaintImage(e, layout); + PaintField( e, layout, colors, - IsHighContrastHighlighted() ? SystemColors.HighlightText : colors.WindowText, + IsHighContrastHighlighted() + ? SystemColors.HighlightText + : colors.WindowText, drawFocus: false); if (Control.Focused && Control.ShowFocusCues) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeButtonRendererCodeGenerationInstructions.md b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeButtonRendererCodeGenerationInstructions.md new file mode 100644 index 00000000000..c6a7ac4069c --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeButtonRendererCodeGenerationInstructions.md @@ -0,0 +1,88 @@ +Create a ButtonDarkModeRenderer class similar to the existing ButtonRenderer in System.Windows.Forms, but specifically designed for dark mode rendering. The class should follow the same architectural patterns as the provided ButtonRenderer while implementing Windows 11 dark mode visual styling. + +## Visual Styling Details + +### Button Shapes and Corners +1. All buttons should have rounded corners with a radius of approximately 4-5 pixels +2. The corners must be properly anti-aliased +3. For the rounded corners, implement transparency at the very edges so the parent background shows through correctly - this is crucial as the button's rectangular client area includes the corners, but visually they should appear rounded +4. Use alpha blending at the rounded edges to create a smooth transition between the button and its background + +### Color Schemes +1. Normal Button (non-default): + - Background: Dark gray (#2B2B2B) when not hovered + - Hover state: Slightly lighter gray (#3B3B3B) - this change should be clearly noticeable + - Pressed state: Even lighter gray (#4B4B4B) with a subtle inward appearance + - Disabled state: Very dark gray (#252525) with reduced opacity text (around 40% opacity) + +2. Default Button: + - Background: Accent color (purple tone similar to #6B2FBF when not hovered) + - Hover state: Slightly lighter accent color (#7C3FD0) + - Pressed state: Darker accent color (#5B1FAF) with subtle inward appearance + - Disabled state: Desaturated accent color with reduced opacity text + +3. Text Colors: + - Normal state: Light gray (#E0E0E0) for standard buttons, white (#FFFFFF) for default buttons + - Disabled state: Reduced opacity version of normal text color (around 40% opacity) + +### Border Styles +1. None border style: + - No visible border, matching Windows 11 dark mode + - Only the fill color and rounded corners are visible + +2. Single border style: + - Hair-thin (1px) border with a color that provides sufficient contrast + - For normal buttons: Medium gray (#555555) + - For default buttons: Slightly darker version of the accent color + +3. 3D border style: + - Slightly thicker border (1-2px) + - For normal buttons: Top/left slightly lighter (#555555), bottom/right slightly darker (#222222) + - For default buttons: Similar effect but with accent color variations + - On hover: Subtly enhance the contrast between the edges + - On press: Invert the light/dark edges to create an inset appearance + +### Focus Indication +1. When a button has focus, draw a 1px dotted outline 2px inside the button's edge +2. The focus indicator should follow the rounded corners +3. For normal buttons: Use a light gray (#AAAAAA) for the focus indicator +4. For default buttons: Use white (#FFFFFF) or very light purple for the focus indicator +5. Ensure the focus indicator doesn't interfere with text or image rendering + +### State Transitions +1. All state changes (hover, press, etc.) should have a subtle fade/transition effect if possible +2. The hover and pressed state visual changes should be more pronounced than in the standard renderer +3. Implement a slight scale-down effect (approximately 0.5-1% reduction) when buttons are pressed + +## Functional Requirements + +1. Maintain complete feature parity with ButtonRenderer: + - Support for text rendering with all the same formatting options + - Support for image rendering with the same positioning options + - Support for combined text and image rendering + - Proper handling of all PushButtonState values + +2. Use SystemColors appropriately: + - Rely on the dark mode SystemColors when Application.IsDarkModeEnabled is true + - If specific colors need adjustments beyond SystemColors, implement these within the renderer + +3. Transparency and background handling: + - Properly support the IsBackgroundPartiallyTransparent method + - Correctly implement the DrawParentBackground method to handle transparent areas + - Ensure transparent corners are properly rendered showing the parent background + +4. Implement all public methods present in ButtonRenderer with identical signatures and parameters + +5. Thread safety: + - Maintain the same thread-safety approach as ButtonRenderer with ThreadStatic renderer instances + +## Implementation Notes + +1. Follow the same pattern as ButtonRenderer, creating a static class with identical method signatures +2. Do not add any additional features beyond what is specified +3. Maintain backward compatibility with existing button rendering behavior +4. Handle fallback for systems where visual styles are not enabled or supported +5. Provide appropriate XML documentation following the same style as the original ButtonRenderer +6. Prioritize rendering performance, especially for the alpha-blended rounded corners + +The class should integrate seamlessly with the existing WinForms infrastructure while providing modern Windows 11 dark mode visuals. \ No newline at end of file diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRenderer.DarkModeButtonColors.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRenderer.DarkModeButtonColors.cs new file mode 100644 index 00000000000..579682bbb5b --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRenderer.DarkModeButtonColors.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Windows.Forms; + +internal static partial class ButtonDarkModeRenderer +{ + /// Cache of colors for different button states + internal static class DarkModeButtonColors + { + // Normal Button (non-default) + /// Button background color for normal state (#2B2B2B) + public static Color NormalBackgroundColor => Color.FromArgb(43, 43, 43); + + /// Button background color for hover state (#3B3B3B) + public static Color HoverBackgroundColor => Color.FromArgb(59, 59, 59); + + /// Button background color for pressed state (#4B4B4B) + public static Color PressedBackgroundColor => Color.FromArgb(75, 75, 75); + + /// Button background color for disabled state (#252525) + public static Color DisabledBackgroundColor => Color.FromArgb(37, 37, 37); + + // Default Button + /// Default button background color (#6B2FBF) + public static Color DefaultBackgroundColor => Color.FromArgb(107, 47, 191); + + /// Default button hover background color (#7C3FD0) + public static Color DefaultHoverBackgroundColor => Color.FromArgb(124, 63, 208); + + /// Default button pressed background color (#5B1FAF) + public static Color DefaultPressedBackgroundColor => Color.FromArgb(91, 31, 175); + + /// Default button disabled background color (desaturated accent) + public static Color DefaultDisabledBackgroundColor => Color.FromArgb(85, 40, 140); + + // Text Colors + /// Normal text color (#E0E0E0) + public static Color NormalTextColor => Color.FromArgb(224, 224, 224); + + /// Default button text color (#FFFFFF) + public static Color DefaultTextColor => Color.White; + + /// Disabled text color (~40% opacity, #606060) + public static Color DisabledTextColor => Color.FromArgb(96, 96, 96); + + // Border Colors + /// Button single border color (#555555) + public static Color SingleBorderColor => Color.FromArgb(85, 85, 85); + + /// Button top-left border color (#555555) + public static Color TopLeftBorderColor => Color.FromArgb(85, 85, 85); + + /// Button bottom-right border color (#222222) + public static Color BottomRightBorderColor => Color.FromArgb(34, 34, 34); + + // Focus Colors + /// Focus indicator color (#AAAAAA) + public static Color FocusIndicatorColor => Color.FromArgb(170, 170, 170); + + /// Default button focus indicator color (#FFFFFF) + public static Color DefaultFocusIndicatorColor => Color.White; + } +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRenderer.cs new file mode 100644 index 00000000000..545f97e65e3 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRenderer.cs @@ -0,0 +1,123 @@ +// 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; + +namespace System.Windows.Forms; + +/// +/// Provides methods used to render a button control in dark mode. +/// +internal static partial class ButtonDarkModeRenderer +{ + // Singleton instances for each renderer type + internal static IButtonDarkModeRenderer StandardRenderer { get; } = new StandardButtonDarkModeRenderer(); + internal static IButtonDarkModeRenderer FlatRenderer { get; } = new FlatButtonDarkModeRenderer(); + internal static IButtonDarkModeRenderer PopupRenderer { get; } = new PopupButtonDarkModeRenderer(); + internal static IButtonDarkModeRenderer SystemRenderer { get; } = new SystemButtonDarkModeRenderer(); + + // Define padding values for each renderer type + internal static Padding StandardPaddingCore { get; } = new(0); + internal static Padding FlatPaddingCore { get; } = new(0); + internal static Padding PopupPaddingCore { get; } = new(0); + internal static Padding SystemPaddingCore { get; } = new(0); + + /// + /// Returns the appropriate padding for the specified flat style. + /// + internal static Padding GetPaddingCore(FlatStyle flatStyle) => flatStyle switch + { + FlatStyle.Flat => FlatPaddingCore, + FlatStyle.Popup => PopupPaddingCore, + FlatStyle.System => SystemPaddingCore, + _ => StandardPaddingCore + }; + + /// + /// Draws a button border using a specified path and border properties, + /// with the pen aligned inward to maintain consistent dimensions. + /// + public static void DrawButtonBorder(Graphics graphics, GraphicsPath path, Color borderColor, int borderWidth) + { + using var borderPen = new Pen(borderColor, borderWidth) + { + Alignment = PenAlignment.Inset // Align pen inward + }; + + graphics.DrawPath(borderPen, path); + } + + /// + /// Clears the background with the parent's background color or the control's background color if no parent is available. + /// + /// Graphics context to draw on + private static void ClearBackground(Graphics graphics, Color parentBackgroundColor) => + graphics.Clear(parentBackgroundColor); + + /// + /// Renders a button with the specified properties and delegates for painting image and field. + /// + internal static void RenderButton( + Graphics graphics, + Rectangle bounds, + FlatStyle flatStyle, + PushButtonState state, + bool isDefault, + bool focused, + bool showFocusCues, + Color parentBackgroundColor, + Action paintImage, + Action paintField) + { + IButtonDarkModeRenderer renderer = flatStyle switch + { + FlatStyle.Flat => FlatRenderer, + FlatStyle.Popup => PopupRenderer, + FlatStyle.System => SystemRenderer, + _ => StandardRenderer + }; + + // Background over the whole Button area needs to be cleared in any case. + ClearBackground(graphics, parentBackgroundColor); + + // Use padding from ButtonDarkModeRenderer + Padding padding = GetPaddingCore(FlatStyle.System); + Rectangle paddedBounds = new( + x: bounds.X + padding.Left, + y: bounds.Y + padding.Top, + width: bounds.Width - padding.Horizontal, + height: bounds.Height - padding.Vertical); + + // Draw button background and get content bounds + Rectangle contentBounds = renderer.DrawButtonBackground(graphics, paddedBounds, state, isDefault); + + // Offset content bounds for Popup style when button is pressed + if (flatStyle == FlatStyle.Popup && state == PushButtonState.Pressed) + { + contentBounds.Offset(1, 1); + } + + // Paint image and field using the provided delegates + paintImage(contentBounds); + + paintField( + contentBounds, + renderer.GetTextColor(state, isDefault), + false); + + if (focused && flatStyle == FlatStyle.System) + { + // Focus with the system renderer is completely surrounded by a border. + renderer.DrawFocusIndicator(graphics, bounds, isDefault); + return; + } + + if (focused && showFocusCues) + { + // Draw focus indicator for other styles + renderer.DrawFocusIndicator(graphics, contentBounds, isDefault); + } + } +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRendererFactory.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRendererFactory.cs new file mode 100644 index 00000000000..00c7d55b73b --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/ButtonDarkModeRendererFactory.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Windows.Forms; + +/// +/// Factory class for creating button dark mode renderers based on FlatStyle. +/// +internal static class ButtonDarkModeRendererFactory +{ + /// + /// Gets the appropriate renderer for the specified FlatStyle. + /// + public static IButtonDarkModeRenderer GetRenderer(FlatStyle flatStyle) => + flatStyle switch + { + FlatStyle.Flat => GetFlatRenderer(), + FlatStyle.Popup => GetPopupRenderer(), + FlatStyle.System => GetSystemRenderer(), + _ => GetStandardRenderer() // FlatStyle.Standard is default + }; + + /// + /// Gets the renderer for FlatStyle.Standard. + /// + private static IButtonDarkModeRenderer GetStandardRenderer() + { + // Use ThreadStatic field to cache renderer instances + return ButtonDarkModeRenderer.StandardRenderer; + } + + /// + /// Gets the renderer for FlatStyle.Flat. + /// + private static IButtonDarkModeRenderer GetFlatRenderer() + { + return ButtonDarkModeRenderer.FlatRenderer; + } + + /// + /// Gets the renderer for FlatStyle.Popup. + /// + private static IButtonDarkModeRenderer GetPopupRenderer() + { + return ButtonDarkModeRenderer.PopupRenderer; + } + + /// + /// Gets the renderer for FlatStyle.System. + /// + private static IButtonDarkModeRenderer GetSystemRenderer() + { + return ButtonDarkModeRenderer.SystemRenderer; + } +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/FlatButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/FlatButtonDarkModeRenderer.cs new file mode 100644 index 00000000000..9ce9e889ff9 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/FlatButtonDarkModeRenderer.cs @@ -0,0 +1,128 @@ +// 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; + +namespace System.Windows.Forms; + +/// +/// Provides methods for rendering a button with Flat FlatStyle in dark mode. +/// +internal class FlatButtonDarkModeRenderer : IButtonDarkModeRenderer +{ + // Magic numbers as consts or static properties + private const int FocusIndicatorInflate = -2; + private const int BorderThicknessDefault = 2; + private const int BorderThicknessNormal = 1; + + private static Color PressedDefaultBorderColor => Color.FromArgb(200, 0, 0, 0); + private static int DefaultBorderColorRedOffset => -20; + private static int DefaultBorderColorGreenOffset => -10; + private static int DefaultBorderColorBlueOffset => -30; + + /// + /// Draws button background with flat styling (no rounded corners). + /// + public Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) + { + Color backColor = GetBackgroundColor(state, isDefault); + using var brush = backColor.GetCachedSolidBrushScope(); + graphics.FillRectangle(brush, bounds); + + DrawButtonBorder(graphics, bounds, state, isDefault); + + return bounds; + } + + /// + /// Draws a focus rectangle with dotted lines inside the button. + /// + public void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault) + { + // Create a slightly smaller rectangle for the focus indicator + Rectangle focusRect = Rectangle.Inflate(contentBounds, FocusIndicatorInflate, FocusIndicatorInflate); + + // Create dotted pen with appropriate color + Color focusColor = isDefault + ? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultFocusIndicatorColor + : ButtonDarkModeRenderer.DarkModeButtonColors.FocusIndicatorColor; + + using var focusPen = new Pen(focusColor) + { + DashStyle = DashStyle.Dot + }; + + // Draw the focus rectangle (no rounded corners) + graphics.DrawRectangle(focusPen, focusRect); + } + + /// + /// Gets the text color appropriate for the button state and type. + /// + public Color GetTextColor(PushButtonState state, bool isDefault) + { + return state == PushButtonState.Disabled + ? ButtonDarkModeRenderer.DarkModeButtonColors.DisabledTextColor + : isDefault + ? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultTextColor + : ButtonDarkModeRenderer.DarkModeButtonColors.NormalTextColor; + } + + /// + /// Gets the background color appropriate for the button state and type. + /// + private static Color GetBackgroundColor(PushButtonState state, bool isDefault) => + isDefault + ? state switch + { + PushButtonState.Normal => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor, + PushButtonState.Hot => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultHoverBackgroundColor, + PushButtonState.Pressed => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultPressedBackgroundColor, + PushButtonState.Disabled => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultDisabledBackgroundColor, + _ => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor + } + : state switch + { + PushButtonState.Normal => ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor, + PushButtonState.Hot => ButtonDarkModeRenderer.DarkModeButtonColors.HoverBackgroundColor, + PushButtonState.Pressed => ButtonDarkModeRenderer.DarkModeButtonColors.PressedBackgroundColor, + PushButtonState.Disabled => ButtonDarkModeRenderer.DarkModeButtonColors.DisabledBackgroundColor, + _ => ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor + }; + + /// + /// Draws the button border based on the current state. + /// + private static void DrawButtonBorder(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) + { + // For flat style, we need to create a GraphicsPath for the rectangle + using GraphicsPath path = new(); + path.AddRectangle(bounds); + + // For pressed state, draw a darker border + if (state == PushButtonState.Pressed) + { + Color borderColor = isDefault + ? PressedDefaultBorderColor + : ButtonDarkModeRenderer.DarkModeButtonColors.BottomRightBorderColor; + + ButtonDarkModeRenderer.DrawButtonBorder(graphics, path, borderColor, BorderThicknessNormal); + } + + // For other states, draw a single-pixel border + else if (state != PushButtonState.Disabled) + { + Color borderColor = isDefault + ? Color.FromArgb( + red: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.R + DefaultBorderColorRedOffset, + green: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.G + DefaultBorderColorGreenOffset, + blue: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.B + DefaultBorderColorBlueOffset) + : ButtonDarkModeRenderer.DarkModeButtonColors.SingleBorderColor; + + int thickness = isDefault ? BorderThicknessDefault : BorderThicknessNormal; + ButtonDarkModeRenderer.DrawButtonBorder(graphics, path, borderColor, thickness); + } + } +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/IButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/IButtonDarkModeRenderer.cs new file mode 100644 index 00000000000..7a621f80a2e --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/IButtonDarkModeRenderer.cs @@ -0,0 +1,35 @@ +// 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; + +namespace System.Windows.Forms; + +/// +/// Interface for dark mode button renderers with various FlatStyle implementations. +/// +internal interface IButtonDarkModeRenderer +{ + /// + /// Draws button background with appropriate styling. + /// + /// Graphics context to draw on + /// Bounds of the button + /// State of the button (normal, hot, pressed, disabled) + /// True if button is the default button + /// The content bounds (area inside the button for text/image) + Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, VisualStyles.PushButtonState state, bool isDefault); + + /// + /// Draws focus indicator appropriate for this style. + /// + /// Graphics context to draw on + /// Content bounds of the button + /// True if button is the default button + void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault); + + /// + /// Gets the text color appropriate for the button state and type. + /// + Color GetTextColor(VisualStyles.PushButtonState state, bool isDefault); +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/PopupButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/PopupButtonDarkModeRenderer.cs new file mode 100644 index 00000000000..f093ea5b0c8 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/PopupButtonDarkModeRenderer.cs @@ -0,0 +1,299 @@ +// 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; + +namespace System.Windows.Forms; + +/// +/// Provides methods for rendering a button with Popup FlatStyle in dark mode. +/// +internal class PopupButtonDarkModeRenderer : IButtonDarkModeRenderer +{ + // UI constants + private const int ButtonCornerRadius = 5; + private const int FocusCornerRadius = 3; + private const int FocusPadding = 2; + private const int BorderThickness = 2; + private const int ContentOffset = 1; // Offset for content when pressed + + // Border color constants + private static Color ShadowDarkColor { get; } = Color.FromArgb(40, 40, 40); // Deeper shadow + private static Color ShadowColor { get; } = Color.FromArgb(60, 60, 60); // Standard shadow + private static Color HighlightColor { get; } = Color.FromArgb(110, 110, 110); // Standard highlight + private static Color HighlightBrightColor { get; } = Color.FromArgb(130, 130, 130); // Brighter highlight + private static Color DisabledBorderDarkColor { get; } = Color.FromArgb(45, 45, 45); + private static Color DisabledBorderLightColor { get; } = Color.FromArgb(55, 55, 55); + private static Color DisabledBorderMidColor { get; } = Color.FromArgb(50, 50, 50); + + // Default border color adjustment constants + private const int DefaultBorderROffset = 30; + private const int DefaultBorderGOffset = 20; + private const int DefaultBorderBOffset = 40; + + // Cached pens for border drawing, also per-thread + [ThreadStatic] + private static Pen? s_topLeftOuterPen; + + [ThreadStatic] + private static Pen? s_bottomRightOuterPen; + + [ThreadStatic] + private static Pen? s_topLeftInnerPen; + + [ThreadStatic] + private static Pen? s_bottomRightInnerPen; + + [ThreadStatic] + private static Pen? s_defaultInnerBorderPen; + + /// + /// Draws button background with popup styling, including subtle 3D effect. + /// + public Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) + { + // Use padding from ButtonDarkModeRenderer + Padding padding = ButtonDarkModeRenderer.GetPaddingCore(FlatStyle.Popup); + Rectangle paddedBounds = Rectangle.Inflate(bounds, -padding.Left, -padding.Top); + + // Content rect will be used to position text and images + Rectangle contentBounds = Rectangle.Inflate(paddedBounds, -padding.Left, -padding.Top); + + // Adjust content position when pressed to enhance 3D effect + if (state == PushButtonState.Pressed) + { + contentBounds.Offset(ContentOffset, ContentOffset); + } + + // Create path for rounded corners + GraphicsPath path = GetRoundedRectanglePath(paddedBounds, ButtonCornerRadius); + + // Get appropriate background color based on state + Color backColor = GetBackgroundColor(state, isDefault); + + // Fill the background + using var brush = backColor.GetCachedSolidBrushScope(); + graphics.FillPath(brush, path); + + // Draw 3D effect borders + DrawButtonBorder(graphics, paddedBounds, state, isDefault); + + // Return content bounds (area inside the button for text/image) + return contentBounds; + } + + /// + /// Draws a focus rectangle with dotted lines inside the button. + /// Adjusts for the 3D effect based on the button's state. + /// + public void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault) + { + // Create a slightly smaller rectangle for the focus indicator + Rectangle focusRect = Rectangle.Inflate(contentBounds, -FocusPadding, -FocusPadding); + + // Create dotted pen with appropriate color + Color focusColor = isDefault + ? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultFocusIndicatorColor + : ButtonDarkModeRenderer.DarkModeButtonColors.FocusIndicatorColor; + + using var focusPen = new Pen(focusColor) + { + DashStyle = DashStyle.Dot + }; + + // Draw the focus rectangle with rounded corners + GraphicsPath focusPath = GetRoundedRectanglePath(focusRect, FocusCornerRadius); + graphics.DrawPath(focusPen, focusPath); + } + + /// + /// Gets the text color appropriate for the button state and type. + /// Adjusts color for 3D effect when needed. + /// + public Color GetTextColor(PushButtonState state, bool isDefault) + { + Color baseColor = state == PushButtonState.Disabled + ? ButtonDarkModeRenderer.DarkModeButtonColors.DisabledTextColor + : isDefault + ? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultTextColor + : ButtonDarkModeRenderer.DarkModeButtonColors.NormalTextColor; + + // No special color adjustment needed as the text is moved via content bounds + // in the DrawButtonBackground method for pressed state + return baseColor; + } + + /// + /// Gets the background color appropriate for the button state and type. + /// + private static Color GetBackgroundColor(PushButtonState state, bool isDefault) => + isDefault + ? state switch + { + PushButtonState.Normal => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor, + PushButtonState.Hot => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultHoverBackgroundColor, + PushButtonState.Pressed => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultPressedBackgroundColor, + PushButtonState.Disabled => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultDisabledBackgroundColor, + _ => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor + } + : state switch + { + PushButtonState.Normal => ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor, + PushButtonState.Hot => ButtonDarkModeRenderer.DarkModeButtonColors.HoverBackgroundColor, + PushButtonState.Pressed => ButtonDarkModeRenderer.DarkModeButtonColors.PressedBackgroundColor, + PushButtonState.Disabled => ButtonDarkModeRenderer.DarkModeButtonColors.DisabledBackgroundColor, + _ => ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor + }; + + /// + /// Draws the 3D effect border for the button. + /// + private static void DrawButtonBorder(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) + { + // Save original smoothing mode to restore later + SmoothingMode originalMode = graphics.SmoothingMode; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + // Create a GraphicsPath for the border to ensure consistent alignment + // The path needs to match exactly the same dimensions as the filled background + using GraphicsPath path = GetRoundedRectanglePath(bounds, ButtonCornerRadius); + + // Popup style has a 3D effect border + Rectangle borderRect = bounds; + + // Use slightly more contrasting border colors for dark mode 3D effect + Color topLeftOuter, bottomRightOuter, topLeftInner, bottomRightInner; + + if (state == PushButtonState.Pressed) + { + // In pressed state, invert the 3D effect: highlight bottom/right, shadow top/left + topLeftOuter = ShadowColor; // shadow + bottomRightOuter = HighlightColor; // highlight + topLeftInner = ShadowDarkColor; // deeper shadow + bottomRightInner = HighlightBrightColor; // brighter highlight + } + else if (state == PushButtonState.Disabled) + { + // Disabled: subtle, low-contrast border + topLeftOuter = DisabledBorderLightColor; + bottomRightOuter = DisabledBorderDarkColor; + topLeftInner = DisabledBorderMidColor; + bottomRightInner = DisabledBorderMidColor; + } + else + { + // Normal/hot: highlight top/left, shadow bottom/right + topLeftOuter = HighlightColor; // highlight + bottomRightOuter = ShadowColor; // shadow + topLeftInner = HighlightBrightColor; // brighter highlight + bottomRightInner = ShadowDarkColor; // deeper shadow + } + + // Initialize or update cached outer pens + s_topLeftOuterPen = InitializeOrUpdatePen(s_topLeftOuterPen, topLeftOuter); + s_bottomRightOuterPen = InitializeOrUpdatePen(s_bottomRightOuterPen, bottomRightOuter); + + // Draw the outer 3D border lines + // Top + graphics.DrawLine(s_topLeftOuterPen, borderRect.Left, borderRect.Top, borderRect.Right - 1, borderRect.Top); + // Left + graphics.DrawLine(s_topLeftOuterPen, borderRect.Left, borderRect.Top, borderRect.Left, borderRect.Bottom - 1); + // Bottom + graphics.DrawLine(s_bottomRightOuterPen, borderRect.Left, borderRect.Bottom - 1, borderRect.Right - 1, borderRect.Bottom - 1); + // Right + graphics.DrawLine(s_bottomRightOuterPen, borderRect.Right - 1, borderRect.Top, borderRect.Right - 1, borderRect.Bottom - 1); + + // Inner border for more depth + borderRect.Inflate(-BorderThickness, -BorderThickness); + + // Initialize or update cached inner pens + s_topLeftInnerPen = InitializeOrUpdatePen(s_topLeftInnerPen, topLeftInner); + s_bottomRightInnerPen = InitializeOrUpdatePen(s_bottomRightInnerPen, bottomRightInner); + + // Draw the inner 3D border lines + // Top + graphics.DrawLine(s_topLeftInnerPen, borderRect.Left, borderRect.Top, borderRect.Right - 1, borderRect.Top); + // Left + graphics.DrawLine(s_topLeftInnerPen, borderRect.Left, borderRect.Top, borderRect.Left, borderRect.Bottom - 1); + // Bottom + graphics.DrawLine(s_bottomRightInnerPen, borderRect.Left, borderRect.Bottom - 1, borderRect.Right - 1, borderRect.Bottom - 1); + // Right + graphics.DrawLine(s_bottomRightInnerPen, borderRect.Right - 1, borderRect.Top, borderRect.Right - 1, borderRect.Bottom - 1); + + // For default buttons, add an additional inner border + if (isDefault && state != PushButtonState.Disabled) + { + borderRect.Inflate(-BorderThickness, -BorderThickness); + Color innerBorderColor = Color.FromArgb( + Math.Max(0, ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.R - DefaultBorderROffset), + Math.Max(0, ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.G - DefaultBorderGOffset), + Math.Max(0, ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.B - DefaultBorderBOffset)); + + // Initialize or update default inner border pen + s_defaultInnerBorderPen = InitializeOrUpdatePen(s_defaultInnerBorderPen, innerBorderColor); + graphics.DrawRectangle(s_defaultInnerBorderPen, borderRect); + } + + // Restore original smoothing mode + graphics.SmoothingMode = originalMode; + } + + /// + /// Initializes a new pen if null or updates an existing pen's color. + /// + private static Pen InitializeOrUpdatePen(Pen? pen, Color color) + { + if (pen is null) + { + pen = new Pen(color) { Alignment = PenAlignment.Inset }; + } + else if (pen.Color != color) + { + pen.Color = color; + } + + return pen; + } + + /// + /// Creates a GraphicsPath for a rounded rectangle. + /// + private static GraphicsPath GetRoundedRectanglePath(Rectangle bounds, int radius) + { + int diameter = radius * 2; + Rectangle arcRect = new(bounds.Location, new Size(diameter, diameter)); + GraphicsPath path = new(); + + // Top left corner + path.AddArc( + arcRect, + startAngle: 180, + sweepAngle: 90); + + // Top right corner + arcRect.X = bounds.Right - diameter; + path.AddArc( + arcRect, + startAngle: 270, + sweepAngle: 90); + + // Bottom right corner + arcRect.Y = bounds.Bottom - diameter; + path.AddArc( + arcRect, + startAngle: 0, + sweepAngle: 90); + + // Bottom left corner + arcRect.X = bounds.Left; + path.AddArc( + arcRect, + startAngle: 90, + sweepAngle: 90); + + path.CloseFigure(); + return path; + } +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/StandardButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/StandardButtonDarkModeRenderer.cs new file mode 100644 index 00000000000..af26bda59a8 --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/StandardButtonDarkModeRenderer.cs @@ -0,0 +1,178 @@ +// 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; + +namespace System.Windows.Forms; + +/// +/// Provides methods for rendering a button with Standard FlatStyle in dark mode. +/// +internal class StandardButtonDarkModeRenderer : IButtonDarkModeRenderer +{ + // Magic numbers as consts or static properties + private const int CornerRadius = 5; + private const int FocusIndicatorCornerRadius = 4; + private const int FocusIndicatorInset = 3; + private const int PressedInnerBorderAlpha = 200; + private const int PressedInnerBorderR = 180; + private const int PressedInnerBorderG = 180; + private const int PressedInnerBorderB = 180; + private const int DefaultBorderRedOffset = -20; + private const int DefaultBorderGreenOffset = -10; + private const int DefaultBorderBlueOffset = -30; + private const int DefaultBorderThickness = 3; + private const int SingleBorderThickness = 2; + + /// + /// Draws button background with standard styling (slightly rounded corners). + /// + public Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) + { + // Standard style uses slightly rounded corners + using GraphicsPath path = GetRoundedRectanglePath(bounds, CornerRadius); + + // Get appropriate background color based on state + Color backColor = GetBackgroundColor(state, isDefault); + + // Fill the background + using var brush = backColor.GetCachedSolidBrushScope(); + graphics.FillPath(brush, path); + + // Draw border if needed + DrawButtonBorder(graphics, path, state, isDefault); + + // Return content bounds (area inside the button for text/image) + return bounds; + } + + /// + /// Draws a focus rectangle with dotted lines inside the button. + /// + public void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault) + { + // Create a slightly smaller rectangle for the focus indicator + Rectangle focusRect = Rectangle.Inflate(contentBounds, -FocusIndicatorInset, -FocusIndicatorInset); + + // Create dotted pen with appropriate color + Color focusColor = isDefault + ? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultFocusIndicatorColor + : ButtonDarkModeRenderer.DarkModeButtonColors.FocusIndicatorColor; + + using var focusPen = new Pen(focusColor) + { + DashStyle = DashStyle.Dot + }; + + // Draw the focus rectangle with rounded corners + using GraphicsPath focusPath = GetRoundedRectanglePath(focusRect, FocusIndicatorCornerRadius); + graphics.DrawPath(focusPen, focusPath); + } + + /// + /// Gets the text color appropriate for the button state and type. + /// + public Color GetTextColor(PushButtonState state, bool isDefault) + { + return state == PushButtonState.Disabled + ? ButtonDarkModeRenderer.DarkModeButtonColors.DisabledTextColor + : isDefault + ? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultTextColor + : ButtonDarkModeRenderer.DarkModeButtonColors.NormalTextColor; + } + + /// + /// Gets the background color appropriate for the button state and type. + /// + private static Color GetBackgroundColor(PushButtonState state, bool isDefault) => + isDefault + ? state switch + { + PushButtonState.Normal => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor, + PushButtonState.Hot => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultHoverBackgroundColor, + PushButtonState.Pressed => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultPressedBackgroundColor, + PushButtonState.Disabled => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultDisabledBackgroundColor, + _ => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor + } + : state switch + { + PushButtonState.Normal => ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor, + PushButtonState.Hot => ButtonDarkModeRenderer.DarkModeButtonColors.HoverBackgroundColor, + PushButtonState.Pressed => ButtonDarkModeRenderer.DarkModeButtonColors.PressedBackgroundColor, + PushButtonState.Disabled => ButtonDarkModeRenderer.DarkModeButtonColors.DisabledBackgroundColor, + _ => ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor + }; + + /// + /// Draws the button border based on the current state. + /// + private static void DrawButtonBorder(Graphics graphics, GraphicsPath path, PushButtonState state, bool isDefault) + { + // For pressed state, draw a darker inner border + if (state == PushButtonState.Pressed) + { + Color borderColor = isDefault + ? Color.FromArgb(PressedInnerBorderAlpha, PressedInnerBorderR, PressedInnerBorderG, PressedInnerBorderB) + : ButtonDarkModeRenderer.DarkModeButtonColors.BottomRightBorderColor; + + // Use the helper with inset alignment + ButtonDarkModeRenderer.DrawButtonBorder(graphics, path, borderColor, SingleBorderThickness); + } + + // For other states, draw a single-pixel border + else if (state != PushButtonState.Disabled) + { + Color borderColor = isDefault + ? Color.FromArgb( + red: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.R + DefaultBorderRedOffset, + green: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.G + DefaultBorderGreenOffset, + blue: ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.B + DefaultBorderBlueOffset) + : ButtonDarkModeRenderer.DarkModeButtonColors.SingleBorderColor; + + int thickness = isDefault ? DefaultBorderThickness : SingleBorderThickness; + ButtonDarkModeRenderer.DrawButtonBorder(graphics, path, borderColor, thickness); + } + } + + /// + /// Creates a GraphicsPath for a rounded rectangle. + /// + private static GraphicsPath GetRoundedRectanglePath(Rectangle bounds, int radius) + { + int diameter = radius * 2; + Rectangle arcRect = new(bounds.Location, new Size(diameter, diameter)); + GraphicsPath path = new(); + + // Top left corner + path.AddArc( + rect: arcRect, + startAngle: 180, + sweepAngle: 90); + + // Top right corner + arcRect.X = bounds.Right - diameter; + path.AddArc( + arcRect, + startAngle: 270, + sweepAngle: 90); + + // Bottom right corner + arcRect.Y = bounds.Bottom - diameter; + path.AddArc( + arcRect, + startAngle: 0, + sweepAngle: 90); + + // Bottom left corner + arcRect.X = bounds.Left; + path.AddArc( + arcRect, + startAngle: 90, + sweepAngle: 90); + + path.CloseFigure(); + return path; + } +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/SystemButtonDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/SystemButtonDarkModeRenderer.cs new file mode 100644 index 00000000000..b6569a86e4e --- /dev/null +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/DarkModeRenderer/SystemButtonDarkModeRenderer.cs @@ -0,0 +1,299 @@ +// 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; + +namespace System.Windows.Forms; + +/// +/// Provides methods for rendering a button with System FlatStyle in dark mode. +/// +internal class SystemButtonDarkModeRenderer : IButtonDarkModeRenderer +{ + // Magic numbers as consts or static properties + private const int CornerRadius = 8; + private const int FocusIndicatorCornerRadius = 6; + + private const int DefaultButtonBorderThickness = 0; + private const int NonDefaultButtonBorderThickness = 0; + private const int FocusedButtonBorderThickness = 3; + private const int DarkBorderGapThickness = 2; + private const int SystemStylePadding = FocusedButtonBorderThickness + DarkBorderGapThickness; + + private const int DefaultBackgroundColorOffset = 20; + + /// + /// Draws button background with system styling (larger rounded corners). + /// + public Rectangle DrawButtonBackground(Graphics graphics, Rectangle bounds, PushButtonState state, bool isDefault) + { + // Shrink for DarkBorderGap and FocusBorderThickness + Rectangle fillBounds = Rectangle.Inflate(bounds, -SystemStylePadding, -SystemStylePadding); + using GraphicsPath fillPath = GetRoundedRectanglePath(fillBounds, CornerRadius - DarkBorderGapThickness); + + // Get appropriate background color based on state + Color backColor = GetBackgroundColor(state, isDefault); + + // Fill the background + using var brush = backColor.GetCachedSolidBrushScope(); + graphics.FillPath(brush, fillPath); + + // Return content bounds (area inside the button for text/image) + return fillBounds; + } + + /// + /// Draws a focus indicator using a white thicker border. + /// + public void DrawFocusIndicator(Graphics graphics, Rectangle contentBounds, bool isDefault) + { + // We need the bottom and the right border one pixel inside the button + Rectangle focusRect = new Rectangle( + x: contentBounds.X, + y: contentBounds.Y, + width: contentBounds.Width - 1, + height: contentBounds.Height - 1); + + // Create path for the focus outline + using GraphicsPath focusPath = GetRoundedRectanglePath(focusRect, FocusIndicatorCornerRadius); + + // System style uses a solid white border instead of dotted lines + using var focusPen = Color.White.GetCachedPenScope(FocusedButtonBorderThickness); + graphics.DrawPath(focusPen, focusPath); + } + + /// + /// Gets the text color appropriate for the button state and type. + /// + public Color GetTextColor(PushButtonState state, bool isDefault) + { + return state == PushButtonState.Disabled + ? ButtonDarkModeRenderer.DarkModeButtonColors.DisabledTextColor + : isDefault + ? ButtonDarkModeRenderer.DarkModeButtonColors.DefaultTextColor + : ButtonDarkModeRenderer.DarkModeButtonColors.NormalTextColor; + } + + /// + /// Gets the background color appropriate for the button state and type. + /// + private static Color GetBackgroundColor(PushButtonState state, bool isDefault) + { + // For default button in System style, use a darker version of the background color + return isDefault + ? state switch + { + PushButtonState.Normal => Color.FromArgb( + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.R - DefaultBackgroundColorOffset, + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.G - DefaultBackgroundColorOffset, + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor.B - DefaultBackgroundColorOffset), + PushButtonState.Hot => Color.FromArgb( + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultHoverBackgroundColor.R - DefaultBackgroundColorOffset, + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultHoverBackgroundColor.G - DefaultBackgroundColorOffset, + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultHoverBackgroundColor.B - DefaultBackgroundColorOffset), + PushButtonState.Pressed => Color.FromArgb( + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultPressedBackgroundColor.R - DefaultBackgroundColorOffset, + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultPressedBackgroundColor.G - DefaultBackgroundColorOffset, + ButtonDarkModeRenderer.DarkModeButtonColors.DefaultPressedBackgroundColor.B - DefaultBackgroundColorOffset), + PushButtonState.Disabled => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultDisabledBackgroundColor, + _ => ButtonDarkModeRenderer.DarkModeButtonColors.DefaultBackgroundColor + } + : state switch + { + PushButtonState.Normal => ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor, + PushButtonState.Hot => ButtonDarkModeRenderer.DarkModeButtonColors.HoverBackgroundColor, + PushButtonState.Pressed => ButtonDarkModeRenderer.DarkModeButtonColors.PressedBackgroundColor, + PushButtonState.Disabled => ButtonDarkModeRenderer.DarkModeButtonColors.DisabledBackgroundColor, + _ => ButtonDarkModeRenderer.DarkModeButtonColors.NormalBackgroundColor + }; + } + + /// + /// Draws the button border based on the current state, using anti-aliasing and an additional inner border. + /// + public static void DrawButtonBorder( + Graphics graphics, + Rectangle bounds, + PushButtonState state, + bool isDefault, + bool isFocused) + { + // Outer border path + Rectangle borderRect = Rectangle.Inflate(bounds, -SystemStylePadding, -SystemStylePadding); + using GraphicsPath borderPath = GetRoundedRectanglePath(borderRect, CornerRadius); + + // We need to implement a subtle 3d effect around the already + // painted filling. We do this by drawing a border with a 1px pen, + // which is - with brighter colors - top and right a bit darker than + // the fill color, and bottom and left yet another bit darker. + // + // For darker fill colors, we use a slightly lighter color for the top and right sides, + // and a yet bit lighter color for the bottom and left sides. + // We never change the color for the borders, we just adjust the brightness. + + // Get base color for the border based on button state and type + Color backColor = GetBackgroundColor(state, isDefault); + + // Skip border drawing for disabled state + if (state == PushButtonState.Disabled) + { + return; + } + + // For normal colors, make borders darker + // For dark colors, make borders lighter + bool isDarkColor = backColor.GetBrightness() < 0.5; + + // Top-left border (slightly lighter/darker) + Color topLeftColor = isDarkColor + ? ControlPaint.Light(backColor, 0.2f) + : ControlPaint.Dark(backColor, 0.1f); + + // Bottom-right border (more pronounced light/dark) + Color bottomRightColor = isDarkColor + ? ControlPaint.Light(backColor, 0.4f) + : ControlPaint.Dark(backColor, 0.2f); + + // Determine border thickness + int borderThickness = isDefault ? DefaultButtonBorderThickness : NonDefaultButtonBorderThickness; + + if (isFocused) + { + // Don't draw regular border if focus border is already drawn + return; + } + + // Save graphics state to restore anti-aliasing settings later + GraphicsState graphicState = graphics.Save(); + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + // Draw top-left border segment + using var topLeftPen = topLeftColor.GetCachedPenScope(borderThickness); + graphics.DrawPath(topLeftPen, GetTopLeftSegmentPath(borderRect, CornerRadius)); + + // Draw bottom-right border segment + using var bottomRightPen = bottomRightColor.GetCachedPenScope(borderThickness); + graphics.DrawPath(bottomRightPen, GetBottomRightSegmentPath(borderRect, CornerRadius)); + + // Restore graphics state + graphics.Restore(graphicState); + } + + /// + /// Creates a path for the top and left segments of a rounded rectangle. + /// + private static GraphicsPath GetTopLeftSegmentPath(Rectangle bounds, int radius) + { + GraphicsPath path = new(); + int diameter = radius * 2; + + // Top left corner arc + Rectangle arcRect = new(bounds.Location, new Size(diameter, diameter)); + path.AddArc(arcRect, 180, 90); + + // Top line + path.AddLine(bounds.Left + radius, bounds.Top, bounds.Right - radius, bounds.Top); + + // Top right corner arc (just the top portion) + arcRect.X = bounds.Right - diameter; + path.AddArc(arcRect, 270, 45); + + // Path back to middle of right side + path.AddLine( + bounds.Right - (int)(radius * Math.Sin(Math.PI / 4)), + bounds.Top + (int)(radius * (1 - Math.Cos(Math.PI / 4))), + bounds.Right, + bounds.Top + bounds.Height / 2); + + // Path to middle of bottom + path.AddLine(bounds.Right, bounds.Top + bounds.Height / 2, bounds.Left + bounds.Width / 2, bounds.Bottom); + + // Path to bottom left corner + path.AddLine(bounds.Left + bounds.Width / 2, bounds.Bottom, bounds.Left, bounds.Bottom - bounds.Height / 2); + + // Path back to start + path.AddLine(bounds.Left, bounds.Bottom - bounds.Height / 2, bounds.Left, bounds.Top + radius); + + return path; + } + + /// + /// Creates a path for the bottom and right segments of a rounded rectangle. + /// + private static GraphicsPath GetBottomRightSegmentPath(Rectangle bounds, int radius) + { + GraphicsPath path = new(); + int diameter = radius * 2; + + // Start from middle of top edge + path.AddLine(bounds.Left + bounds.Width / 2, bounds.Top, bounds.Right, bounds.Top + bounds.Height / 2); + + // Right line + path.AddLine(bounds.Right, bounds.Top + bounds.Height / 2, bounds.Right, bounds.Bottom - radius); + + // Bottom right corner arc + Rectangle arcRect = new(bounds.Right - diameter, bounds.Bottom - diameter, diameter, diameter); + path.AddArc(arcRect, 0, 90); + + // Bottom line + path.AddLine(bounds.Right - radius, bounds.Bottom, bounds.Left + radius, bounds.Bottom); + + // Bottom left corner arc + arcRect.X = bounds.Left; + path.AddArc(arcRect, 90, 45); + + // Path back to middle of left side + path.AddLine( + bounds.Left + (int)(radius * (1 - Math.Cos(Math.PI/4))), + bounds.Bottom - (int)(radius * Math.Sin(Math.PI/4)), + bounds.Left, + bounds.Top + bounds.Height / 2); + + // Close the path back to start + path.AddLine(bounds.Left, bounds.Top + bounds.Height / 2, bounds.Left + bounds.Width / 2, bounds.Top); + + return path; + } + + /// + /// Creates a GraphicsPath for a rounded rectangle. + /// + private static GraphicsPath GetRoundedRectanglePath(Rectangle bounds, int radius) + { + GraphicsPath path = new(); + int diameter = radius * 2; + Rectangle arcRect = new(bounds.Location, new Size(diameter, diameter)); + + // Top left corner + path.AddArc( + rect: arcRect, + startAngle: 180, + sweepAngle: 90); + + // Top right corner + arcRect.X = bounds.Right - diameter; + path.AddArc( + rect: arcRect, + startAngle: 270, + sweepAngle: 90); + + // Bottom right corner + arcRect.Y = bounds.Bottom - diameter; + path.AddArc( + rect: arcRect, + startAngle: 0, + sweepAngle: 90); + + // Bottom left corner + arcRect.X = bounds.Left; + path.AddArc( + rect: arcRect, + startAngle: 90, + sweepAngle: 90); + + path.CloseFigure(); + return path; + } +} diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripSystemDarkModeRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripSystemDarkModeRenderer.cs index 5df04054885..b1b1745d917 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripSystemDarkModeRenderer.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripSystemDarkModeRenderer.cs @@ -206,17 +206,17 @@ protected override void OnRenderToolStripBorder(ToolStripRenderEventArgs e) Debug.Assert(toolStripDropDown is not null, $"ToolStripDropDown cannot be null in {nameof(OnRenderToolStripBorder)}."); + using var borderPen = GetDarkModePen(SystemColors.ControlDark); + if (toolStripDropDown.DropShadowEnabled) { bounds.Width -= 1; bounds.Height -= 1; - using var borderPen = GetDarkModePen(SystemColors.ControlDark); g.DrawRectangle(borderPen, bounds); } else { - using var borderPen = GetDarkModePen(SystemColors.ControlDark); g.DrawRectangle(borderPen, bounds); } } diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripSystemRenderer.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripSystemRenderer.cs index 4dfa8b522d3..53063c8032e 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripSystemRenderer.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/ToolStrips/ToolStripSystemRenderer.cs @@ -51,7 +51,7 @@ internal ToolStripRenderer DarkModeRenderer { get { - _toolStripDarkModeRenderer ??= new ToolStripSystemDarkModeRenderer(isSystemDefaultAlternative: false); + _toolStripDarkModeRenderer ??= new ToolStripSystemDarkModeRenderer(isSystemDefaultAlternative: true); return _toolStripDarkModeRenderer; } }