Bug 1855360 - Fix the skip-if syntax. a=bustage-fix
[gecko.git] / widget / ThemeColors.cpp
blob1562eaee1d3b0c89206bf853192434c466b9f91c
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 {
18 struct ColorPalette {
19 ColorPalette(nscolor aAccent, nscolor aForeground);
21 constexpr ColorPalette(sRGBColor aAccent, sRGBColor aForeground,
22 sRGBColor aLight, sRGBColor aDark, sRGBColor aDarker)
23 : mAccent(aAccent),
24 mForeground(aForeground),
25 mAccentLight(aLight),
26 mAccentDark(aDark),
27 mAccentDarker(aDarker) {}
29 constexpr static ColorPalette Default() {
30 return ColorPalette(
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);
46 return 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);
55 nscolor lightColor =
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);
77 sRGBColor mAccent;
78 sRGBColor mForeground;
80 // Note that depending on the exact accent color, lighter/darker might really
81 // be inverted.
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,
108 ColorScheme aScheme)
109 : mDefaultPalette(aScheme == ColorScheme::Light ? &sDefaultLightPalette
110 : &sDefaultDarkPalette) {
111 const auto& color = aStyle.StyleUI()->mAccentColor;
112 if (color.IsAuto()) {
113 return;
115 MOZ_ASSERT(color.IsColor());
116 nscolor accentColor =
117 ColorPalette::EnsureOpaque(color.AsColor().CalcColor(aStyle));
118 if (sRGBColor::FromABGR(accentColor) == mDefaultPalette->mAccent) {
119 return;
121 mAccentColor.emplace(accentColor);
124 sRGBColor ThemeAccentColor::Get() const {
125 if (!mAccentColor) {
126 return mDefaultPalette->mAccent;
128 return sRGBColor::FromABGR(*mAccentColor);
131 sRGBColor ThemeAccentColor::GetForeground() const {
132 if (!mAccentColor) {
133 return mDefaultPalette->mForeground;
135 return sRGBColor::FromABGR(
136 ThemeColors::ComputeCustomAccentForeground(*mAccentColor));
139 sRGBColor ThemeAccentColor::GetLight() const {
140 if (!mAccentColor) {
141 return mDefaultPalette->mAccentLight;
143 return sRGBColor::FromABGR(ColorPalette::GetLight(*mAccentColor));
146 sRGBColor ThemeAccentColor::GetDark() const {
147 if (!mAccentColor) {
148 return mDefaultPalette->mAccentDark;
150 return sRGBColor::FromABGR(ColorPalette::GetDark(*mAccentColor));
153 sRGBColor ThemeAccentColor::GetDarker() const {
154 if (!mAccentColor) {
155 return mDefaultPalette->mAccentDarker;
157 return sRGBColor::FromABGR(ColorPalette::GetDarker(*mAccentColor));
160 bool ThemeColors::ShouldBeHighContrast(const nsPresContext& aPc) {
161 // We make sure that we're drawing backgrounds, since otherwise layout will
162 // darken our used text colors etc anyways, and that can cause contrast issues
163 // with dark high-contrast themes.
164 return aPc.GetBackgroundColorDraw() &&
165 PreferenceSheet::PrefsFor(*aPc.Document())
166 .NonNativeThemeShouldBeHighContrast();
169 ColorScheme ThemeColors::ColorSchemeForWidget(const nsIFrame* aFrame,
170 StyleAppearance aAppearance,
171 bool aHighContrast) {
172 if (!nsNativeTheme::IsWidgetScrollbarPart(aAppearance)) {
173 return LookAndFeel::ColorSchemeForFrame(aFrame);
175 // Scrollbars are a bit tricky. Their used color-scheme depends on whether the
176 // background they are on is light or dark.
178 // TODO(emilio): This heuristic effectively predates the color-scheme CSS
179 // property. Perhaps we should check whether the style or the document set
180 // `color-scheme` to something that isn't `normal`, and if so go through the
181 // code-path above.
182 if (aHighContrast) {
183 return ColorScheme::Light;
185 if (StaticPrefs::widget_disable_dark_scrollbar()) {
186 return ColorScheme::Light;
188 return nsNativeTheme::IsDarkBackgroundForScrollbar(
189 const_cast<nsIFrame*>(aFrame))
190 ? ColorScheme::Dark
191 : ColorScheme::Light;
194 /*static*/
195 void ThemeColors::RecomputeAccentColors() {
196 MOZ_RELEASE_ASSERT(NS_IsMainThread());
198 sDefaultLightPalette =
199 ColorPalette(GetAccentColor(true, ColorScheme::Light),
200 GetAccentColor(false, ColorScheme::Light));
202 sDefaultDarkPalette = ColorPalette(GetAccentColor(true, ColorScheme::Dark),
203 GetAccentColor(false, ColorScheme::Dark));
206 /*static*/
207 nscolor ThemeColors::ComputeCustomAccentForeground(nscolor aColor) {
208 // Contrast ratio is defined in
209 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef as:
211 // (L1 + 0.05) / (L2 + 0.05)
213 // Where L1 is the lighter color, and L2 is the darker one. So we determine
214 // whether we're dark or light and resolve the equation for the target ratio.
216 // So when lightening:
218 // L1 = k * (L2 + 0.05) - 0.05
220 // And when darkening:
222 // L2 = (L1 + 0.05) / k - 0.05
224 const float luminance = RelativeLuminanceUtils::Compute(aColor);
226 // We generally prefer white unless we can't because the color is really light
227 // and we can't provide reasonable contrast.
228 const float ratioWithWhite = 1.05f / (luminance + 0.05f);
229 const bool canBeWhite =
230 ratioWithWhite >=
231 StaticPrefs::layout_css_accent_color_min_contrast_ratio();
232 if (canBeWhite) {
233 return NS_RGB(0xff, 0xff, 0xff);
235 const float targetRatio =
236 StaticPrefs::layout_css_accent_color_darkening_target_contrast_ratio();
237 const float targetLuminance = (luminance + 0.05f) / targetRatio - 0.05f;
238 return RelativeLuminanceUtils::Adjust(aColor, targetLuminance);
241 nscolor ThemeColors::AdjustUnthemedScrollbarThumbColor(
242 nscolor aFaceColor, dom::ElementState aStates) {
243 // In Windows 10, scrollbar thumb has the following colors:
245 // State | Color | Luminance
246 // -------+----------+----------
247 // Normal | Gray 205 | 61.0%
248 // Hover | Gray 166 | 38.1%
249 // Active | Gray 96 | 11.7%
251 // This function is written based on the ratios between the values.
252 bool isActive = aStates.HasState(dom::ElementState::ACTIVE);
253 bool isHover = aStates.HasState(dom::ElementState::HOVER);
254 if (!isActive && !isHover) {
255 return aFaceColor;
257 float luminance = RelativeLuminanceUtils::Compute(aFaceColor);
258 if (isActive) {
259 // 11.7 / 61.0
260 luminance = ScaleLuminanceBy(luminance, 0.192f);
261 } else {
262 // 38.1 / 61.0
263 luminance = ScaleLuminanceBy(luminance, 0.625f);
265 return RelativeLuminanceUtils::Adjust(aFaceColor, luminance);
268 } // namespace mozilla::widget