From 4800f1995686088b6b4a1035a92a87a2f87b73e9 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Thu, 25 Jul 2024 17:02:28 -0700 Subject: [PATCH 1/2] Add alternate system color experimental feature Allows overriding KnownColor system values with an alternate set, which in the initial iteration is "dark mode" colors. Enables "dark mode" features in Windows Forms. This is from the approved part of https://github.com/dotnet/winforms/issues/7641 with further naming iteration done offline with API review. --- docs/project/list-of-diagnostics.md | 1 + .../Windows/User32/Interop.HIGHCONTRASTW.cs | 32 +++ .../User32/Interop.SystemParametersInfo.cs | 3 +- .../Common/src/System/Experimentals.cs | 3 + .../System.Drawing.Primitives.sln | 26 ++- .../ref/System.Drawing.Primitives.cs | 2 + .../src/System.Drawing.Primitives.csproj | 5 + .../src/System/Drawing/KnownColorTable.cs | 185 +++++++++++------- .../src/System/Drawing/SystemColors.cs | 23 +++ .../tests/ColorTests.cs | 26 ++- 10 files changed, 218 insertions(+), 88 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/User32/Interop.HIGHCONTRASTW.cs diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 6f3c52063df44..3d6c8e6d53bb9 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -293,3 +293,4 @@ Diagnostic id values for experimental APIs must not be recycled, as that could s | Diagnostic ID | Introduced | Removed | Description | | :---------------- | ---------: | ------: | :---------- | | __`SYSLIB5001`__ | .NET 9 | TBD | `Tensor` and related APIs in System.Numerics.Tensors are experimental in .NET 9 | +| __`SYSLIB5002`__ | .NET 9 | TBD | `SystemColors` alternate colors are experimental in .NET 9 | diff --git a/src/libraries/Common/src/Interop/Windows/User32/Interop.HIGHCONTRASTW.cs b/src/libraries/Common/src/Interop/Windows/User32/Interop.HIGHCONTRASTW.cs new file mode 100644 index 0000000000000..25c9691d325e1 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/User32/Interop.HIGHCONTRASTW.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class User32 + { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public unsafe partial struct HIGHCONTRASTW + { + internal uint cbSize; + internal HIGHCONTRASTW_FLAGS dwFlags; + internal void* lpszDefaultScheme; + } + + [Flags] + public enum HIGHCONTRASTW_FLAGS : uint + { + HCF_HIGHCONTRASTON = 0x00000001, + HCF_AVAILABLE = 0x00000002, + HCF_HOTKEYACTIVE = 0x00000004, + HCF_CONFIRMHOTKEY = 0x00000008, + HCF_HOTKEYSOUND = 0x00000010, + HCF_INDICATOR = 0x00000020, + HCF_HOTKEYAVAILABLE = 0x00000040, + HCF_OPTION_NOTHEMECHANGE = 0x00001000 + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/User32/Interop.SystemParametersInfo.cs b/src/libraries/Common/src/Interop/Windows/User32/Interop.SystemParametersInfo.cs index 5bca617b9b6f9..7d4fc2856cffc 100644 --- a/src/libraries/Common/src/Interop/Windows/User32/Interop.SystemParametersInfo.cs +++ b/src/libraries/Common/src/Interop/Windows/User32/Interop.SystemParametersInfo.cs @@ -11,7 +11,8 @@ internal static partial class User32 public enum SystemParametersAction : uint { SPI_GETICONTITLELOGFONT = 0x1F, - SPI_GETNONCLIENTMETRICS = 0x29 + SPI_GETNONCLIENTMETRICS = 0x29, + SPI_GETHIGHCONTRAST = 0x42 } [LibraryImport(Libraries.User32)] diff --git a/src/libraries/Common/src/System/Experimentals.cs b/src/libraries/Common/src/System/Experimentals.cs index befcdca48327d..39ef81d8462a2 100644 --- a/src/libraries/Common/src/System/Experimentals.cs +++ b/src/libraries/Common/src/System/Experimentals.cs @@ -19,6 +19,9 @@ internal static class Experimentals // Tensor and related APIs are marked as [Experimental] in .NET 9 internal const string TensorTDiagId = "SYSLIB5001"; + // SystemColors alternate colors are marked as [Experimental] in .NET 9 + internal const string SystemColorsDiagId = "SYSLIB5002"; + // When adding a new diagnostic ID, add it to the table in docs\project\list-of-diagnostics.md as well. // Keep new const identifiers above this comment. } diff --git a/src/libraries/System.Drawing.Primitives/System.Drawing.Primitives.sln b/src/libraries/System.Drawing.Primitives/System.Drawing.Primitives.sln index d6a4e28428dd1..3e45053326007 100644 --- a/src/libraries/System.Drawing.Primitives/System.Drawing.Primitives.sln +++ b/src/libraries/System.Drawing.Primitives/System.Drawing.Primitives.sln @@ -1,4 +1,8 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35111.106 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{39205290-06C5-468E-B5C9-D9C5737909EE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Collections.NonGeneric", "..\System.Collections.NonGeneric\ref\System.Collections.NonGeneric.csproj", "{4B06D595-C5B5-49E3-BC5D-7CA55B52D91B}" @@ -45,11 +49,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{722DCDC1-751 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{8349AD04-5979-4347-A869-7F76B043453E}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "tools\gen", "{1ACFEEFC-7E61-483E-9CAF-1EB2DFC11558}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{1ACFEEFC-7E61-483E-9CAF-1EB2DFC11558}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "tools\src", "{D99A011B-F48D-4E22-9C5B-050294758522}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D99A011B-F48D-4E22-9C5B-050294758522}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{5CB40A8A-59D5-4E57-8F9A-D716C5BDFDC7}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{5CB40A8A-59D5-4E57-8F9A-D716C5BDFDC7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{0205F064-E9AB-408E-BC47-D1EB62C753C7}" EndProject @@ -141,29 +145,33 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {39205290-06C5-468E-B5C9-D9C5737909EE} = {B004DC3D-DA89-4C76-8D15-327CCDB6D7C0} - {515B6C1E-757F-497E-9707-37B5822FFC9A} = {B004DC3D-DA89-4C76-8D15-327CCDB6D7C0} {4B06D595-C5B5-49E3-BC5D-7CA55B52D91B} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} {6ED31F56-EBDB-4E4D-A6D5-8F6078B1A241} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} {9D080C1F-6334-4C14-BABF-D0D9132C0D83} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} {F92BFBC7-A148-44D5-A977-01926068615D} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} {D2E753F4-34A3-4641-9C0F-53539147CCF2} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} + {80A68643-0E37-4525-BF06-F50C3BF7B867} = {722DCDC1-7510-4B50-93F4-51E5FF833B2A} + {515B6C1E-757F-497E-9707-37B5822FFC9A} = {B004DC3D-DA89-4C76-8D15-327CCDB6D7C0} {731DA2F5-004D-46F0-8751-4945163E8DF4} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} {D8C6E8A8-4E73-42CD-A310-C63B64844A4C} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} - {FD462F99-C9F6-4D3E-B080-F5E337E8F2F8} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} - {80A68643-0E37-4525-BF06-F50C3BF7B867} = {722DCDC1-7510-4B50-93F4-51E5FF833B2A} {F06CCE8D-0066-4B17-8EF0-162AE711F6D8} = {8349AD04-5979-4347-A869-7F76B043453E} {315E574D-6A26-4A89-83B5-1C948F3C83D2} = {8349AD04-5979-4347-A869-7F76B043453E} {9ECCC771-064F-403E-8E0E-7B049AAFAD36} = {8349AD04-5979-4347-A869-7F76B043453E} + {FD462F99-C9F6-4D3E-B080-F5E337E8F2F8} = {F2D0660B-B4A3-4039-A47D-63F9D1CE19B6} {D5E974B9-EB58-4D32-A4F4-C31559436DEA} = {1ACFEEFC-7E61-483E-9CAF-1EB2DFC11558} {D2D8DF0A-836A-4521-A9F5-349F91E87046} = {1ACFEEFC-7E61-483E-9CAF-1EB2DFC11558} - {1ACFEEFC-7E61-483E-9CAF-1EB2DFC11558} = {0205F064-E9AB-408E-BC47-D1EB62C753C7} {198C17DB-F65F-4165-996B-128E3123A6CF} = {D99A011B-F48D-4E22-9C5B-050294758522} {A92D7FC7-E3A9-4260-8F25-7FCF3AA900DC} = {D99A011B-F48D-4E22-9C5B-050294758522} - {D99A011B-F48D-4E22-9C5B-050294758522} = {0205F064-E9AB-408E-BC47-D1EB62C753C7} {0A6457CF-5932-44F6-9983-978FA163B3A5} = {5CB40A8A-59D5-4E57-8F9A-D716C5BDFDC7} + {1ACFEEFC-7E61-483E-9CAF-1EB2DFC11558} = {0205F064-E9AB-408E-BC47-D1EB62C753C7} + {D99A011B-F48D-4E22-9C5B-050294758522} = {0205F064-E9AB-408E-BC47-D1EB62C753C7} {5CB40A8A-59D5-4E57-8F9A-D716C5BDFDC7} = {0205F064-E9AB-408E-BC47-D1EB62C753C7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E2DD25F1-FA29-41D5-AB37-65DDC6A49304} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{a92d7fc7-e3a9-4260-8f25-7fcf3aa900dc}*SharedItemsImports = 5 + ..\..\tools\illink\src\ILLink.Shared\ILLink.Shared.projitems*{d2d8df0a-836a-4521-a9f5-349f91e87046}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/libraries/System.Drawing.Primitives/ref/System.Drawing.Primitives.cs b/src/libraries/System.Drawing.Primitives/ref/System.Drawing.Primitives.cs index 52f5b2f54bd17..4ab096f9be624 100644 --- a/src/libraries/System.Drawing.Primitives/ref/System.Drawing.Primitives.cs +++ b/src/libraries/System.Drawing.Primitives/ref/System.Drawing.Primitives.cs @@ -625,5 +625,7 @@ public static partial class SystemColors public static System.Drawing.Color Window { get { throw null; } } public static System.Drawing.Color WindowFrame { get { throw null; } } public static System.Drawing.Color WindowText { get { throw null; } } + [System.Diagnostics.CodeAnalysis.Experimental("SYSLIB5002", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + public static bool UseAlternativeColorSet { get { throw null; } set { } } } } diff --git a/src/libraries/System.Drawing.Primitives/src/System.Drawing.Primitives.csproj b/src/libraries/System.Drawing.Primitives/src/System.Drawing.Primitives.csproj index b0db347a20569..58b5852c2c916 100644 --- a/src/libraries/System.Drawing.Primitives/src/System.Drawing.Primitives.csproj +++ b/src/libraries/System.Drawing.Primitives/src/System.Drawing.Primitives.csproj @@ -29,6 +29,7 @@ Link="System\Drawing\ColorConverterCommon.cs" /> + @@ -36,8 +37,12 @@ Link="Common\Interop\Windows\Interop.Libraries.cs" /> + + diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/KnownColorTable.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/KnownColorTable.cs index 7ac095098ca37..67cda2762d45d 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/KnownColorTable.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/KnownColorTable.cs @@ -18,32 +18,32 @@ internal static class KnownColorTable 0, // "System" colors, Part 1 #if FEATURE_WINDOWS_SYSTEM_COLORS - (uint)(byte)Interop.User32.Win32SystemColors.ActiveBorder, - (uint)(byte)Interop.User32.Win32SystemColors.ActiveCaption, - (uint)(byte)Interop.User32.Win32SystemColors.ActiveCaptionText, - (uint)(byte)Interop.User32.Win32SystemColors.AppWorkspace, - (uint)(byte)Interop.User32.Win32SystemColors.Control, - (uint)(byte)Interop.User32.Win32SystemColors.ControlDark, - (uint)(byte)Interop.User32.Win32SystemColors.ControlDarkDark, - (uint)(byte)Interop.User32.Win32SystemColors.ControlLight, - (uint)(byte)Interop.User32.Win32SystemColors.ControlLightLight, - (uint)(byte)Interop.User32.Win32SystemColors.ControlText, - (uint)(byte)Interop.User32.Win32SystemColors.Desktop, - (uint)(byte)Interop.User32.Win32SystemColors.GrayText, - (uint)(byte)Interop.User32.Win32SystemColors.Highlight, - (uint)(byte)Interop.User32.Win32SystemColors.HighlightText, - (uint)(byte)Interop.User32.Win32SystemColors.HotTrack, - (uint)(byte)Interop.User32.Win32SystemColors.InactiveBorder, - (uint)(byte)Interop.User32.Win32SystemColors.InactiveCaption, - (uint)(byte)Interop.User32.Win32SystemColors.InactiveCaptionText, - (uint)(byte)Interop.User32.Win32SystemColors.Info, - (uint)(byte)Interop.User32.Win32SystemColors.InfoText, - (uint)(byte)Interop.User32.Win32SystemColors.Menu, - (uint)(byte)Interop.User32.Win32SystemColors.MenuText, - (uint)(byte)Interop.User32.Win32SystemColors.ScrollBar, - (uint)(byte)Interop.User32.Win32SystemColors.Window, - (uint)(byte)Interop.User32.Win32SystemColors.WindowFrame, - (uint)(byte)Interop.User32.Win32SystemColors.WindowText, + (byte)Interop.User32.Win32SystemColors.ActiveBorder, + (byte)Interop.User32.Win32SystemColors.ActiveCaption, + (byte)Interop.User32.Win32SystemColors.ActiveCaptionText, + (byte)Interop.User32.Win32SystemColors.AppWorkspace, + (byte)Interop.User32.Win32SystemColors.Control, + (byte)Interop.User32.Win32SystemColors.ControlDark, + (byte)Interop.User32.Win32SystemColors.ControlDarkDark, + (byte)Interop.User32.Win32SystemColors.ControlLight, + (byte)Interop.User32.Win32SystemColors.ControlLightLight, + (byte)Interop.User32.Win32SystemColors.ControlText, + (byte)Interop.User32.Win32SystemColors.Desktop, + (byte)Interop.User32.Win32SystemColors.GrayText, + (byte)Interop.User32.Win32SystemColors.Highlight, + (byte)Interop.User32.Win32SystemColors.HighlightText, + (byte)Interop.User32.Win32SystemColors.HotTrack, + (byte)Interop.User32.Win32SystemColors.InactiveBorder, + (byte)Interop.User32.Win32SystemColors.InactiveCaption, + (byte)Interop.User32.Win32SystemColors.InactiveCaptionText, + (byte)Interop.User32.Win32SystemColors.Info, + (byte)Interop.User32.Win32SystemColors.InfoText, + (byte)Interop.User32.Win32SystemColors.Menu, + (byte)Interop.User32.Win32SystemColors.MenuText, + (byte)Interop.User32.Win32SystemColors.ScrollBar, + (byte)Interop.User32.Win32SystemColors.Window, + (byte)Interop.User32.Win32SystemColors.WindowFrame, + (byte)Interop.User32.Win32SystemColors.WindowText, #else // Hard-coded constants, based on default Windows settings. 0xFFD4D0C8, // ActiveBorder @@ -217,13 +217,13 @@ internal static class KnownColorTable 0xFF9ACD32, // YellowGreen #if FEATURE_WINDOWS_SYSTEM_COLORS // "System" colors, Part 2 - (uint)(byte)Interop.User32.Win32SystemColors.ButtonFace, - (uint)(byte)Interop.User32.Win32SystemColors.ButtonHighlight, - (uint)(byte)Interop.User32.Win32SystemColors.ButtonShadow, - (uint)(byte)Interop.User32.Win32SystemColors.GradientActiveCaption, - (uint)(byte)Interop.User32.Win32SystemColors.GradientInactiveCaption, - (uint)(byte)Interop.User32.Win32SystemColors.MenuBar, - (uint)(byte)Interop.User32.Win32SystemColors.MenuHighlight, + (byte)Interop.User32.Win32SystemColors.ButtonFace, + (byte)Interop.User32.Win32SystemColors.ButtonHighlight, + (byte)Interop.User32.Win32SystemColors.ButtonShadow, + (byte)Interop.User32.Win32SystemColors.GradientActiveCaption, + (byte)Interop.User32.Win32SystemColors.GradientInactiveCaption, + (byte)Interop.User32.Win32SystemColors.MenuBar, + (byte)Interop.User32.Win32SystemColors.MenuHighlight, #else 0xFFF0F0F0, // ButtonFace 0xFFFFFFFF, // ButtonHighlight @@ -242,8 +242,8 @@ internal static class KnownColorTable [ // "not a known color" KnownColorKindUnknown, + // "System" colors, Part 1 -#if FEATURE_WINDOWS_SYSTEM_COLORS KnownColorKindSystem, // ActiveBorder KnownColorKindSystem, // ActiveCaption KnownColorKindSystem, // ActiveCaptionText @@ -270,35 +270,7 @@ internal static class KnownColorTable KnownColorKindSystem, // Window KnownColorKindSystem, // WindowFrame KnownColorKindSystem, // WindowText -#else - // Hard-coded constants, based on default Windows settings. - KnownColorKindSystem, // ActiveBorder - KnownColorKindSystem, // ActiveCaption - KnownColorKindSystem, // ActiveCaptionText - KnownColorKindSystem, // AppWorkspace - KnownColorKindSystem, // Control - KnownColorKindSystem, // ControlDark - KnownColorKindSystem, // ControlDarkDark - KnownColorKindSystem, // ControlLight - KnownColorKindSystem, // ControlLightLight - KnownColorKindSystem, // ControlText - KnownColorKindSystem, // Desktop - KnownColorKindSystem, // GrayText - KnownColorKindSystem, // Highlight - KnownColorKindSystem, // HighlightText - KnownColorKindSystem, // HotTrack - KnownColorKindSystem, // InactiveBorder - KnownColorKindSystem, // InactiveCaption - KnownColorKindSystem, // InactiveCaptionText - KnownColorKindSystem, // Info - KnownColorKindSystem, // InfoText - KnownColorKindSystem, // Menu - KnownColorKindSystem, // MenuText - KnownColorKindSystem, // ScrollBar - KnownColorKindSystem, // Window - KnownColorKindSystem, // WindowFrame - KnownColorKindSystem, // WindowText -#endif + // "Web" Colors, Part 1 KnownColorKindWeb, // Transparent KnownColorKindWeb, // AliceBlue @@ -441,16 +413,8 @@ internal static class KnownColorTable KnownColorKindWeb, // WhiteSmoke KnownColorKindWeb, // Yellow KnownColorKindWeb, // YellowGreen -#if FEATURE_WINDOWS_SYSTEM_COLORS - // "System" colors, Part 2 - KnownColorKindSystem, // ButtonFace - KnownColorKindSystem, // ButtonHighlight - KnownColorKindSystem, // ButtonShadow - KnownColorKindSystem, // GradientActiveCaption - KnownColorKindSystem, // GradientInactiveCaption - KnownColorKindSystem, // MenuBar - KnownColorKindSystem, // MenuHighlight -#else + + // "System" colors, Part 1 KnownColorKindSystem, // ButtonFace KnownColorKindSystem, // ButtonHighlight KnownColorKindSystem, // ButtonShadow @@ -458,11 +422,51 @@ internal static class KnownColorTable KnownColorKindSystem, // GradientInactiveCaption KnownColorKindSystem, // MenuBar KnownColorKindSystem, // MenuHighlight -#endif + // "Web" colors, Part 2 KnownColorKindWeb, // RebeccaPurple ]; + private static ReadOnlySpan AlternateSystemColors => + [ + 0, // To align with KnownColor.ActiveBorder = 1 + + // Existing New + 0xFF464646, // FFB4B4B4 - FF464646: ActiveBorder - Dark gray + 0xFF3C5F78, // FF99B4D1 - FF3C5F78: ActiveCaption - Highlighted Text Background + 0xFFFFFFFF, // FF000000 - FFBEBEBE: ActiveCaptionText - White + 0xFF3C3C3C, // FFABABAB - FF3C3C3C: AppWorkspace - Panel Background + 0xFF202020, // FFF0F0F0 - FF373737: Control - Normal Panel/Windows Background + 0xFF4A4A4A, // FFA0A0A0 - FF464646: ControlDark - A lighter gray for dark mode + 0xFF5A5A5A, // FF696969 - FF5A5A5A: ControlDarkDark - An even lighter gray for dark mode + 0xFF2E2E2E, // FFE3E3E3 - FF2E2E2E: ControlLight - Unfocused Textbox Background + 0xFF1F1F1F, // FFFFFFFF - FF1F1F1F: ControlLightLight - Focused Textbox Background + 0xFFFFFFFF, // FF000000 - FFFFFFFF: ControlText - Control Forecolor and Text Color + 0xFF101010, // FF000000 - FF101010: Desktop - Black + 0xFF969696, // FF6D6D6D - FF969696: GrayText - Prompt Text Focused TextBox + 0xFF2864B4, // FF0078D7 - FF2864B4: Highlight - Highlighted Panel in DarkMode + 0xFF000000, // FFFFFFFF - FF000000: HighlightText - White + 0xFF2D5FAF, // FF0066CC - FF2D5FAF: HotTrack - Background of the ToggleSwitch + 0xFF3C3F41, // FFF4F7FC - FF3C3F41: InactiveBorder - Dark gray + 0xFF374B5A, // FFBFCBDD - FF374B5A: InactiveCaption - Highlighted Panel in DarkMode + 0xFFBEBEBE, // FF000000 - FFBEBEBE: InactiveCaptionText - Middle Dark Panel + 0xFF50503C, // FFFFFFE1 - FF50503C: Info - Link Label + 0xFFBEBEBE, // FF000000 - FFBEBEBE: InfoText - Prompt Text Color + 0xFF373737, // FFF0F0F0 - FF373737: Menu - Normal Menu Background + 0xFFF0F0F0, // FF000000 - FFF0F0F0: MenuText - White + 0xFF505050, // FFC8C8C8 - FF505050: ScrollBar - Scrollbars and Scrollbar Arrows + 0xFF323232, // FFFFFFFF - FF323232: Window - Window Background + 0xFF282828, // FF646464 - FF282828: WindowFrame - White + 0xFFF0F0F0, // FF000000 - FFF0F0F0: WindowText - White + 0xFF202020, // FFF0F0F0 - FF373737: ButtonFace - Same as Window Background + 0xFF101010, // FFFFFFFF - FF101010: ButtonHighlight - White + 0xFF464646, // FFA0A0A0 - FF464646: ButtonShadow - Same as Scrollbar Elements + 0XFF416482, // FFB9D1EA - FF416482: GradientActiveCaption - Same as Highlighted Text Background + 0xFF557396, // FFD7E4F2 - FF557396: GradientInactiveCaption - Same as Highlighted Panel in DarkMode + 0xFF373737, // FFF0F0F0 - FF373737: MenuBar - Same as Normal Menu Background + 0xFF2A80D2 // FF3399FF - FF2A80D2: MenuHighlight - Same as Highlighted Menu Background + ]; + internal static Color ArgbToKnownColor(uint argb) { Debug.Assert((argb & Color.ARGBAlphaMask) == Color.ARGBAlphaMask); @@ -495,14 +499,43 @@ public static uint GetSystemColorArgb(KnownColor color) { Debug.Assert(Color.IsKnownColorSystem(color)); - return ColorTranslator.COLORREFToARGB(Interop.User32.GetSysColor((byte)ColorValueTable[(int)color])); + if (!SystemColors.s_useAlternativeColorSet || HighContrastEnabled()) + { + return ColorTranslator.COLORREFToARGB(Interop.User32.GetSysColor((byte)ColorValueTable[(int)color])); + } + + int index = color <= KnownColor.WindowText ? (int)color : (int)color - (int)KnownColor.ButtonFace + (int)KnownColor.WindowText + 1; + return AlternateSystemColors[index]; + } + + private static unsafe bool HighContrastEnabled() + { + Interop.User32.HIGHCONTRASTW highContrast = default; + + // Note that the documentation for HIGHCONTRASTW says that the lpszDefaultScheme member needs to be + // freed, but this is incorrect. No internal users ever free the pointer and the pointer never changes. + highContrast.cbSize = (uint)sizeof(Interop.User32.HIGHCONTRASTW); + bool success = Interop.User32.SystemParametersInfoW( + Interop.User32.SystemParametersAction.SPI_GETHIGHCONTRAST, + highContrast.cbSize, + &highContrast, + 0); // This has no meaning when getting values + + return success && highContrast.dwFlags.HasFlag(Interop.User32.HIGHCONTRASTW_FLAGS.HCF_HIGHCONTRASTON); } #else + public static uint GetSystemColorArgb(KnownColor color) { Debug.Assert(Color.IsKnownColorSystem(color)); - return ColorValueTable[(int)color]; + if (!SystemColors.s_useAlternativeColorSet) + { + return ColorValueTable[(int)color]; + } + + int index = color <= KnownColor.WindowText ? (int)color : (int)color - (int)KnownColor.ButtonFace + (int)KnownColor.WindowText + 1; + return AlternateSystemColors[index]; } #endif } diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/SystemColors.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/SystemColors.cs index 3549c80735937..a65f4149e283f 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/SystemColors.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/SystemColors.cs @@ -1,10 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; + namespace System.Drawing { public static class SystemColors { + internal static bool s_useAlternativeColorSet; + public static Color ActiveBorder => Color.FromKnownColor(KnownColor.ActiveBorder); public static Color ActiveCaption => Color.FromKnownColor(KnownColor.ActiveCaption); public static Color ActiveCaptionText => Color.FromKnownColor(KnownColor.ActiveCaptionText); @@ -47,5 +51,24 @@ public static class SystemColors public static Color Window => Color.FromKnownColor(KnownColor.Window); public static Color WindowFrame => Color.FromKnownColor(KnownColor.WindowFrame); public static Color WindowText => Color.FromKnownColor(KnownColor.WindowText); + + /// + /// When , system values will return + /// the alternative color set (as returned by statics or + /// ). This is currently "dark mode" + /// variants of the system colors. + /// + /// + /// + /// On Windows, system values will always return the current + /// Windows color when the OS has a high contrast theme enabled. + /// + /// + [Experimental(Experimentals.SystemColorsDiagId, UrlFormat = Experimentals.SharedUrlFormat)] + public static bool UseAlternativeColorSet + { + get => s_useAlternativeColorSet; + set => s_useAlternativeColorSet = value; + } } } diff --git a/src/libraries/System.Drawing.Primitives/tests/ColorTests.cs b/src/libraries/System.Drawing.Primitives/tests/ColorTests.cs index c1267aac24008..a4a674d9b517b 100644 --- a/src/libraries/System.Drawing.Primitives/tests/ColorTests.cs +++ b/src/libraries/System.Drawing.Primitives/tests/ColorTests.cs @@ -448,7 +448,7 @@ public void GetSaturation(int r, int g, int b, float expected) public static IEnumerable Equality_MemberData() { yield return new object[] { Color.AliceBlue, Color.AliceBlue, true }; - yield return new object[] { Color.AliceBlue, Color.White, false}; + yield return new object[] { Color.AliceBlue, Color.White, false }; yield return new object[] { Color.AliceBlue, Color.Black, false }; yield return new object[] { Color.FromArgb(255, 1, 2, 3), Color.FromArgb(255, 1, 2, 3), true }; @@ -466,7 +466,7 @@ public static IEnumerable Equality_MemberData() string someNameConstructed = string.Join("", "Some", "Name"); Assert.NotSame("SomeName", someNameConstructed); // If this fails the above must be changed so this test is correct. - yield return new object[] {Color.FromName("SomeName"), Color.FromName(someNameConstructed), true}; + yield return new object[] { Color.FromName("SomeName"), Color.FromName(someNameConstructed), true }; } [Theory] @@ -889,5 +889,27 @@ private static int GetColorRefValue(Color color) // The COLORREF value has the following hexadecimal form: 0x00bbggrr. return color.B << 16 | color.G << 8 | color.R; } + + [Fact] + public void SystemColor_AlternativeColors() + { + try + { +#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + Drawing.SystemColors.UseAlternativeColorSet = true; +#pragma warning restore SYSLIB5002 + + Assert.Equal(0xFF464646, (uint)Drawing.SystemColors.ActiveBorder.ToArgb()); + Assert.Equal(0xFFF0F0F0, (uint)Drawing.SystemColors.WindowText.ToArgb()); + Assert.Equal(0xFF202020, (uint)Drawing.SystemColors.ButtonFace.ToArgb()); + Assert.Equal(0xFF2A80D2, (uint)Drawing.SystemColors.MenuHighlight.ToArgb()); + } + finally + { +#pragma warning disable SYSLIB5002 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + Drawing.SystemColors.UseAlternativeColorSet = false; +#pragma warning restore SYSLIB5002 + } + } } } From 1011768eca5fdb3ba1b667ad0f7d025a7edf8e29 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Mon, 29 Jul 2024 10:09:57 -0700 Subject: [PATCH 2/2] Respond to feedback --- .../src/System/Drawing/KnownColorTable.cs | 35 +++++++++++-------- .../src/System/Drawing/SystemColors.cs | 5 +++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/KnownColorTable.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/KnownColorTable.cs index 67cda2762d45d..06922761f4b3e 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/KnownColorTable.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/KnownColorTable.cs @@ -427,6 +427,11 @@ internal static class KnownColorTable KnownColorKindWeb, // RebeccaPurple ]; + // These values were based on manual investigation of dark mode themes in the + // Win32 Common Controls and WinUI. There aren't direct mappings published by + // Windows, these may change slightly when this feature is finalized to make + // sure we have the best experience in hybrid dark mode scenarios (mixing + // WPF, WinForms, and WinUI). private static ReadOnlySpan AlternateSystemColors => [ 0, // To align with KnownColor.ActiveBorder = 1 @@ -494,18 +499,24 @@ public static uint KnownColorToArgb(KnownColor color) : ColorValueTable[(int)color]; } + private static uint GetAlternateSystemColorArgb(KnownColor color) + { + // Shift the original (split) index to fit the alternate color map. + int index = color <= KnownColor.WindowText + ? (int)color + : (int)color - (int)KnownColor.ButtonFace + (int)KnownColor.WindowText + 1; + + return AlternateSystemColors[index]; + } + #if FEATURE_WINDOWS_SYSTEM_COLORS public static uint GetSystemColorArgb(KnownColor color) { Debug.Assert(Color.IsKnownColorSystem(color)); - if (!SystemColors.s_useAlternativeColorSet || HighContrastEnabled()) - { - return ColorTranslator.COLORREFToARGB(Interop.User32.GetSysColor((byte)ColorValueTable[(int)color])); - } - - int index = color <= KnownColor.WindowText ? (int)color : (int)color - (int)KnownColor.ButtonFace + (int)KnownColor.WindowText + 1; - return AlternateSystemColors[index]; + return !SystemColors.s_useAlternativeColorSet || HighContrastEnabled() + ? ColorTranslator.COLORREFToARGB(Interop.User32.GetSysColor((byte)ColorValueTable[(int)color])) + : GetAlternateSystemColorArgb(color); } private static unsafe bool HighContrastEnabled() @@ -529,13 +540,9 @@ public static uint GetSystemColorArgb(KnownColor color) { Debug.Assert(Color.IsKnownColorSystem(color)); - if (!SystemColors.s_useAlternativeColorSet) - { - return ColorValueTable[(int)color]; - } - - int index = color <= KnownColor.WindowText ? (int)color : (int)color - (int)KnownColor.ButtonFace + (int)KnownColor.WindowText + 1; - return AlternateSystemColors[index]; + return (!SystemColors.s_useAlternativeColorSet) + ? ColorValueTable[(int)color] + : GetAlternateSystemColorArgb(color); } #endif } diff --git a/src/libraries/System.Drawing.Primitives/src/System/Drawing/SystemColors.cs b/src/libraries/System.Drawing.Primitives/src/System/Drawing/SystemColors.cs index a65f4149e283f..daf6e33770ce9 100644 --- a/src/libraries/System.Drawing.Primitives/src/System/Drawing/SystemColors.cs +++ b/src/libraries/System.Drawing.Primitives/src/System/Drawing/SystemColors.cs @@ -60,6 +60,11 @@ public static class SystemColors /// /// /// + /// values are always looked up every + /// time you use them and do not retain any other context. As such, existing + /// values will change when this property is set. + /// + /// /// On Windows, system values will always return the current /// Windows color when the OS has a high contrast theme enabled. ///