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_layout.h"
10 #include "mozilla/StaticPrefs_widget.h"
11 #include "ThemeDrawing.h"
12 #include "nsNativeTheme.h"
14 using namespace mozilla::gfx
;
16 namespace mozilla::widget
{
19 ColorPalette(nscolor aAccent
, nscolor aForeground
);
21 constexpr ColorPalette(sRGBColor aAccent
, sRGBColor aForeground
,
22 sRGBColor aLight
, sRGBColor aDark
, sRGBColor aDarker
)
24 mForeground(aForeground
),
27 mAccentDarker(aDarker
) {}
29 constexpr static ColorPalette
Default() {
31 sDefaultAccent
, sDefaultAccentText
,
32 sRGBColor::UnusualFromARGB(0x4d008deb), // Luminance: 25.04791%
33 sRGBColor::UnusualFromARGB(0xff0250bb), // Luminance: 9.33808%
34 sRGBColor::UnusualFromARGB(0xff054096) // Luminance: 5.90106%
38 // Ensure accent color is opaque by blending with white. This serves two
39 // purposes: On one hand, it avoids surprises if we overdraw. On the other, it
40 // makes our math below make more sense, as we want to match the browser
41 // style, which has an opaque accent color.
42 static nscolor
EnsureOpaque(nscolor aAccent
) {
43 if (NS_GET_A(aAccent
) != 0xff) {
44 return NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aAccent
);
49 static nscolor
GetLight(nscolor aAccent
) {
50 // The luminance from the light color divided by the one of the accent color
51 // in the default palette.
52 constexpr float kLightLuminanceScale
= 25.048f
/ 13.693f
;
53 const float lightLuminanceAdjust
= ThemeColors::ScaleLuminanceBy(
54 RelativeLuminanceUtils::Compute(aAccent
), kLightLuminanceScale
);
56 RelativeLuminanceUtils::Adjust(aAccent
, lightLuminanceAdjust
);
57 return NS_RGBA(NS_GET_R(lightColor
), NS_GET_G(lightColor
),
58 NS_GET_B(lightColor
), 0x4d);
61 static nscolor
GetDark(nscolor aAccent
) {
62 // Same deal as above (but without the alpha).
63 constexpr float kDarkLuminanceScale
= 9.338f
/ 13.693f
;
64 const float darkLuminanceAdjust
= ThemeColors::ScaleLuminanceBy(
65 RelativeLuminanceUtils::Compute(aAccent
), kDarkLuminanceScale
);
66 return RelativeLuminanceUtils::Adjust(aAccent
, darkLuminanceAdjust
);
69 static nscolor
GetDarker(nscolor aAccent
) {
70 // Same deal as above.
71 constexpr float kDarkerLuminanceScale
= 5.901f
/ 13.693f
;
72 const float darkerLuminanceAdjust
= ThemeColors::ScaleLuminanceBy(
73 RelativeLuminanceUtils::Compute(aAccent
), kDarkerLuminanceScale
);
74 return RelativeLuminanceUtils::Adjust(aAccent
, darkerLuminanceAdjust
);
78 sRGBColor mForeground
;
80 // Note that depending on the exact accent color, lighter/darker might really
82 sRGBColor mAccentLight
;
83 sRGBColor mAccentDark
;
84 sRGBColor mAccentDarker
;
87 static nscolor
GetAccentColor(bool aBackground
, ColorScheme aScheme
) {
88 auto useStandins
= LookAndFeel::UseStandins(
89 !StaticPrefs::widget_non_native_theme_use_theme_accent());
90 return ColorPalette::EnsureOpaque(
91 LookAndFeel::Color(aBackground
? LookAndFeel::ColorID::Accentcolor
92 : LookAndFeel::ColorID::Accentcolortext
,
93 aScheme
, useStandins
));
96 static ColorPalette sDefaultLightPalette
= ColorPalette::Default();
97 static ColorPalette sDefaultDarkPalette
= ColorPalette::Default();
99 ColorPalette::ColorPalette(nscolor aAccent
, nscolor aForeground
) {
100 mAccent
= sRGBColor::FromABGR(aAccent
);
101 mForeground
= sRGBColor::FromABGR(aForeground
);
102 mAccentLight
= sRGBColor::FromABGR(GetLight(aAccent
));
103 mAccentDark
= sRGBColor::FromABGR(GetDark(aAccent
));
104 mAccentDarker
= sRGBColor::FromABGR(GetDarker(aAccent
));
107 ThemeAccentColor::ThemeAccentColor(const ComputedStyle
& aStyle
,
109 : mDefaultPalette(aScheme
== ColorScheme::Light
? &sDefaultLightPalette
110 : &sDefaultDarkPalette
) {
111 const auto& color
= aStyle
.StyleUI()->mAccentColor
;
112 if (color
.IsAuto()) {
115 MOZ_ASSERT(color
.IsColor());
116 nscolor accentColor
=
117 ColorPalette::EnsureOpaque(color
.AsColor().CalcColor(aStyle
));
118 if (sRGBColor::FromABGR(accentColor
) == mDefaultPalette
->mAccent
) {
121 mAccentColor
.emplace(accentColor
);
124 sRGBColor
ThemeAccentColor::Get() const {
126 return mDefaultPalette
->mAccent
;
128 return sRGBColor::FromABGR(*mAccentColor
);
131 sRGBColor
ThemeAccentColor::GetForeground() const {
133 return mDefaultPalette
->mForeground
;
135 return sRGBColor::FromABGR(
136 ThemeColors::ComputeCustomAccentForeground(*mAccentColor
));
139 sRGBColor
ThemeAccentColor::GetLight() const {
141 return mDefaultPalette
->mAccentLight
;
143 return sRGBColor::FromABGR(ColorPalette::GetLight(*mAccentColor
));
146 sRGBColor
ThemeAccentColor::GetDark() const {
148 return mDefaultPalette
->mAccentDark
;
150 return sRGBColor::FromABGR(ColorPalette::GetDark(*mAccentColor
));
153 sRGBColor
ThemeAccentColor::GetDarker() const {
155 return mDefaultPalette
->mAccentDarker
;
157 return sRGBColor::FromABGR(ColorPalette::GetDarker(*mAccentColor
));
160 auto ThemeColors::ShouldBeHighContrast(const nsPresContext
& aPc
)
161 -> HighContrastInfo
{
162 // We make sure that we're drawing backgrounds, since otherwise layout will
163 // darken our used text colors etc anyways, and that can cause contrast issues
164 // with dark high-contrast themes.
165 if (!aPc
.GetBackgroundColorDraw()) {
168 const auto& prefs
= PreferenceSheet::PrefsFor(*aPc
.Document());
169 return {prefs
.NonNativeThemeShouldBeHighContrast(),
170 prefs
.mMustUseLightSystemColors
};
173 ColorScheme
ThemeColors::ColorSchemeForWidget(const nsIFrame
* aFrame
,
174 StyleAppearance aAppearance
,
175 const HighContrastInfo
& aInfo
) {
176 if (aInfo
.mMustUseLightSystemColors
) {
177 return ColorScheme::Light
;
179 if (!nsNativeTheme::IsWidgetScrollbarPart(aAppearance
)) {
180 return LookAndFeel::ColorSchemeForFrame(aFrame
);
182 // Scrollbars are a bit tricky. Their used color-scheme depends on whether the
183 // background they are on is light or dark.
185 // TODO(emilio): This heuristic effectively predates the color-scheme CSS
186 // property. Perhaps we should check whether the style or the document set
187 // `color-scheme` to something that isn't `normal`, and if so go through the
189 if (StaticPrefs::widget_disable_dark_scrollbar()) {
190 return ColorScheme::Light
;
192 return nsNativeTheme::IsDarkBackgroundForScrollbar(
193 const_cast<nsIFrame
*>(aFrame
))
195 : ColorScheme::Light
;
199 void ThemeColors::RecomputeAccentColors() {
200 MOZ_RELEASE_ASSERT(NS_IsMainThread());
202 sDefaultLightPalette
=
203 ColorPalette(GetAccentColor(true, ColorScheme::Light
),
204 GetAccentColor(false, ColorScheme::Light
));
206 sDefaultDarkPalette
= ColorPalette(GetAccentColor(true, ColorScheme::Dark
),
207 GetAccentColor(false, ColorScheme::Dark
));
211 nscolor
ThemeColors::ComputeCustomAccentForeground(nscolor aColor
) {
212 // Contrast ratio is defined in
213 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef as:
215 // (L1 + 0.05) / (L2 + 0.05)
217 // Where L1 is the lighter color, and L2 is the darker one. So we determine
218 // whether we're dark or light and resolve the equation for the target ratio.
220 // So when lightening:
222 // L1 = k * (L2 + 0.05) - 0.05
224 // And when darkening:
226 // L2 = (L1 + 0.05) / k - 0.05
228 const float luminance
= RelativeLuminanceUtils::Compute(aColor
);
230 // We generally prefer white unless we can't because the color is really light
231 // and we can't provide reasonable contrast.
232 const float ratioWithWhite
= 1.05f
/ (luminance
+ 0.05f
);
233 const bool canBeWhite
=
235 StaticPrefs::layout_css_accent_color_min_contrast_ratio();
237 return NS_RGB(0xff, 0xff, 0xff);
239 const float targetRatio
=
240 StaticPrefs::layout_css_accent_color_darkening_target_contrast_ratio();
241 const float targetLuminance
= (luminance
+ 0.05f
) / targetRatio
- 0.05f
;
242 return RelativeLuminanceUtils::Adjust(aColor
, targetLuminance
);
245 nscolor
ThemeColors::AdjustUnthemedScrollbarThumbColor(
246 nscolor aFaceColor
, dom::ElementState aStates
) {
247 // In Windows 10, scrollbar thumb has the following colors:
249 // State | Color | Luminance
250 // -------+----------+----------
251 // Normal | Gray 205 | 61.0%
252 // Hover | Gray 166 | 38.1%
253 // Active | Gray 96 | 11.7%
255 // This function is written based on the ratios between the values.
256 bool isActive
= aStates
.HasState(dom::ElementState::ACTIVE
);
257 bool isHover
= aStates
.HasState(dom::ElementState::HOVER
);
258 if (!isActive
&& !isHover
) {
261 float luminance
= RelativeLuminanceUtils::Compute(aFaceColor
);
264 luminance
= ScaleLuminanceBy(luminance
, 0.192f
);
267 luminance
= ScaleLuminanceBy(luminance
, 0.625f
);
269 return RelativeLuminanceUtils::Adjust(aFaceColor
, luminance
);
272 } // namespace mozilla::widget