forked from mirrors/gecko-dev
		
	 d8e936e706
			
		
	
	
		d8e936e706
		
	
	
	
	
		
			
			This seems hard to reliably test... Basically, this forces all windows HCM to use "light" system colors for non-native drawing, the same way we do for system color resolution. Differential Revision: https://phabricator.services.mozilla.com/D201205
		
			
				
	
	
		
			272 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "ThemeColors.h"
 | |
| 
 | |
| #include "mozilla/RelativeLuminanceUtils.h"
 | |
| #include "mozilla/StaticPrefs_layout.h"
 | |
| #include "mozilla/StaticPrefs_widget.h"
 | |
| #include "ThemeDrawing.h"
 | |
| #include "nsNativeTheme.h"
 | |
| 
 | |
| using namespace mozilla::gfx;
 | |
| 
 | |
| namespace mozilla::widget {
 | |
| 
 | |
| struct ColorPalette {
 | |
|   ColorPalette(nscolor aAccent, nscolor aForeground);
 | |
| 
 | |
|   constexpr ColorPalette(sRGBColor aAccent, sRGBColor aForeground,
 | |
|                          sRGBColor aLight, sRGBColor aDark, sRGBColor aDarker)
 | |
|       : mAccent(aAccent),
 | |
|         mForeground(aForeground),
 | |
|         mAccentLight(aLight),
 | |
|         mAccentDark(aDark),
 | |
|         mAccentDarker(aDarker) {}
 | |
| 
 | |
|   constexpr static ColorPalette Default() {
 | |
|     return ColorPalette(
 | |
|         sDefaultAccent, sDefaultAccentText,
 | |
|         sRGBColor::UnusualFromARGB(0x4d008deb),  // Luminance: 25.04791%
 | |
|         sRGBColor::UnusualFromARGB(0xff0250bb),  // Luminance: 9.33808%
 | |
|         sRGBColor::UnusualFromARGB(0xff054096)   // Luminance: 5.90106%
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   // Ensure accent color is opaque by blending with white. This serves two
 | |
|   // purposes: On one hand, it avoids surprises if we overdraw. On the other, it
 | |
|   // makes our math below make more sense, as we want to match the browser
 | |
|   // style, which has an opaque accent color.
 | |
|   static nscolor EnsureOpaque(nscolor aAccent) {
 | |
|     if (NS_GET_A(aAccent) != 0xff) {
 | |
|       return NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aAccent);
 | |
|     }
 | |
|     return aAccent;
 | |
|   }
 | |
| 
 | |
|   static nscolor GetLight(nscolor aAccent) {
 | |
|     // The luminance from the light color divided by the one of the accent color
 | |
|     // in the default palette.
 | |
|     constexpr float kLightLuminanceScale = 25.048f / 13.693f;
 | |
|     const float lightLuminanceAdjust = ThemeColors::ScaleLuminanceBy(
 | |
|         RelativeLuminanceUtils::Compute(aAccent), kLightLuminanceScale);
 | |
|     nscolor lightColor =
 | |
|         RelativeLuminanceUtils::Adjust(aAccent, lightLuminanceAdjust);
 | |
|     return NS_RGBA(NS_GET_R(lightColor), NS_GET_G(lightColor),
 | |
|                    NS_GET_B(lightColor), 0x4d);
 | |
|   }
 | |
| 
 | |
|   static nscolor GetDark(nscolor aAccent) {
 | |
|     // Same deal as above (but without the alpha).
 | |
|     constexpr float kDarkLuminanceScale = 9.338f / 13.693f;
 | |
|     const float darkLuminanceAdjust = ThemeColors::ScaleLuminanceBy(
 | |
|         RelativeLuminanceUtils::Compute(aAccent), kDarkLuminanceScale);
 | |
|     return RelativeLuminanceUtils::Adjust(aAccent, darkLuminanceAdjust);
 | |
|   }
 | |
| 
 | |
|   static nscolor GetDarker(nscolor aAccent) {
 | |
|     // Same deal as above.
 | |
|     constexpr float kDarkerLuminanceScale = 5.901f / 13.693f;
 | |
|     const float darkerLuminanceAdjust = ThemeColors::ScaleLuminanceBy(
 | |
|         RelativeLuminanceUtils::Compute(aAccent), kDarkerLuminanceScale);
 | |
|     return RelativeLuminanceUtils::Adjust(aAccent, darkerLuminanceAdjust);
 | |
|   }
 | |
| 
 | |
|   sRGBColor mAccent;
 | |
|   sRGBColor mForeground;
 | |
| 
 | |
|   // Note that depending on the exact accent color, lighter/darker might really
 | |
|   // be inverted.
 | |
|   sRGBColor mAccentLight;
 | |
|   sRGBColor mAccentDark;
 | |
|   sRGBColor mAccentDarker;
 | |
| };
 | |
| 
 | |
| static nscolor GetAccentColor(bool aBackground, ColorScheme aScheme) {
 | |
|   auto useStandins = LookAndFeel::UseStandins(
 | |
|       !StaticPrefs::widget_non_native_theme_use_theme_accent());
 | |
|   return ColorPalette::EnsureOpaque(
 | |
|       LookAndFeel::Color(aBackground ? LookAndFeel::ColorID::Accentcolor
 | |
|                                      : LookAndFeel::ColorID::Accentcolortext,
 | |
|                          aScheme, useStandins));
 | |
| }
 | |
| 
 | |
| static ColorPalette sDefaultLightPalette = ColorPalette::Default();
 | |
| static ColorPalette sDefaultDarkPalette = ColorPalette::Default();
 | |
| 
 | |
| ColorPalette::ColorPalette(nscolor aAccent, nscolor aForeground) {
 | |
|   mAccent = sRGBColor::FromABGR(aAccent);
 | |
|   mForeground = sRGBColor::FromABGR(aForeground);
 | |
|   mAccentLight = sRGBColor::FromABGR(GetLight(aAccent));
 | |
|   mAccentDark = sRGBColor::FromABGR(GetDark(aAccent));
 | |
|   mAccentDarker = sRGBColor::FromABGR(GetDarker(aAccent));
 | |
| }
 | |
| 
 | |
| ThemeAccentColor::ThemeAccentColor(const ComputedStyle& aStyle,
 | |
|                                    ColorScheme aScheme)
 | |
|     : mDefaultPalette(aScheme == ColorScheme::Light ? &sDefaultLightPalette
 | |
|                                                     : &sDefaultDarkPalette) {
 | |
|   const auto& color = aStyle.StyleUI()->mAccentColor;
 | |
|   if (color.IsAuto()) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT(color.IsColor());
 | |
|   nscolor accentColor =
 | |
|       ColorPalette::EnsureOpaque(color.AsColor().CalcColor(aStyle));
 | |
|   if (sRGBColor::FromABGR(accentColor) == mDefaultPalette->mAccent) {
 | |
|     return;
 | |
|   }
 | |
|   mAccentColor.emplace(accentColor);
 | |
| }
 | |
| 
 | |
| sRGBColor ThemeAccentColor::Get() const {
 | |
|   if (!mAccentColor) {
 | |
|     return mDefaultPalette->mAccent;
 | |
|   }
 | |
|   return sRGBColor::FromABGR(*mAccentColor);
 | |
| }
 | |
| 
 | |
| sRGBColor ThemeAccentColor::GetForeground() const {
 | |
|   if (!mAccentColor) {
 | |
|     return mDefaultPalette->mForeground;
 | |
|   }
 | |
|   return sRGBColor::FromABGR(
 | |
|       ThemeColors::ComputeCustomAccentForeground(*mAccentColor));
 | |
| }
 | |
| 
 | |
| sRGBColor ThemeAccentColor::GetLight() const {
 | |
|   if (!mAccentColor) {
 | |
|     return mDefaultPalette->mAccentLight;
 | |
|   }
 | |
|   return sRGBColor::FromABGR(ColorPalette::GetLight(*mAccentColor));
 | |
| }
 | |
| 
 | |
| sRGBColor ThemeAccentColor::GetDark() const {
 | |
|   if (!mAccentColor) {
 | |
|     return mDefaultPalette->mAccentDark;
 | |
|   }
 | |
|   return sRGBColor::FromABGR(ColorPalette::GetDark(*mAccentColor));
 | |
| }
 | |
| 
 | |
| sRGBColor ThemeAccentColor::GetDarker() const {
 | |
|   if (!mAccentColor) {
 | |
|     return mDefaultPalette->mAccentDarker;
 | |
|   }
 | |
|   return sRGBColor::FromABGR(ColorPalette::GetDarker(*mAccentColor));
 | |
| }
 | |
| 
 | |
| auto ThemeColors::ShouldBeHighContrast(const nsPresContext& aPc)
 | |
|     -> HighContrastInfo {
 | |
|   // We make sure that we're drawing backgrounds, since otherwise layout will
 | |
|   // darken our used text colors etc anyways, and that can cause contrast issues
 | |
|   // with dark high-contrast themes.
 | |
|   if (!aPc.GetBackgroundColorDraw()) {
 | |
|     return {};
 | |
|   }
 | |
|   const auto& prefs = PreferenceSheet::PrefsFor(*aPc.Document());
 | |
|   return {prefs.NonNativeThemeShouldBeHighContrast(),
 | |
|           prefs.mMustUseLightSystemColors};
 | |
| }
 | |
| 
 | |
| ColorScheme ThemeColors::ColorSchemeForWidget(const nsIFrame* aFrame,
 | |
|                                               StyleAppearance aAppearance,
 | |
|                                               const HighContrastInfo& aInfo) {
 | |
|   if (aInfo.mMustUseLightSystemColors) {
 | |
|     return ColorScheme::Light;
 | |
|   }
 | |
|   if (!nsNativeTheme::IsWidgetScrollbarPart(aAppearance)) {
 | |
|     return LookAndFeel::ColorSchemeForFrame(aFrame);
 | |
|   }
 | |
|   // Scrollbars are a bit tricky. Their used color-scheme depends on whether the
 | |
|   // background they are on is light or dark.
 | |
|   //
 | |
|   // TODO(emilio): This heuristic effectively predates the color-scheme CSS
 | |
|   // property. Perhaps we should check whether the style or the document set
 | |
|   // `color-scheme` to something that isn't `normal`, and if so go through the
 | |
|   // code-path above.
 | |
|   if (StaticPrefs::widget_disable_dark_scrollbar()) {
 | |
|     return ColorScheme::Light;
 | |
|   }
 | |
|   return nsNativeTheme::IsDarkBackgroundForScrollbar(
 | |
|              const_cast<nsIFrame*>(aFrame))
 | |
|              ? ColorScheme::Dark
 | |
|              : ColorScheme::Light;
 | |
| }
 | |
| 
 | |
| /*static*/
 | |
| void ThemeColors::RecomputeAccentColors() {
 | |
|   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   sDefaultLightPalette =
 | |
|       ColorPalette(GetAccentColor(true, ColorScheme::Light),
 | |
|                    GetAccentColor(false, ColorScheme::Light));
 | |
| 
 | |
|   sDefaultDarkPalette = ColorPalette(GetAccentColor(true, ColorScheme::Dark),
 | |
|                                      GetAccentColor(false, ColorScheme::Dark));
 | |
| }
 | |
| 
 | |
| /*static*/
 | |
| nscolor ThemeColors::ComputeCustomAccentForeground(nscolor aColor) {
 | |
|   // Contrast ratio is defined in
 | |
|   // https://www.w3.org/TR/WCAG20/#contrast-ratiodef as:
 | |
|   //
 | |
|   //   (L1 + 0.05) / (L2 + 0.05)
 | |
|   //
 | |
|   // Where L1 is the lighter color, and L2 is the darker one. So we determine
 | |
|   // whether we're dark or light and resolve the equation for the target ratio.
 | |
|   //
 | |
|   // So when lightening:
 | |
|   //
 | |
|   //   L1 = k * (L2 + 0.05) - 0.05
 | |
|   //
 | |
|   // And when darkening:
 | |
|   //
 | |
|   //   L2 = (L1 + 0.05) / k - 0.05
 | |
|   //
 | |
|   const float luminance = RelativeLuminanceUtils::Compute(aColor);
 | |
| 
 | |
|   // We generally prefer white unless we can't because the color is really light
 | |
|   // and we can't provide reasonable contrast.
 | |
|   const float ratioWithWhite = 1.05f / (luminance + 0.05f);
 | |
|   const bool canBeWhite =
 | |
|       ratioWithWhite >=
 | |
|       StaticPrefs::layout_css_accent_color_min_contrast_ratio();
 | |
|   if (canBeWhite) {
 | |
|     return NS_RGB(0xff, 0xff, 0xff);
 | |
|   }
 | |
|   const float targetRatio =
 | |
|       StaticPrefs::layout_css_accent_color_darkening_target_contrast_ratio();
 | |
|   const float targetLuminance = (luminance + 0.05f) / targetRatio - 0.05f;
 | |
|   return RelativeLuminanceUtils::Adjust(aColor, targetLuminance);
 | |
| }
 | |
| 
 | |
| nscolor ThemeColors::AdjustUnthemedScrollbarThumbColor(
 | |
|     nscolor aFaceColor, dom::ElementState aStates) {
 | |
|   // In Windows 10, scrollbar thumb has the following colors:
 | |
|   //
 | |
|   // State  | Color    | Luminance
 | |
|   // -------+----------+----------
 | |
|   // Normal | Gray 205 |     61.0%
 | |
|   // Hover  | Gray 166 |     38.1%
 | |
|   // Active | Gray 96  |     11.7%
 | |
|   //
 | |
|   // This function is written based on the ratios between the values.
 | |
|   bool isActive = aStates.HasState(dom::ElementState::ACTIVE);
 | |
|   bool isHover = aStates.HasState(dom::ElementState::HOVER);
 | |
|   if (!isActive && !isHover) {
 | |
|     return aFaceColor;
 | |
|   }
 | |
|   float luminance = RelativeLuminanceUtils::Compute(aFaceColor);
 | |
|   if (isActive) {
 | |
|     // 11.7 / 61.0
 | |
|     luminance = ScaleLuminanceBy(luminance, 0.192f);
 | |
|   } else {
 | |
|     // 38.1 / 61.0
 | |
|     luminance = ScaleLuminanceBy(luminance, 0.625f);
 | |
|   }
 | |
|   return RelativeLuminanceUtils::Adjust(aFaceColor, luminance);
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::widget
 |