1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ThemeColors.h"
8 #include "mozilla/RelativeLuminanceUtils.h"
9 #include "mozilla/StaticPrefs_widget.h"
10 #include "ThemeDrawing.h"
11 #include "nsNativeTheme.h"
13 using namespace mozilla::gfx
;
15 namespace mozilla::widget
{
18 ColorPalette(nscolor aAccent
, nscolor aForeground
);
20 constexpr ColorPalette(sRGBColor aAccent
, sRGBColor aForeground
,
21 sRGBColor aLight
, sRGBColor aDark
, sRGBColor aDarker
)
23 mForeground(aForeground
),
26 mAccentDarker(aDarker
) {}
28 constexpr static ColorPalette
Default() {
30 sDefaultAccent
, sDefaultAccentForeground
,
31 sRGBColor::UnusualFromARGB(0x4d008deb), // Luminance: 25.04791%
32 sRGBColor::UnusualFromARGB(0xff0250bb), // Luminance: 9.33808%
33 sRGBColor::UnusualFromARGB(0xff054096) // Luminance: 5.90106%
37 // Ensure accent color is opaque by blending with white. This serves two
38 // purposes: On one hand, it avoids surprises if we overdraw. On the other, it
39 // makes our math below make more sense, as we want to match the browser
40 // style, which has an opaque accent color.
41 static nscolor
EnsureOpaque(nscolor aAccent
) {
42 if (NS_GET_A(aAccent
) != 0xff) {
43 return NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aAccent
);
48 static nscolor
GetLight(nscolor aAccent
) {
49 // The luminance from the light color divided by the one of the accent color
50 // in the default palette.
51 constexpr float kLightLuminanceScale
= 25.048f
/ 13.693f
;
52 const float lightLuminanceAdjust
= ThemeColors::ScaleLuminanceBy(
53 RelativeLuminanceUtils::Compute(aAccent
), kLightLuminanceScale
);
55 RelativeLuminanceUtils::Adjust(aAccent
, lightLuminanceAdjust
);
56 return NS_RGBA(NS_GET_R(lightColor
), NS_GET_G(lightColor
),
57 NS_GET_B(lightColor
), 0x4d);
60 static nscolor
GetDark(nscolor aAccent
) {
61 // Same deal as above (but without the alpha).
62 constexpr float kDarkLuminanceScale
= 9.338f
/ 13.693f
;
63 const float darkLuminanceAdjust
= ThemeColors::ScaleLuminanceBy(
64 RelativeLuminanceUtils::Compute(aAccent
), kDarkLuminanceScale
);
65 return RelativeLuminanceUtils::Adjust(aAccent
, darkLuminanceAdjust
);
68 static nscolor
GetDarker(nscolor aAccent
) {
69 // Same deal as above.
70 constexpr float kDarkerLuminanceScale
= 5.901f
/ 13.693f
;
71 const float darkerLuminanceAdjust
= ThemeColors::ScaleLuminanceBy(
72 RelativeLuminanceUtils::Compute(aAccent
), kDarkerLuminanceScale
);
73 return RelativeLuminanceUtils::Adjust(aAccent
, darkerLuminanceAdjust
);
77 sRGBColor mForeground
;
79 // Note that depending on the exact accent color, lighter/darker might really
81 sRGBColor mAccentLight
;
82 sRGBColor mAccentDark
;
83 sRGBColor mAccentDarker
;
86 static nscolor
ThemedAccentColor(bool aBackground
) {
87 MOZ_ASSERT(StaticPrefs::widget_non_native_theme_use_theme_accent());
88 // TODO(emilio): In the future we should probably add dark-color-scheme
89 // support for non-native form controls.
90 return ColorPalette::EnsureOpaque(LookAndFeel::Color(
91 aBackground
? LookAndFeel::ColorID::MozAccentColor
92 : LookAndFeel::ColorID::MozAccentColorForeground
,
93 LookAndFeel::ColorScheme::Light
, LookAndFeel::UseStandins::No
));
96 static ColorPalette sDefaultPalette
= ColorPalette::Default();
98 ColorPalette::ColorPalette(nscolor aAccent
, nscolor aForeground
) {
99 mAccent
= sRGBColor::FromABGR(aAccent
);
100 mForeground
= sRGBColor::FromABGR(aForeground
);
101 mAccentLight
= sRGBColor::FromABGR(GetLight(aAccent
));
102 mAccentDark
= sRGBColor::FromABGR(GetDark(aAccent
));
103 mAccentDarker
= sRGBColor::FromABGR(GetDarker(aAccent
));
106 ThemeAccentColor::ThemeAccentColor(const ComputedStyle
& aStyle
) {
107 const auto& color
= aStyle
.StyleUI()->mAccentColor
;
108 if (color
.IsColor()) {
109 mAccentColor
.emplace(
110 ColorPalette::EnsureOpaque(color
.AsColor().CalcColor(aStyle
)));
112 MOZ_ASSERT(color
.IsAuto());
116 sRGBColor
ThemeAccentColor::Get() const {
118 return sDefaultPalette
.mAccent
;
120 return sRGBColor::FromABGR(*mAccentColor
);
123 sRGBColor
ThemeAccentColor::GetForeground() const {
125 return sDefaultPalette
.mForeground
;
127 return sRGBColor::FromABGR(
128 ThemeColors::ComputeCustomAccentForeground(*mAccentColor
));
131 sRGBColor
ThemeAccentColor::GetLight() const {
133 return sDefaultPalette
.mAccentLight
;
135 return sRGBColor::FromABGR(ColorPalette::GetLight(*mAccentColor
));
138 sRGBColor
ThemeAccentColor::GetDark() const {
140 return sDefaultPalette
.mAccentDark
;
142 return sRGBColor::FromABGR(ColorPalette::GetDark(*mAccentColor
));
145 sRGBColor
ThemeAccentColor::GetDarker() const {
147 return sDefaultPalette
.mAccentDarker
;
149 return sRGBColor::FromABGR(ColorPalette::GetDarker(*mAccentColor
));
152 bool ThemeColors::ShouldBeHighContrast(const nsPresContext
& aPc
) {
153 // We make sure that we're drawing backgrounds, since otherwise layout will
154 // darken our used text colors etc anyways, and that can cause contrast issues
155 // with dark high-contrast themes.
156 return aPc
.GetBackgroundColorDraw() &&
157 PreferenceSheet::PrefsFor(*aPc
.Document())
158 .NonNativeThemeShouldBeHighContrast();
161 ColorScheme
ThemeColors::ColorSchemeForWidget(const nsIFrame
* aFrame
,
162 StyleAppearance aAppearance
,
163 bool aHighContrast
) {
164 if (!nsNativeTheme::IsWidgetScrollbarPart(aAppearance
)) {
165 return LookAndFeel::ColorSchemeForFrame(aFrame
);
167 // Scrollbars are a bit tricky. Their used color-scheme depends on whether the
168 // background they are on is light or dark.
170 // TODO(emilio): This heuristic effectively predates the color-scheme CSS
171 // property. Perhaps we should check whether the style or the document set
172 // `color-scheme` to something that isn't `normal`, and if so go through the
175 return ColorScheme::Light
;
177 if (StaticPrefs::widget_disable_dark_scrollbar()) {
178 return ColorScheme::Light
;
180 return nsNativeTheme::IsDarkBackground(const_cast<nsIFrame
*>(aFrame
))
182 : ColorScheme::Light
;
186 void ThemeColors::RecomputeAccentColors() {
187 MOZ_RELEASE_ASSERT(NS_IsMainThread());
189 if (!StaticPrefs::widget_non_native_theme_use_theme_accent()) {
190 sDefaultPalette
= ColorPalette::Default();
195 ColorPalette(ThemedAccentColor(true), ThemedAccentColor(false));
199 nscolor
ThemeColors::ComputeCustomAccentForeground(nscolor aColor
) {
200 // Contrast ratio is defined in
201 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef as:
203 // (L1 + 0.05) / (L2 + 0.05)
205 // Where L1 is the lighter color, and L2 is the darker one. So we determine
206 // whether we're dark or light and resolve the equation for the target ratio.
208 // So when lightening:
210 // L1 = k * (L2 + 0.05) - 0.05
212 // And when darkening:
214 // L2 = (L1 + 0.05) / k - 0.05
216 const float luminance
= RelativeLuminanceUtils::Compute(aColor
);
218 // We generally prefer white unless we can't because the color is really light
219 // and we can't provide reasonable contrast.
220 const float ratioWithWhite
= 1.05f
/ (luminance
+ 0.05f
);
221 const bool canBeWhite
=
223 StaticPrefs::layout_css_accent_color_min_contrast_ratio();
225 return NS_RGB(0xff, 0xff, 0xff);
227 const float targetRatio
=
228 StaticPrefs::layout_css_accent_color_darkening_target_contrast_ratio();
229 const float targetLuminance
= (luminance
+ 0.05f
) / targetRatio
- 0.05f
;
230 return RelativeLuminanceUtils::Adjust(aColor
, targetLuminance
);
233 nscolor
ThemeColors::AdjustUnthemedScrollbarThumbColor(nscolor aFaceColor
,
234 EventStates aStates
) {
235 // In Windows 10, scrollbar thumb has the following colors:
237 // State | Color | Luminance
238 // -------+----------+----------
239 // Normal | Gray 205 | 61.0%
240 // Hover | Gray 166 | 38.1%
241 // Active | Gray 96 | 11.7%
243 // This function is written based on the ratios between the values.
244 bool isActive
= aStates
.HasState(NS_EVENT_STATE_ACTIVE
);
245 bool isHover
= aStates
.HasState(NS_EVENT_STATE_HOVER
);
246 if (!isActive
&& !isHover
) {
249 float luminance
= RelativeLuminanceUtils::Compute(aFaceColor
);
252 luminance
= ScaleLuminanceBy(luminance
, 0.192f
);
255 luminance
= ScaleLuminanceBy(luminance
, 0.625f
);
257 return RelativeLuminanceUtils::Adjust(aFaceColor
, luminance
);
260 } // namespace mozilla::widget