Commit af14fc18 authored by John Smith's avatar John Smith Committed by Commit Bot

Create initial Mac NativeTheme color mappings for color pipeline

This creates the initial set and additionally partially fixes an issue
where the NativeThemeRedirectedEquivalenceTest.NativeUiGetSystemColor
tests weren't doing the correct thing around the Mac system overrides.
On light mode, anything overridden wasn't actually getting to the color
pipeline code because of the other returns. In dark mode, the check
intended for checking if you were in an incognito window was passing
and pushing the code through NativeTheme::GetSystem color without
getting the overrides in either mode.

This is only a partial fix because there is a broader issue with how
the Mac handles light/dark with the NSAppearance. This gets applied
at the app or window level so anything that gets a named NSColor
actually is still getting a color based on that NSAppearance, not
the passed in ColorScheme. This is a bigger architectural question on
how/if it can be resolved which will need to be done in a later change.

This also moves NSSystemColorToSkColor to skia_utils_mac so it can be
used by both the old and new color systems without code duplication.

Bug: 1003612
Change-Id: Ifb5166166dafda83f0a97fbb1c2a3b407927b68d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2147925
Commit-Queue: John Smith <johnsm@microsoft.com>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Reviewed-by: default avatarFlorin Malita <fmalita@chromium.org>
Cr-Commit-Position: refs/heads/master@{#761148}
parent 29431afb
......@@ -31,9 +31,10 @@ const char* enum_names[] = {
int main(int argc, const char* argv[]) {
const auto add_mixers = [](ui::ColorProvider* provider, bool dark_window) {
// TODO(pkasting): Use standard provider setup functions once those exist.
ui::AddCoreDefaultColorMixers(provider, dark_window);
ui::AddNativeColorMixers(provider);
ui::AddUiColorMixers(provider);
ui::AddCoreDefaultColorMixer(provider, dark_window);
ui::AddNativeCoreColorMixer(provider, dark_window);
ui::AddUiColorMixer(provider);
ui::AddNativeUiColorMixer(provider, dark_window);
AddChromeColorMixers(provider);
AddOmniboxColorMixers(provider, false);
};
......
......@@ -41,6 +41,17 @@ SK_API SkRect CGRectToSkRect(const CGRect& rect);
CGRect SkIRectToCGRect(const SkIRect& rect);
CGRect SkRectToCGRect(const SkRect& rect);
// Converts NSColor to an SKColor.
// NSColor has a number of methods that return system colors (i.e. controlled by
// user preferences). This function converts the color given by an NSColor class
// method to an SkColor. Official documentation suggests developers only rely on
// +[NSColor selectedTextBackgroundColor] and +[NSColor selectedControlColor],
// but other colors give a good baseline. For many, a gradient is involved; the
// palette chosen based on the enum value given by +[NSColor currentColorTint].
// Apple's documentation also suggests to use NSColorList, but the system color
// list is just populated with class methods on NSColor.
SK_API SkColor NSSystemColorToSkColor(NSColor* color);
// Converts CGColorRef to the ARGB layout Skia expects. The given CGColorRef
// should be in the sRGB color space and include alpha.
SK_API SkColor CGColorRefToSkColor(CGColorRef color);
......
......@@ -126,23 +126,49 @@ CGRect SkRectToCGRect(const SkRect& rect) {
return cg_rect;
}
SkColor NSSystemColorToSkColor(NSColor* color) {
// System colors use the an NSNamedColorSpace called "System", so first step
// is to convert the color into something that can be worked with.
NSColor* device_color =
[color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
if (device_color)
return NSDeviceColorToSkColor(device_color);
// Sometimes the conversion is not possible, but we can get an approximation
// by going through a CGColorRef. Note that simply using NSColor methods for
// accessing components for system colors results in exceptions like
// "-numberOfComponents not valid for the NSColor NSNamedColorSpace System
// windowBackgroundColor; need to first convert colorspace." Hence the
// conversion first to CGColor.
CGColorRef cg_color = [color CGColor];
const size_t component_count = CGColorGetNumberOfComponents(cg_color);
if (component_count == 4)
return CGColorRefToSkColor(cg_color);
CHECK(component_count == 1 || component_count == 2);
// 1-2 components means a grayscale channel and maybe an alpha channel, which
// CGColorRefToSkColor will not like. But RGB is additive, so the conversion
// is easy (RGB to grayscale is less easy).
const CGFloat* components = CGColorGetComponents(cg_color);
CGFloat alpha = component_count == 2 ? components[1] : 1.0f;
return SkColor4f{components[0], components[0], components[0], alpha}
.toSkColor();
}
SkColor CGColorRefToSkColor(CGColorRef color) {
// TODO(ccameron): This assumes that |color| is already in sRGB. Ideally we'd
// use something like CGColorCreateCopyByMatchingToColorSpace, but that's
// only available in macOS 10.11.
DCHECK(CGColorGetNumberOfComponents(color) == 4);
const CGFloat* components = CGColorGetComponents(color);
return SkColorSetARGB(SkScalarRoundToInt(255.0 * components[3]), // alpha
SkScalarRoundToInt(255.0 * components[0]), // red
SkScalarRoundToInt(255.0 * components[1]), // green
SkScalarRoundToInt(255.0 * components[2])); // blue
return SkColor4f{components[0], components[1], components[2], components[3]}
.toSkColor();
}
CGColorRef CGColorCreateFromSkColor(SkColor color) {
double components[] = {SkColorGetR(color) / 255.0,
SkColorGetG(color) / 255.0,
SkColorGetB(color) / 255.0,
SkColorGetA(color) / 255.0};
CGFloat components[] = {
SkColorGetR(color) / 255.0f, SkColorGetG(color) / 255.0f,
SkColorGetB(color) / 255.0f, SkColorGetA(color) / 255.0f};
return CGColorCreate(base::mac::GetSRGBColorSpace(), components);
}
......@@ -153,34 +179,28 @@ SkColor NSDeviceColorToSkColor(NSColor* color) {
CGFloat red, green, blue, alpha;
color = [color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
[color getRed:&red green:&green blue:&blue alpha:&alpha];
return SkColorSetARGB(SkScalarRoundToInt(255.0 * alpha),
SkScalarRoundToInt(255.0 * red),
SkScalarRoundToInt(255.0 * green),
SkScalarRoundToInt(255.0 * blue));
return SkColor4f{red, green, blue, alpha}.toSkColor();
}
// Converts ARGB to NSColor.
NSColor* SkColorToCalibratedNSColor(SkColor color) {
return [NSColor colorWithCalibratedRed:SkColorGetR(color) / 255.0
green:SkColorGetG(color) / 255.0
blue:SkColorGetB(color) / 255.0
alpha:SkColorGetA(color) / 255.0];
return [NSColor colorWithCalibratedRed:SkColorGetR(color) / 255.0f
green:SkColorGetG(color) / 255.0f
blue:SkColorGetB(color) / 255.0f
alpha:SkColorGetA(color) / 255.0f];
}
NSColor* SkColorToDeviceNSColor(SkColor color) {
return [NSColor colorWithDeviceRed:SkColorGetR(color) / 255.0
green:SkColorGetG(color) / 255.0
blue:SkColorGetB(color) / 255.0
alpha:SkColorGetA(color) / 255.0];
return [NSColor colorWithDeviceRed:SkColorGetR(color) / 255.0f
green:SkColorGetG(color) / 255.0f
blue:SkColorGetB(color) / 255.0f
alpha:SkColorGetA(color) / 255.0f];
}
NSColor* SkColorToSRGBNSColor(SkColor color) {
const CGFloat components[] = {
SkColorGetR(color) / 255.0,
SkColorGetG(color) / 255.0,
SkColorGetB(color) / 255.0,
SkColorGetA(color) / 255.0
};
SkColorGetR(color) / 255.0f, SkColorGetG(color) / 255.0f,
SkColorGetB(color) / 255.0f, SkColorGetA(color) / 255.0f};
return [NSColor colorWithColorSpace:[NSColorSpace sRGBColorSpace]
components:components
count:4];
......
......@@ -84,14 +84,15 @@ jumbo_component("mixers") {
public_deps = [ "//base" ]
if (is_chromeos) {
sources += [ "cros/native_color_mixer.cc" ]
sources += [ "cros/native_color_mixers.cc" ]
} else if (is_fuchsia) {
sources += [ "fuchsia/native_color_mixer.cc" ]
sources += [ "fuchsia/native_color_mixers.cc" ]
} else if (is_linux) {
sources += [ "linux/native_color_mixer.cc" ]
sources += [ "linux/native_color_mixers.cc" ]
} else if (is_mac) {
sources += [ "mac/native_color_mixer.cc" ]
libs = [ "AppKit.framework" ]
sources += [ "mac/native_color_mixers.mm" ]
} else if (is_win) {
sources += [ "win/native_color_mixer.cc" ]
sources += [ "win/native_color_mixers.cc" ]
}
}
include_rules = [
"+third_party/skia/include",
"+skia/ext",
"+ui/gfx",
]
......@@ -86,8 +86,6 @@
E(kColorTabSelectedForeground, \
NativeTheme::kColorId_TabTitleColorActive) \
E(kColorTableBackground, NativeTheme::kColorId_TableBackground) \
E(kColorTableBackgroundAlternate, \
NativeTheme::kColorId_TableBackgroundAlternate) \
E(kColorTableForeground, NativeTheme::kColorId_TableText) \
E(kColorTableGroupingIndicator, \
NativeTheme::kColorId_TableGroupingIndicatorColor) \
......@@ -165,10 +163,25 @@
E(kColorNativeWindowText, COLOR_WINDOWTEXT)
#endif
#if defined(OS_MACOSX)
#define MACOSX_COLOR_IDS \
/* TODO(https://crug.com/1071669): Paired with the comment above for */ \
/* kColorButtonPressedBackground, work out how to */ \
/* remove this or fit it into the color pipeline structures. */ \
E(kColorButtonPressedBackgroundShade, \
NativeTheme::kColorId_ButtonPressedShade) \
E(kColorTableBackgroundAlternate, \
NativeTheme::kColorId_TableBackgroundAlternate)
#endif
#if defined(OS_WIN)
#define COLOR_IDS \
CROSS_PLATFORM_COLOR_IDS \
WIN_COLOR_IDS
#elif defined(OS_MACOSX)
#define COLOR_IDS \
CROSS_PLATFORM_COLOR_IDS \
MACOSX_COLOR_IDS
#else
#define COLOR_IDS CROSS_PLATFORM_COLOR_IDS
#endif
......
......@@ -11,20 +11,30 @@ namespace ui {
class ColorProvider;
// Adds color mixers to |provider| that provide kColorSetNative, as well as
// mappings from this set to cross-platform IDs. This function should be
// implemented on a per-platform basis in relevant subdirectories.
COMPONENT_EXPORT(COLOR) void AddNativeColorMixers(ColorProvider* provider);
// The ordering of the mixer functions below reflects the
// order in which they are added to the providers.
// Adds color mixers to |provider| that provide kColorSetCoreDefaults.
// Adds a color mixer to |provider| that provide kColorSetCoreDefaults.
// |dark window| should be set if the window for this provider is "dark themed",
// e.g. system native dark mode is enabled or the window is incognito.
COMPONENT_EXPORT(COLOR)
void AddCoreDefaultColorMixers(ColorProvider* provider, bool dark_window);
void AddCoreDefaultColorMixer(ColorProvider* provider, bool dark_window);
// Adds color mixers to |provider| that combine the above color sets with
// Adds a color mixer to |provider| that provide kColorSetNative.
// This function should be implemented on a per-platform basis in
// relevant subdirectories.
COMPONENT_EXPORT(COLOR)
void AddNativeCoreColorMixer(ColorProvider* provider, bool dark_window);
// Adds a color mixer to |provider| that combine the above color sets with
// recipes as necessary to produce all colors needed by ui/.
COMPONENT_EXPORT(COLOR) void AddUiColorMixers(ColorProvider* provider);
COMPONENT_EXPORT(COLOR) void AddUiColorMixer(ColorProvider* provider);
// Adds a color mixer to |provider| that can add to kColorSetNative.
// Intended for colors needed by ui/ that this platform overrides but
// are outside the set defined in the core mixer.
COMPONENT_EXPORT(COLOR)
void AddNativeUiColorMixer(ColorProvider* provider, bool dark_window);
} // namespace ui
......
......@@ -58,7 +58,7 @@ ColorMixer& AddMixerForLightMode(ColorProvider* provider) {
} // namespace
void AddCoreDefaultColorMixers(ColorProvider* provider, bool dark_window) {
void AddCoreDefaultColorMixer(ColorProvider* provider, bool dark_window) {
ColorMixer& mixer = dark_window ? AddMixerForDarkMode(provider)
: AddMixerForLightMode(provider);
mixer[kColorDisabledForeground] = BlendForMinContrast(
......
......@@ -8,7 +8,11 @@
namespace ui {
void AddNativeColorMixers(ColorProvider* provider) {
void AddNativeCoreColorMixer(ColorProvider* provider, bool dark_window) {
NOTIMPLEMENTED();
}
void AddNativeUiColorMixer(ColorProvider* provider, bool dark_window) {
NOTIMPLEMENTED();
}
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
......@@ -8,7 +8,11 @@
namespace ui {
void AddNativeColorMixers(ColorProvider* provider) {
void AddNativeCoreColorMixer(ColorProvider* provider, bool dark_window) {
NOTIMPLEMENTED();
}
void AddNativeUiColorMixer(ColorProvider* provider, bool dark_window) {
NOTIMPLEMENTED();
}
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
......@@ -8,7 +8,11 @@
namespace ui {
void AddNativeColorMixers(ColorProvider* provider) {
void AddNativeCoreColorMixer(ColorProvider* provider, bool dark_window) {
NOTIMPLEMENTED();
}
void AddNativeUiColorMixer(ColorProvider* provider, bool dark_window) {
NOTIMPLEMENTED();
}
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/color/color_mixers.h"
#include "base/logging.h"
namespace ui {
void AddNativeColorMixers(ColorProvider* provider) {
NOTIMPLEMENTED();
}
} // namespace ui
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/color/color_mixers.h"
#import <Cocoa/Cocoa.h>
#include "base/logging.h"
#import "skia/ext/skia_utils_mac.h"
#include "ui/color/color_mixer.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_recipe.h"
#include "ui/color/color_set.h"
#include "ui/gfx/color_palette.h"
namespace ui {
void AddNativeCoreColorMixer(ColorProvider* provider, bool dark_window) {
provider->AddMixer().AddSet({kColorSetNative,
{
{kColorTextSelectionBackground,
skia::NSSystemColorToSkColor(
[NSColor selectedTextBackgroundColor])},
}});
}
void AddNativeUiColorMixer(ColorProvider* provider, bool dark_window) {
ColorMixer& mixer = provider->AddMixer();
mixer.AddSet(
{kColorSetNative,
{
{kColorButtonPressedBackgroundShade,
SkColorSetA(SK_ColorBLACK, 0x10)},
{kColorFocusableBorderFocused,
SkColorSetA(skia::NSSystemColorToSkColor(
[NSColor keyboardFocusIndicatorColor]),
0x66)},
{kColorMenuBorder, SkColorSetA(SK_ColorBLACK, 0x60)},
{kColorMenuItemDisabledForeground,
skia::NSSystemColorToSkColor([NSColor disabledControlTextColor])},
{kColorMenuItemForeground,
skia::NSSystemColorToSkColor([NSColor controlTextColor])},
{kColorTextSelectionBackground,
skia::NSSystemColorToSkColor(
[NSColor selectedTextBackgroundColor])},
}});
mixer[kColorMenuItemHighlightedForeground] = {kColorPrimaryForeground};
mixer[kColorMenuItemSelectedForeground] = {kColorPrimaryForeground};
if (@available(macOS 10.14, *)) {
mixer[kColorTableBackgroundAlternate] = {skia::NSSystemColorToSkColor(
NSColor.alternatingContentBackgroundColors[1])};
} else {
mixer[kColorTableBackgroundAlternate] = {skia::NSSystemColorToSkColor(
NSColor.controlAlternatingRowBackgroundColors[1])};
}
dark_window
? mixer[kColorMenuSeparator] = {SkColorSetA(gfx::kGoogleGrey800, 0xCC)}
: mixer[kColorMenuSeparator] = {SkColorSetA(SK_ColorBLACK, 0x26)};
}
} // namespace ui
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "build/build_config.h"
#include "ui/color/color_mixers.h"
#include "ui/color/color_mixer.h"
......@@ -12,7 +12,7 @@
namespace ui {
void AddUiColorMixers(ColorProvider* provider) {
void AddUiColorMixer(ColorProvider* provider) {
ColorMixer& mixer = provider->AddMixer();
mixer[kColorBubbleBackground] = {kColorPrimaryBackground};
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
......@@ -14,7 +14,7 @@
namespace ui {
void AddMixerForNativeColors(ColorProvider* provider) {
void AddNativeCoreColorMixer(ColorProvider* provider, bool dark_window) {
// TODO(pkasting): Not clear whether this is really the set of interest.
// Maybe there's some way to query colors used by UxTheme.dll, or maybe we
// should be hardcoding a list of colors for system light/dark modes based on
......@@ -58,13 +58,8 @@ void AddMixerForNativeColors(ColorProvider* provider) {
}});
}
void AddMixerToMapToCrossPlatformIds(ColorProvider* provider) {
void AddNativeUiColorMixer(ColorProvider* provider, bool dark_window) {
// TODO(pkasting): Add recipes
}
void AddNativeColorMixers(ColorProvider* provider) {
AddMixerForNativeColors(provider);
AddMixerToMapToCrossPlatformIds(provider);
}
} // namespace ui
This diff is collapsed.
......@@ -91,6 +91,8 @@ class NATIVE_THEME_EXPORT NativeThemeMac : public NativeThemeBase {
std::unique_ptr<NativeTheme::ColorSchemeNativeThemeObserver>
color_scheme_observer_;
bool should_only_use_dark_colors_;
DISALLOW_COPY_AND_ASSIGN(NativeThemeMac);
};
......
......@@ -96,45 +96,6 @@ struct EnumArray {
VALUE array[static_cast<size_t>(KEY::COUNT)];
};
// NSColor has a number of methods that return system colors (i.e. controlled by
// user preferences). This function converts the color given by an NSColor class
// method to an SkColor. Official documentation suggests developers only rely on
// +[NSColor selectedTextBackgroundColor] and +[NSColor selectedControlColor],
// but other colors give a good baseline. For many, a gradient is involved; the
// palette chosen based on the enum value given by +[NSColor currentColorTint].
// Apple's documentation also suggests to use NSColorList, but the system color
// list is just populated with class methods on NSColor.
SkColor NSSystemColorToSkColor(NSColor* color) {
// System colors use the an NSNamedColorSpace called "System", so first step
// is to convert the color into something that can be worked with.
NSColor* device_color =
[color colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]];
if (device_color)
return skia::NSDeviceColorToSkColor(device_color);
// Sometimes the conversion is not possible, but we can get an approximation
// by going through a CGColorRef. Note that simply using NSColor methods for
// accessing components for system colors results in exceptions like
// "-numberOfComponents not valid for the NSColor NSNamedColorSpace System
// windowBackgroundColor; need to first convert colorspace." Hence the
// conversion first to CGColor.
CGColorRef cg_color = [color CGColor];
const size_t component_count = CGColorGetNumberOfComponents(cg_color);
if (component_count == 4)
return skia::CGColorRefToSkColor(cg_color);
CHECK(component_count == 1 || component_count == 2);
// 1-2 components means a grayscale channel and maybe an alpha channel, which
// CGColorRefToSkColor will not like. But RGB is additive, so the conversion
// is easy (RGB to grayscale is less easy).
const CGFloat* components = CGColorGetComponents(cg_color);
CGFloat alpha = component_count == 2 ? components[1] : 1.0;
return SkColorSetARGB(SkScalarRoundToInt(255.0 * alpha),
SkScalarRoundToInt(255.0 * components[0]),
SkScalarRoundToInt(255.0 * components[0]),
SkScalarRoundToInt(255.0 * components[0]));
}
// Converts an SkColor to grayscale by using luminance for all three components.
// Experimentally, this seems to produce a better result than a flat average or
// a min/max average for UI controls.
......@@ -193,7 +154,17 @@ SkColor NativeThemeMac::GetSystemColor(ColorId color_id,
if (color_scheme == ColorScheme::kDefault)
color_scheme = GetDefaultSystemColorScheme();
if ((color_scheme == ColorScheme::kDark) != IsDarkMode())
// The first check makes sure that when we are using the color providers that
// we actually go to the providers instead of just returning the colors
// below. The second check is to make sure that when not using color
// providers, we only skip the rest of the method when we are in an incognito
// window.
// TODO(http://crbug.com/1057754): Remove the && kPlatformHighContrast
// once NativeTheme.cc handles kColorProviderReirection and
// kPlatformHighContrast both being on.
if ((base::FeatureList::IsEnabled(features::kColorProviderRedirection) &&
color_scheme != ColorScheme::kPlatformHighContrast) ||
should_only_use_dark_colors_)
return NativeTheme::GetSystemColor(color_id, color_scheme);
// Empirically, currentAppearance is incorrect when switching
......@@ -222,9 +193,9 @@ SkColor NativeThemeMac::GetSystemColor(ColorId color_id,
// Mac has a couple of specific color overrides, documented below.
switch (color_id) {
case kColorId_EnabledMenuItemForegroundColor:
return NSSystemColorToSkColor([NSColor controlTextColor]);
return skia::NSSystemColorToSkColor([NSColor controlTextColor]);
case kColorId_DisabledMenuItemForegroundColor:
return NSSystemColorToSkColor([NSColor disabledControlTextColor]);
return skia::NSSystemColorToSkColor([NSColor disabledControlTextColor]);
case kColorId_MenuSeparatorColor:
return color_scheme == ColorScheme::kDark
? SkColorSetA(gfx::kGoogleGrey800, 0xCC)
......@@ -243,19 +214,20 @@ SkColor NativeThemeMac::GetSystemColor(ColorId color_id,
// and propagate it to the View hierarchy.
case kColorId_LabelTextSelectionBackgroundFocused:
case kColorId_TextfieldSelectionBackgroundFocused:
return NSSystemColorToSkColor([NSColor selectedTextBackgroundColor]);
return skia::NSSystemColorToSkColor(
[NSColor selectedTextBackgroundColor]);
case kColorId_FocusedBorderColor:
return SkColorSetA(
NSSystemColorToSkColor([NSColor keyboardFocusIndicatorColor]),
skia::NSSystemColorToSkColor([NSColor keyboardFocusIndicatorColor]),
0x66);
case kColorId_TableBackgroundAlternate:
if (@available(macOS 10.14, *)) {
return NSSystemColorToSkColor(
return skia::NSSystemColorToSkColor(
NSColor.alternatingContentBackgroundColors[1]);
}
return NSSystemColorToSkColor(
return skia::NSSystemColorToSkColor(
NSColor.controlAlternatingRowBackgroundColors[1]);
default:
......@@ -301,7 +273,8 @@ void NativeThemeMac::PaintMenuItemBackground(
NativeThemeMac::NativeThemeMac(bool configure_web_instance,
bool should_only_use_dark_colors)
: NativeThemeBase(should_only_use_dark_colors) {
: NativeThemeBase(should_only_use_dark_colors),
should_only_use_dark_colors_(should_only_use_dark_colors) {
if (!should_only_use_dark_colors)
InitializeDarkModeStateAndObserver();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment