зеркало из https://github.com/mozilla/gecko-dev.git
2289 строки
89 KiB
C++
2289 строки
89 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 "nsNativeBasicTheme.h"
|
|
|
|
#include "gfxBlur.h"
|
|
#include "mozilla/MathAlgorithms.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/gfx/Rect.h"
|
|
#include "mozilla/gfx/Types.h"
|
|
#include "mozilla/gfx/Filters.h"
|
|
#include "mozilla/RelativeLuminanceUtils.h"
|
|
#include "mozilla/StaticPrefs_widget.h"
|
|
#include "mozilla/webrender/WebRenderAPI.h"
|
|
#include "nsCSSColorUtils.h"
|
|
#include "nsCSSRendering.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "PathHelpers.h"
|
|
|
|
#include "nsDeviceContext.h"
|
|
|
|
#include "nsRangeFrame.h"
|
|
#include "mozilla/dom/HTMLMeterElement.h"
|
|
#include "mozilla/dom/HTMLProgressElement.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::widget;
|
|
using namespace mozilla::gfx;
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme, nsNativeTheme, nsITheme)
|
|
|
|
namespace {
|
|
|
|
static constexpr sRGBColor sTransparent = sRGBColor::White(0.0);
|
|
|
|
// This pushes and pops a clip rect to the draw target.
|
|
//
|
|
// This is done to reduce fuzz in places where we may have antialiasing,
|
|
// because skia is not clip-invariant: given different clips, it does not
|
|
// guarantee the same result, even if the painted content doesn't intersect
|
|
// the clips.
|
|
//
|
|
// This is a bit sad, overall, but...
|
|
struct MOZ_RAII AutoClipRect {
|
|
AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
|
|
mDt.PushClipRect(aRect.ToUnknownRect());
|
|
}
|
|
|
|
~AutoClipRect() { mDt.PopClip(); }
|
|
|
|
private:
|
|
DrawTarget& mDt;
|
|
};
|
|
|
|
static LayoutDeviceIntCoord SnapBorderWidth(
|
|
CSSCoord aCssWidth, nsNativeBasicTheme::DPIRatio aDpiRatio) {
|
|
if (aCssWidth == 0.0f) {
|
|
return 0;
|
|
}
|
|
return std::max(LayoutDeviceIntCoord(1), (aCssWidth * aDpiRatio).Truncated());
|
|
}
|
|
|
|
[[nodiscard]] static float ScaleLuminanceBy(float aLuminance, float aFactor) {
|
|
return aLuminance >= 0.18f ? aLuminance * aFactor : aLuminance / aFactor;
|
|
}
|
|
|
|
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, sDefaultAccentForeground,
|
|
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 = 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 = 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 = 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 ThemedAccentColor(bool aBackground) {
|
|
MOZ_ASSERT(StaticPrefs::widget_non_native_theme_use_theme_accent());
|
|
// TODO(emilio): In the future we should probably add dark-color-scheme
|
|
// support for non-native form controls.
|
|
return ColorPalette::EnsureOpaque(LookAndFeel::Color(
|
|
aBackground ? LookAndFeel::ColorID::MozAccentColor
|
|
: LookAndFeel::ColorID::MozAccentColorForeground,
|
|
LookAndFeel::ColorScheme::Light, LookAndFeel::UseStandins::No));
|
|
}
|
|
|
|
static ColorPalette sDefaultPalette = 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));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
nscolor nsNativeBasicTheme::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);
|
|
}
|
|
|
|
class nsNativeBasicTheme::AccentColor {
|
|
Maybe<nscolor> mAccentColor;
|
|
|
|
public:
|
|
AccentColor() = default;
|
|
|
|
explicit AccentColor(const ComputedStyle& aStyle) {
|
|
const auto& color = aStyle.StyleUI()->mAccentColor;
|
|
if (color.IsColor()) {
|
|
mAccentColor.emplace(
|
|
ColorPalette::EnsureOpaque(color.AsColor().CalcColor(aStyle)));
|
|
} else {
|
|
MOZ_ASSERT(color.IsAuto());
|
|
}
|
|
}
|
|
|
|
sRGBColor Get() const {
|
|
if (!mAccentColor) {
|
|
return sDefaultPalette.mAccent;
|
|
}
|
|
return sRGBColor::FromABGR(*mAccentColor);
|
|
}
|
|
|
|
sRGBColor GetForeground() const {
|
|
if (!mAccentColor) {
|
|
return sDefaultPalette.mForeground;
|
|
}
|
|
return sRGBColor::FromABGR(ComputeCustomAccentForeground(*mAccentColor));
|
|
}
|
|
|
|
sRGBColor GetLight() const {
|
|
if (!mAccentColor) {
|
|
return sDefaultPalette.mAccentLight;
|
|
}
|
|
return sRGBColor::FromABGR(ColorPalette::GetLight(*mAccentColor));
|
|
}
|
|
|
|
sRGBColor GetDark() const {
|
|
if (!mAccentColor) {
|
|
return sDefaultPalette.mAccentDark;
|
|
}
|
|
return sRGBColor::FromABGR(ColorPalette::GetDark(*mAccentColor));
|
|
}
|
|
|
|
sRGBColor GetDarker() const {
|
|
if (!mAccentColor) {
|
|
return sDefaultPalette.mAccentDarker;
|
|
}
|
|
return sRGBColor::FromABGR(ColorPalette::GetDarker(*mAccentColor));
|
|
}
|
|
};
|
|
|
|
// Widget color information associated to a particular frame.
|
|
class nsNativeBasicTheme::Colors {
|
|
const AccentColor mAccentColor;
|
|
const dom::Document& mDoc;
|
|
const bool mHighContrast;
|
|
const LookAndFeel::ColorScheme mColorScheme;
|
|
|
|
public:
|
|
explicit Colors(const nsIFrame* aFrame)
|
|
: mAccentColor(*aFrame->Style()),
|
|
mDoc(*aFrame->PresContext()->Document()),
|
|
mHighContrast(ShouldBeHighContrast(*aFrame->PresContext())),
|
|
mColorScheme(LookAndFeel::ColorSchemeForFrame(aFrame)) {}
|
|
|
|
const AccentColor& Accent() const { return mAccentColor; }
|
|
bool HighContrast() const { return mHighContrast; }
|
|
bool IsDark() const { return mColorScheme == LookAndFeel::ColorScheme::Dark; }
|
|
|
|
nscolor SystemNs(StyleSystemColor aColor) const {
|
|
return LookAndFeel::Color(aColor, mColorScheme,
|
|
LookAndFeel::ShouldUseStandins(mDoc, aColor));
|
|
}
|
|
|
|
sRGBColor System(StyleSystemColor aColor) const {
|
|
return sRGBColor::FromABGR(SystemNs(aColor));
|
|
}
|
|
|
|
template <typename Compute>
|
|
sRGBColor SystemOrElse(StyleSystemColor aColor, Compute aCompute) const {
|
|
if (auto color = LookAndFeel::GetColor(
|
|
aColor, mColorScheme,
|
|
LookAndFeel::ShouldUseStandins(mDoc, aColor))) {
|
|
return sRGBColor::FromABGR(*color);
|
|
}
|
|
return aCompute();
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> SystemPair(StyleSystemColor aFirst,
|
|
StyleSystemColor aSecond) const {
|
|
return std::make_pair(System(aFirst), System(aSecond));
|
|
}
|
|
};
|
|
|
|
CSSIntCoord nsNativeBasicTheme::sHorizontalScrollbarHeight = CSSIntCoord(0);
|
|
CSSIntCoord nsNativeBasicTheme::sVerticalScrollbarWidth = CSSIntCoord(0);
|
|
|
|
static constexpr nsLiteralCString kPrefs[] = {
|
|
"widget.non-native-theme.use-theme-accent"_ns,
|
|
"widget.non-native-theme.win.scrollbar.use-system-size"_ns,
|
|
"widget.non-native-theme.scrollbar.size"_ns,
|
|
};
|
|
|
|
void nsNativeBasicTheme::Init() {
|
|
for (const auto& pref : kPrefs) {
|
|
Preferences::RegisterCallback(PrefChangedCallback, pref);
|
|
}
|
|
LookAndFeelChanged();
|
|
}
|
|
|
|
void nsNativeBasicTheme::Shutdown() {
|
|
for (const auto& pref : kPrefs) {
|
|
Preferences::UnregisterCallback(PrefChangedCallback, pref);
|
|
}
|
|
}
|
|
|
|
void nsNativeBasicTheme::LookAndFeelChanged() {
|
|
RecomputeAccentColors();
|
|
RecomputeScrollbarParams();
|
|
}
|
|
|
|
void nsNativeBasicTheme::RecomputeAccentColors() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
|
|
if (!StaticPrefs::widget_non_native_theme_use_theme_accent()) {
|
|
sDefaultPalette = ColorPalette::Default();
|
|
return;
|
|
}
|
|
|
|
sDefaultPalette =
|
|
ColorPalette(ThemedAccentColor(true), ThemedAccentColor(false));
|
|
}
|
|
|
|
void nsNativeBasicTheme::RecomputeScrollbarParams() {
|
|
uint32_t defaultSize = StaticPrefs::widget_non_native_theme_scrollbar_size();
|
|
if (StaticPrefs::widget_non_native_theme_win_scrollbar_use_system_size()) {
|
|
sHorizontalScrollbarHeight = LookAndFeel::GetInt(
|
|
LookAndFeel::IntID::SystemHorizontalScrollbarHeight, defaultSize);
|
|
sVerticalScrollbarWidth = LookAndFeel::GetInt(
|
|
LookAndFeel::IntID::SystemVerticalScrollbarWidth, defaultSize);
|
|
} else {
|
|
sHorizontalScrollbarHeight = sVerticalScrollbarWidth = defaultSize;
|
|
}
|
|
// On GTK, widgets don't account for text scale factor, but that's included
|
|
// in the usual DPI computations, so we undo that here, just like
|
|
// GetMonitorScaleFactor does it in nsNativeThemeGTK.
|
|
float scale =
|
|
LookAndFeel::GetFloat(LookAndFeel::FloatID::TextScaleFactor, 1.0f);
|
|
if (scale != 1.0f) {
|
|
sVerticalScrollbarWidth = float(sVerticalScrollbarWidth) / scale;
|
|
sHorizontalScrollbarHeight = float(sHorizontalScrollbarHeight) / scale;
|
|
}
|
|
}
|
|
|
|
static bool IsScrollbarWidthThin(nsIFrame* aFrame) {
|
|
ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
|
|
auto scrollbarWidth = style->StyleUIReset()->mScrollbarWidth;
|
|
return scrollbarWidth == StyleScrollbarWidth::Thin;
|
|
}
|
|
|
|
/* static */
|
|
auto nsNativeBasicTheme::GetDPIRatioForScrollbarPart(nsPresContext* aPc)
|
|
-> DPIRatio {
|
|
if (auto* rootPc = aPc->GetRootPresContext()) {
|
|
if (nsCOMPtr<nsIWidget> widget = rootPc->GetRootWidget()) {
|
|
return widget->GetDefaultScale();
|
|
}
|
|
}
|
|
return DPIRatio(float(AppUnitsPerCSSPixel()) /
|
|
aPc->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
|
|
}
|
|
|
|
/* static */
|
|
auto nsNativeBasicTheme::GetDPIRatio(nsPresContext* aPc,
|
|
StyleAppearance aAppearance) -> DPIRatio {
|
|
// Widgets react to zoom, except scrollbars.
|
|
if (IsWidgetScrollbarPart(aAppearance)) {
|
|
return GetDPIRatioForScrollbarPart(aPc);
|
|
}
|
|
return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc->AppUnitsPerDevPixel());
|
|
}
|
|
|
|
/* static */
|
|
auto nsNativeBasicTheme::GetDPIRatio(nsIFrame* aFrame,
|
|
StyleAppearance aAppearance) -> DPIRatio {
|
|
return GetDPIRatio(aFrame->PresContext(), aAppearance);
|
|
}
|
|
|
|
// Checkbox and radio need to preserve aspect-ratio for compat. We also snap the
|
|
// size to exact device pixels to avoid snapping disorting the circles.
|
|
static LayoutDeviceRect CheckBoxRadioRect(const LayoutDeviceRect& aRect) {
|
|
// Place a square rect in the center of aRect.
|
|
auto size = std::trunc(std::min(aRect.width, aRect.height));
|
|
auto position = aRect.Center() - LayoutDevicePoint(size * 0.5, size * 0.5);
|
|
return LayoutDeviceRect(position, LayoutDeviceSize(size, size));
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeCheckboxColors(
|
|
const EventStates& aState, StyleAppearance aAppearance,
|
|
const Colors& aColors) {
|
|
MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
|
|
aAppearance == StyleAppearance::Radio);
|
|
|
|
bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
|
|
bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED);
|
|
bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
|
|
aState.HasState(NS_EVENT_STATE_INDETERMINATE);
|
|
|
|
if (isChecked || isIndeterminate) {
|
|
if (isDisabled) {
|
|
auto color = ComputeBorderColor(aState, aColors, OutlineCoversBorder::No);
|
|
return std::make_pair(color, color);
|
|
}
|
|
|
|
bool isActive =
|
|
aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
|
|
bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
|
|
const auto& color = isActive ? aColors.Accent().GetDarker()
|
|
: isHovered ? aColors.Accent().GetDark()
|
|
: aColors.Accent().Get();
|
|
return std::make_pair(color, color);
|
|
}
|
|
|
|
return ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::No);
|
|
}
|
|
|
|
sRGBColor nsNativeBasicTheme::ComputeCheckmarkColor(const EventStates& aState,
|
|
const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return aColors.System(StyleSystemColor::Selecteditemtext);
|
|
}
|
|
if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
|
|
return sRGBColor::White(.8f);
|
|
}
|
|
return aColors.Accent().GetForeground();
|
|
}
|
|
|
|
sRGBColor nsNativeBasicTheme::ComputeBorderColor(
|
|
const EventStates& aState, const Colors& aColors,
|
|
OutlineCoversBorder aOutlineCoversBorder) {
|
|
bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
|
|
if (aColors.HighContrast()) {
|
|
return aColors.System(isDisabled ? StyleSystemColor::Graytext
|
|
: StyleSystemColor::Buttontext);
|
|
}
|
|
bool isActive =
|
|
aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
|
|
bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
|
|
bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUSRING);
|
|
if (isDisabled) {
|
|
return sColorGrey40Alpha50;
|
|
}
|
|
if (isFocused && aOutlineCoversBorder == OutlineCoversBorder::Yes) {
|
|
// If we draw the outline over the border, prevent issues where the border
|
|
// shows underneath if it snaps in the wrong direction by using a
|
|
// transparent border. An alternative to this is ensuring that we snap the
|
|
// offset in PaintRoundedFocusRect the same was a we snap border widths, so
|
|
// that negative offsets are guaranteed to cover the border.
|
|
// But this looks harder to mess up.
|
|
return sTransparent;
|
|
}
|
|
bool dark = aColors.IsDark();
|
|
if (isActive) {
|
|
return dark ? sColorGrey20 : sColorGrey60;
|
|
}
|
|
if (isHovered) {
|
|
return dark ? sColorGrey30 : sColorGrey50;
|
|
}
|
|
return sColorGrey40;
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeButtonColors(
|
|
const EventStates& aState, const Colors& aColors, nsIFrame* aFrame) {
|
|
bool isActive =
|
|
aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
|
|
bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
|
|
bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
|
|
|
|
const sRGBColor backgroundColor = [&] {
|
|
if (isDisabled) {
|
|
return aColors.System(StyleSystemColor::MozButtondisabledface);
|
|
}
|
|
if (isActive) {
|
|
return aColors.System(StyleSystemColor::MozButtonactiveface);
|
|
}
|
|
if (isHovered) {
|
|
return aColors.System(StyleSystemColor::MozButtonhoverface);
|
|
}
|
|
return aColors.System(StyleSystemColor::Buttonface);
|
|
}();
|
|
|
|
const sRGBColor borderColor =
|
|
ComputeBorderColor(aState, aColors, OutlineCoversBorder::Yes);
|
|
return std::make_pair(backgroundColor, borderColor);
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeTextfieldColors(
|
|
const EventStates& aState, const Colors& aColors,
|
|
OutlineCoversBorder aOutlineCoversBorder) {
|
|
nscolor backgroundColor = [&] {
|
|
if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
|
|
return aColors.SystemNs(StyleSystemColor::MozDisabledfield);
|
|
}
|
|
return aColors.SystemNs(StyleSystemColor::Field);
|
|
}();
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_AUTOFILL)) {
|
|
backgroundColor = NS_ComposeColors(
|
|
backgroundColor,
|
|
aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
|
|
}
|
|
|
|
const sRGBColor borderColor =
|
|
ComputeBorderColor(aState, aColors, aOutlineCoversBorder);
|
|
return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeProgressColors(
|
|
const EventStates& aState, const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return aColors.SystemPair(StyleSystemColor::Selecteditem,
|
|
StyleSystemColor::Buttontext);
|
|
}
|
|
|
|
bool isActive =
|
|
aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
|
|
bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
|
|
bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
|
|
|
|
if (isDisabled) {
|
|
return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
|
|
}
|
|
if (isActive || isHovered) {
|
|
return std::make_pair(aColors.Accent().GetDark(),
|
|
aColors.Accent().GetDarker());
|
|
}
|
|
return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeTrackColors(
|
|
const EventStates& aState, const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return aColors.SystemPair(StyleSystemColor::Window,
|
|
StyleSystemColor::Buttontext);
|
|
}
|
|
bool isActive =
|
|
aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
|
|
bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
|
|
bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
|
|
|
|
if (isDisabled) {
|
|
return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
|
|
}
|
|
if (isActive || isHovered) {
|
|
return std::make_pair(sColorGrey20, sColorGrey50);
|
|
}
|
|
return std::make_pair(sColorGrey10, sColorGrey40);
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeThumbColors(
|
|
const EventStates& aState, const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return aColors.SystemPair(StyleSystemColor::Selecteditemtext,
|
|
StyleSystemColor::Selecteditem);
|
|
}
|
|
|
|
bool isActive =
|
|
aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
|
|
bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
|
|
bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
|
|
|
|
const sRGBColor& backgroundColor = [&] {
|
|
if (isDisabled) {
|
|
return sColorGrey40;
|
|
}
|
|
if (isActive) {
|
|
return aColors.Accent().Get();
|
|
}
|
|
if (isHovered) {
|
|
return sColorGrey60;
|
|
}
|
|
return sColorGrey50;
|
|
}();
|
|
|
|
const sRGBColor borderColor = sRGBColor::OpaqueWhite();
|
|
return std::make_pair(backgroundColor, borderColor);
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeProgressColors(
|
|
const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return aColors.SystemPair(StyleSystemColor::Selecteditem,
|
|
StyleSystemColor::Buttontext);
|
|
}
|
|
return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeProgressTrackColors(
|
|
const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return aColors.SystemPair(StyleSystemColor::Buttonface,
|
|
StyleSystemColor::Buttontext);
|
|
}
|
|
return std::make_pair(sColorGrey10, sColorGrey40);
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeMeterchunkColors(
|
|
const EventStates& aMeterState, const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return ComputeProgressColors(aColors);
|
|
}
|
|
sRGBColor borderColor = sColorMeterGreen20;
|
|
sRGBColor chunkColor = sColorMeterGreen10;
|
|
|
|
if (aMeterState.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
|
|
borderColor = sColorMeterYellow20;
|
|
chunkColor = sColorMeterYellow10;
|
|
} else if (aMeterState.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
|
|
borderColor = sColorMeterRed20;
|
|
chunkColor = sColorMeterRed10;
|
|
}
|
|
|
|
return std::make_pair(chunkColor, borderColor);
|
|
}
|
|
|
|
std::array<sRGBColor, 3> nsNativeBasicTheme::ComputeFocusRectColors(
|
|
const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return {aColors.System(StyleSystemColor::Selecteditem),
|
|
aColors.System(StyleSystemColor::Buttontext),
|
|
aColors.System(StyleSystemColor::Window)};
|
|
}
|
|
const auto& accent = aColors.Accent();
|
|
const sRGBColor middle =
|
|
aColors.IsDark() ? sRGBColor::Black(.3f) : sRGBColor::White(.3f);
|
|
return {accent.Get(), middle, accent.GetLight()};
|
|
}
|
|
|
|
bool nsNativeBasicTheme::IsScrollbarTrackOpaque(nsIFrame* aFrame) {
|
|
auto trackColor = ComputeScrollbarTrackColor(
|
|
aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame),
|
|
aFrame->PresContext()->Document()->GetDocumentState(), Colors(aFrame));
|
|
return trackColor.a == 1.0f;
|
|
}
|
|
|
|
sRGBColor nsNativeBasicTheme::ComputeScrollbarTrackColor(
|
|
nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aDocumentState, const Colors& aColors) {
|
|
const nsStyleUI* ui = aStyle.StyleUI();
|
|
if (aColors.HighContrast()) {
|
|
return aColors.System(StyleSystemColor::Window);
|
|
}
|
|
if (ShouldUseDarkScrollbar(aFrame, aStyle)) {
|
|
return sRGBColor::FromU8(20, 20, 25, 77);
|
|
}
|
|
if (ui->mScrollbarColor.IsColors()) {
|
|
return sRGBColor::FromABGR(
|
|
ui->mScrollbarColor.AsColors().track.CalcColor(aStyle));
|
|
}
|
|
if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
|
|
return aColors.SystemOrElse(StyleSystemColor::ThemedScrollbarInactive,
|
|
[] { return sScrollbarColor; });
|
|
}
|
|
return aColors.SystemOrElse(StyleSystemColor::ThemedScrollbar,
|
|
[] { return sScrollbarColor; });
|
|
}
|
|
|
|
nscolor nsNativeBasicTheme::AdjustUnthemedScrollbarThumbColor(
|
|
nscolor aFaceColor, EventStates 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(NS_EVENT_STATE_ACTIVE);
|
|
bool isHover = aStates.HasState(NS_EVENT_STATE_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);
|
|
}
|
|
|
|
/*static*/
|
|
nscolor nsNativeBasicTheme::GetScrollbarButtonColor(nscolor aTrackColor,
|
|
EventStates aStates) {
|
|
// See numbers in GetScrollbarArrowColor.
|
|
// This function is written based on ratios between values listed there.
|
|
|
|
bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE);
|
|
bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER);
|
|
if (!isActive && !isHover) {
|
|
return aTrackColor;
|
|
}
|
|
float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
|
|
if (isActive) {
|
|
if (luminance >= 0.18f) {
|
|
luminance *= 0.134f;
|
|
} else {
|
|
luminance /= 0.134f;
|
|
luminance = std::min(luminance, 1.0f);
|
|
}
|
|
} else {
|
|
if (luminance >= 0.18f) {
|
|
luminance *= 0.805f;
|
|
} else {
|
|
luminance /= 0.805f;
|
|
}
|
|
}
|
|
return RelativeLuminanceUtils::Adjust(aTrackColor, luminance);
|
|
}
|
|
|
|
/*static*/
|
|
Maybe<nscolor> nsNativeBasicTheme::GetScrollbarArrowColor(
|
|
nscolor aButtonColor) {
|
|
// In Windows 10 scrollbar, there are several gray colors used:
|
|
//
|
|
// State | Background (lum) | Arrow | Contrast
|
|
// -------+------------------+---------+---------
|
|
// Normal | Gray 240 (87.1%) | Gray 96 | 5.5
|
|
// Hover | Gray 218 (70.1%) | Black | 15.0
|
|
// Active | Gray 96 (11.7%) | White | 6.3
|
|
//
|
|
// Contrast value is computed based on the definition in
|
|
// https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
|
//
|
|
// This function is written based on these values.
|
|
|
|
if (NS_GET_A(aButtonColor) == 0) {
|
|
// If the button color is transparent, because of e.g.
|
|
// scrollbar-color: <something> transparent, then use
|
|
// the thumb color, which is expected to have enough
|
|
// contrast.
|
|
return Nothing();
|
|
}
|
|
|
|
float luminance = RelativeLuminanceUtils::Compute(aButtonColor);
|
|
// Color with luminance larger than 0.72 has contrast ratio over 4.6
|
|
// to color with luminance of gray 96, so this value is chosen for
|
|
// this range. It is the luminance of gray 221.
|
|
if (luminance >= 0.72) {
|
|
// ComputeRelativeLuminanceFromComponents(96). That function cannot
|
|
// be constexpr because of std::pow.
|
|
const float GRAY96_LUMINANCE = 0.117f;
|
|
return Some(RelativeLuminanceUtils::Adjust(aButtonColor, GRAY96_LUMINANCE));
|
|
}
|
|
// The contrast ratio of a color to black equals that to white when its
|
|
// luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
|
|
// thus the value below. It's the lumanince of gray 118.
|
|
//
|
|
// TODO(emilio): Maybe the button alpha is not the best thing to use here and
|
|
// we should use the thumb alpha? It seems weird that the color of the arrow
|
|
// depends on the opacity of the scrollbar thumb...
|
|
if (luminance >= 0.18) {
|
|
return Some(NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor)));
|
|
}
|
|
return Some(NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor)));
|
|
}
|
|
|
|
bool nsNativeBasicTheme::ShouldUseDarkScrollbar(nsIFrame* aFrame,
|
|
const ComputedStyle& aStyle) {
|
|
if (StaticPrefs::widget_disable_dark_scrollbar()) {
|
|
return false;
|
|
}
|
|
if (aStyle.StyleUI()->mScrollbarColor.IsColors()) {
|
|
return false;
|
|
}
|
|
return nsNativeTheme::IsDarkBackground(aFrame);
|
|
}
|
|
|
|
// Don't use the theme color for dark scrollbars if it's not a color (if it's
|
|
// grey-ish), as that'd either lack enough contrast, or be close to what we'd do
|
|
// by default anyways.
|
|
static bool ShouldUseColorForActiveDarkScrollbarThumb(nscolor aColor) {
|
|
auto IsDifferentEnough = [](int32_t aChannel, int32_t aOtherChannel) {
|
|
return std::abs(aChannel - aOtherChannel) > 10;
|
|
};
|
|
return IsDifferentEnough(NS_GET_R(aColor), NS_GET_G(aColor)) ||
|
|
IsDifferentEnough(NS_GET_R(aColor), NS_GET_B(aColor));
|
|
}
|
|
|
|
sRGBColor nsNativeBasicTheme::ComputeScrollbarThumbColor(
|
|
nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aElementState, const EventStates& aDocumentState,
|
|
const Colors& aColors) {
|
|
if (!aColors.HighContrast() && ShouldUseDarkScrollbar(aFrame, aStyle)) {
|
|
if (aElementState.HasState(NS_EVENT_STATE_ACTIVE) &&
|
|
StaticPrefs::widget_non_native_theme_scrollbar_active_always_themed()) {
|
|
auto color = LookAndFeel::GetColor(
|
|
StyleSystemColor::ThemedScrollbarThumbActive,
|
|
LookAndFeel::ColorScheme::Light, LookAndFeel::UseStandins::No);
|
|
if (color && ShouldUseColorForActiveDarkScrollbarThumb(*color)) {
|
|
return sRGBColor::FromABGR(*color);
|
|
}
|
|
}
|
|
return sRGBColor::FromABGR(AdjustUnthemedScrollbarThumbColor(
|
|
NS_RGBA(249, 249, 250, 102), aElementState));
|
|
}
|
|
|
|
const nsStyleUI* ui = aStyle.StyleUI();
|
|
if (ui->mScrollbarColor.IsColors()) {
|
|
return sRGBColor::FromABGR(AdjustUnthemedScrollbarThumbColor(
|
|
ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle), aElementState));
|
|
}
|
|
|
|
auto systemColor = [&] {
|
|
if (aDocumentState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
|
|
return StyleSystemColor::ThemedScrollbarThumbInactive;
|
|
}
|
|
if (aElementState.HasState(NS_EVENT_STATE_ACTIVE)) {
|
|
if (aColors.HighContrast()) {
|
|
return StyleSystemColor::Selecteditem;
|
|
}
|
|
return StyleSystemColor::ThemedScrollbarThumbActive;
|
|
}
|
|
if (aElementState.HasState(NS_EVENT_STATE_HOVER)) {
|
|
if (aColors.HighContrast()) {
|
|
return StyleSystemColor::Selecteditem;
|
|
}
|
|
return StyleSystemColor::ThemedScrollbarThumbHover;
|
|
}
|
|
if (aColors.HighContrast()) {
|
|
return StyleSystemColor::Windowtext;
|
|
}
|
|
return StyleSystemColor::ThemedScrollbarThumb;
|
|
}();
|
|
|
|
return aColors.SystemOrElse(systemColor, [&] {
|
|
return sRGBColor::FromABGR(AdjustUnthemedScrollbarThumbColor(
|
|
sScrollbarThumbColor.ToABGR(), aElementState));
|
|
});
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor>
|
|
nsNativeBasicTheme::ComputeScrollbarButtonColors(
|
|
nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
|
|
const EventStates& aElementState, const EventStates& aDocumentState,
|
|
const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
if (aElementState.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE |
|
|
NS_EVENT_STATE_HOVER)) {
|
|
return aColors.SystemPair(StyleSystemColor::Selecteditem,
|
|
StyleSystemColor::Buttonface);
|
|
}
|
|
return aColors.SystemPair(StyleSystemColor::Window,
|
|
StyleSystemColor::Windowtext);
|
|
}
|
|
|
|
auto trackColor =
|
|
ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
|
|
nscolor buttonColor =
|
|
GetScrollbarButtonColor(trackColor.ToABGR(), aElementState);
|
|
auto arrowColor =
|
|
GetScrollbarArrowColor(buttonColor)
|
|
.map(sRGBColor::FromABGR)
|
|
.valueOrFrom([&] {
|
|
return ComputeScrollbarThumbColor(aFrame, aStyle, aElementState,
|
|
aDocumentState, aColors);
|
|
});
|
|
return {sRGBColor::FromABGR(buttonColor), arrowColor};
|
|
}
|
|
|
|
static const CSSCoord kInnerFocusOutlineWidth = 2.0f;
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintRoundedFocusRect(PaintBackendData& aBackendData,
|
|
const LayoutDeviceRect& aRect,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio,
|
|
CSSCoord aRadius,
|
|
CSSCoord aOffset) {
|
|
// NOTE(emilio): If the widths or offsets here change, make sure to tweak
|
|
// the GetWidgetOverflow path for FocusOutline.
|
|
auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors(aColors);
|
|
|
|
LayoutDeviceRect focusRect(aRect);
|
|
|
|
// The focus rect is painted outside of the border area (aRect), see:
|
|
//
|
|
// data:text/html,<div style="border: 1px solid; outline: 2px solid
|
|
// red">Foobar</div>
|
|
//
|
|
// But some controls might provide a negative offset to cover the border, if
|
|
// necessary.
|
|
CSSCoord strokeWidth = kInnerFocusOutlineWidth;
|
|
auto strokeWidthDevPx =
|
|
LayoutDeviceCoord(SnapBorderWidth(strokeWidth, aDpiRatio));
|
|
CSSCoord strokeRadius = aRadius;
|
|
focusRect.Inflate(aOffset * aDpiRatio + strokeWidthDevPx);
|
|
|
|
PaintRoundedRectWithRadius(aBackendData, focusRect, sTransparent, innerColor,
|
|
strokeWidth, strokeRadius, aDpiRatio);
|
|
|
|
strokeWidth = CSSCoord(1.0f);
|
|
strokeWidthDevPx = LayoutDeviceCoord(SnapBorderWidth(strokeWidth, aDpiRatio));
|
|
strokeRadius += strokeWidth;
|
|
focusRect.Inflate(strokeWidthDevPx);
|
|
|
|
PaintRoundedRectWithRadius(aBackendData, focusRect, sTransparent, middleColor,
|
|
strokeWidth, strokeRadius, aDpiRatio);
|
|
|
|
strokeWidth = CSSCoord(2.0f);
|
|
strokeWidthDevPx = LayoutDeviceCoord(SnapBorderWidth(strokeWidth, aDpiRatio));
|
|
strokeRadius += strokeWidth;
|
|
focusRect.Inflate(strokeWidthDevPx);
|
|
|
|
PaintRoundedRectWithRadius(aBackendData, focusRect, sTransparent, outerColor,
|
|
strokeWidth, strokeRadius, aDpiRatio);
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintRoundedRectWithRadius(
|
|
WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
|
|
const LayoutDeviceRect& aClipRect, const sRGBColor& aBackgroundColor,
|
|
const sRGBColor& aBorderColor, CSSCoord aBorderWidth, CSSCoord aRadius,
|
|
DPIRatio aDpiRatio) {
|
|
const bool kBackfaceIsVisible = true;
|
|
const LayoutDeviceCoord borderWidth(SnapBorderWidth(aBorderWidth, aDpiRatio));
|
|
const LayoutDeviceCoord radius(aRadius * aDpiRatio);
|
|
const wr::LayoutRect dest = wr::ToLayoutRect(aRect);
|
|
const wr::LayoutRect clip = wr::ToLayoutRect(aClipRect);
|
|
|
|
// Push the background.
|
|
if (aBackgroundColor.a) {
|
|
auto backgroundColor = wr::ToColorF(ToDeviceColor(aBackgroundColor));
|
|
wr::LayoutRect backgroundRect = [&] {
|
|
LayoutDeviceRect bg = aRect;
|
|
bg.Deflate(borderWidth);
|
|
return wr::ToLayoutRect(bg);
|
|
}();
|
|
if (!radius) {
|
|
aWrData.mBuilder.PushRect(backgroundRect, clip, kBackfaceIsVisible,
|
|
backgroundColor);
|
|
} else {
|
|
// NOTE(emilio): This follows DisplayListBuilder::PushRoundedRect and
|
|
// draws the rounded fill as an extra thick rounded border instead of a
|
|
// rectangle that's clipped to a rounded clip. Refer to that method for a
|
|
// justification. See bug 1694269.
|
|
LayoutDeviceCoord backgroundRadius =
|
|
std::max(0.0f, float(radius) - float(borderWidth));
|
|
wr::BorderSide side = {backgroundColor, wr::BorderStyle::Solid};
|
|
const wr::BorderSide sides[4] = {side, side, side, side};
|
|
float h = backgroundRect.width() * 0.6f;
|
|
float v = backgroundRect.height() * 0.6f;
|
|
wr::LayoutSideOffsets widths = {v, h, v, h};
|
|
wr::BorderRadius radii = {{backgroundRadius, backgroundRadius},
|
|
{backgroundRadius, backgroundRadius},
|
|
{backgroundRadius, backgroundRadius},
|
|
{backgroundRadius, backgroundRadius}};
|
|
aWrData.mBuilder.PushBorder(backgroundRect, clip, kBackfaceIsVisible,
|
|
widths, {sides, 4}, radii);
|
|
}
|
|
}
|
|
|
|
if (borderWidth && aBorderColor.a) {
|
|
// Push the border.
|
|
const auto borderColor = ToDeviceColor(aBorderColor);
|
|
const auto side = wr::ToBorderSide(borderColor, StyleBorderStyle::Solid);
|
|
const wr::BorderSide sides[4] = {side, side, side, side};
|
|
const LayoutDeviceSize sideRadius(radius, radius);
|
|
const auto widths =
|
|
wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
|
|
const auto wrRadius =
|
|
wr::ToBorderRadius(sideRadius, sideRadius, sideRadius, sideRadius);
|
|
aWrData.mBuilder.PushBorder(dest, clip, kBackfaceIsVisible, widths,
|
|
{sides, 4}, wrRadius);
|
|
}
|
|
}
|
|
|
|
void nsNativeBasicTheme::FillRect(DrawTarget& aDt,
|
|
const LayoutDeviceRect& aRect,
|
|
const sRGBColor& aColor) {
|
|
aDt.FillRect(aRect.ToUnknownRect(), ColorPattern(ToDeviceColor(aColor)));
|
|
}
|
|
|
|
void nsNativeBasicTheme::FillRect(WebRenderBackendData& aWrData,
|
|
const LayoutDeviceRect& aRect,
|
|
const sRGBColor& aColor) {
|
|
const bool kBackfaceIsVisible = true;
|
|
auto dest = wr::ToLayoutRect(aRect);
|
|
aWrData.mBuilder.PushRect(dest, dest, kBackfaceIsVisible,
|
|
wr::ToColorF(ToDeviceColor(aColor)));
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintRoundedRectWithRadius(
|
|
DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
|
|
const LayoutDeviceRect& aClipRect, const sRGBColor& aBackgroundColor,
|
|
const sRGBColor& aBorderColor, CSSCoord aBorderWidth, CSSCoord aRadius,
|
|
DPIRatio aDpiRatio) {
|
|
const LayoutDeviceCoord borderWidth(SnapBorderWidth(aBorderWidth, aDpiRatio));
|
|
const bool needsClip = !(aRect == aClipRect);
|
|
if (needsClip) {
|
|
aDrawTarget.PushClipRect(aClipRect.ToUnknownRect());
|
|
}
|
|
|
|
LayoutDeviceRect rect(aRect);
|
|
// Deflate the rect by half the border width, so that the middle of the
|
|
// stroke fills exactly the area we want to fill and not more.
|
|
rect.Deflate(borderWidth * 0.5f);
|
|
|
|
LayoutDeviceCoord radius(aRadius * aDpiRatio - borderWidth * 0.5f);
|
|
// Fix up the radius if it's too large with the rect we're going to paint.
|
|
{
|
|
LayoutDeviceCoord min = std::min(rect.width, rect.height);
|
|
if (radius * 2.0f > min) {
|
|
radius = min * 0.5f;
|
|
}
|
|
}
|
|
|
|
Maybe<ColorPattern> backgroundPattern;
|
|
if (aBackgroundColor.a) {
|
|
backgroundPattern.emplace(ToDeviceColor(aBackgroundColor));
|
|
}
|
|
Maybe<ColorPattern> borderPattern;
|
|
if (borderWidth && aBorderColor.a) {
|
|
borderPattern.emplace(ToDeviceColor(aBorderColor));
|
|
}
|
|
|
|
if (borderPattern || backgroundPattern) {
|
|
if (radius) {
|
|
RectCornerRadii radii(radius, radius, radius, radius);
|
|
RefPtr<Path> roundedRect =
|
|
MakePathForRoundedRect(aDrawTarget, rect.ToUnknownRect(), radii);
|
|
|
|
if (backgroundPattern) {
|
|
aDrawTarget.Fill(roundedRect, *backgroundPattern);
|
|
}
|
|
if (borderPattern) {
|
|
aDrawTarget.Stroke(roundedRect, *borderPattern,
|
|
StrokeOptions(borderWidth));
|
|
}
|
|
} else {
|
|
if (backgroundPattern) {
|
|
aDrawTarget.FillRect(rect.ToUnknownRect(), *backgroundPattern);
|
|
}
|
|
if (borderPattern) {
|
|
aDrawTarget.StrokeRect(rect.ToUnknownRect(), *borderPattern,
|
|
StrokeOptions(borderWidth));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needsClip) {
|
|
aDrawTarget.PopClip();
|
|
}
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintCheckboxControl(DrawTarget& aDrawTarget,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
auto [backgroundColor, borderColor] =
|
|
ComputeCheckboxColors(aState, StyleAppearance::Checkbox, aColors);
|
|
{
|
|
const CSSCoord radius = 2.0f;
|
|
CSSCoord borderWidth = kCheckboxRadioBorderWidth;
|
|
if (backgroundColor == borderColor) {
|
|
borderWidth = 0.0f;
|
|
}
|
|
PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
|
|
borderWidth, radius, aDpiRatio);
|
|
}
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
|
|
PaintIndeterminateMark(aDrawTarget, aRect, aState, aColors);
|
|
} else if (aState.HasState(NS_EVENT_STATE_CHECKED)) {
|
|
PaintCheckMark(aDrawTarget, aRect, aState, aColors);
|
|
}
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
|
|
PaintRoundedFocusRect(aDrawTarget, aRect, aColors, aDpiRatio, 5.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
constexpr CSSCoord kCheckboxRadioContentBoxSize = 10.0f;
|
|
constexpr CSSCoord kCheckboxRadioBorderBoxSize =
|
|
kCheckboxRadioContentBoxSize + kCheckboxRadioBorderWidth * 2.0f;
|
|
|
|
// Returns the right scale for points in a aSize x aSize sized box, centered at
|
|
// 0x0 to fill aRect in the smaller dimension.
|
|
static float ScaleToFillRect(const LayoutDeviceRect& aRect, const float aSize) {
|
|
return std::min(aRect.width, aRect.height) / aSize;
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintCheckMark(DrawTarget& aDrawTarget,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors) {
|
|
// Points come from the coordinates on a 14X14 (kCheckboxRadioBorderBoxSize)
|
|
// unit box centered at 0,0
|
|
const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f,
|
|
3.5f, -0.5f, -1.5f, -3.5f};
|
|
const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f,
|
|
-4.0f, 1.0f, 1.25f, -1.0f};
|
|
const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
|
|
const float scale = ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
|
|
auto center = aRect.Center().ToUnknownPoint();
|
|
|
|
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
|
|
Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale);
|
|
builder->MoveTo(p);
|
|
for (int32_t i = 1; i < checkNumPoints; i++) {
|
|
p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale);
|
|
builder->LineTo(p);
|
|
}
|
|
RefPtr<Path> path = builder->Finish();
|
|
|
|
sRGBColor fillColor = ComputeCheckmarkColor(aState, aColors);
|
|
aDrawTarget.Fill(path, ColorPattern(ToDeviceColor(fillColor)));
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintIndeterminateMark(DrawTarget& aDrawTarget,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors) {
|
|
const CSSCoord borderWidth = 2.0f;
|
|
const float scale = ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
|
|
|
|
Rect rect = aRect.ToUnknownRect();
|
|
rect.y += (rect.height / 2) - (borderWidth * scale / 2);
|
|
rect.height = borderWidth * scale;
|
|
rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
|
|
rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
|
|
|
|
sRGBColor fillColor = ComputeCheckmarkColor(aState, aColors);
|
|
aDrawTarget.FillRect(rect, ColorPattern(ToDeviceColor(fillColor)));
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintStrokedCircle(PaintBackendData& aPaintData,
|
|
const LayoutDeviceRect& aRect,
|
|
const sRGBColor& aBackgroundColor,
|
|
const sRGBColor& aBorderColor,
|
|
const CSSCoord aBorderWidth,
|
|
DPIRatio aDpiRatio) {
|
|
auto radius = LayoutDeviceCoord(aRect.Size().width) / aDpiRatio;
|
|
PaintRoundedRectWithRadius(aPaintData, aRect, aBackgroundColor, aBorderColor,
|
|
aBorderWidth, radius, aDpiRatio);
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintCircleShadow(WebRenderBackendData& aWrData,
|
|
const LayoutDeviceRect& aBoxRect,
|
|
const LayoutDeviceRect& aClipRect,
|
|
float aShadowAlpha,
|
|
const CSSPoint& aShadowOffset,
|
|
CSSCoord aShadowBlurStdDev,
|
|
DPIRatio aDpiRatio) {
|
|
const bool kBackfaceIsVisible = true;
|
|
const LayoutDeviceCoord stdDev = aShadowBlurStdDev * aDpiRatio;
|
|
const LayoutDevicePoint shadowOffset = aShadowOffset * aDpiRatio;
|
|
const IntSize inflation =
|
|
gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
|
|
LayoutDeviceRect shadowRect = aBoxRect;
|
|
shadowRect.MoveBy(shadowOffset);
|
|
shadowRect.Inflate(inflation.width, inflation.height);
|
|
const auto boxRect = wr::ToLayoutRect(aBoxRect);
|
|
aWrData.mBuilder.PushBoxShadow(
|
|
wr::ToLayoutRect(shadowRect), wr::ToLayoutRect(aClipRect),
|
|
kBackfaceIsVisible, boxRect,
|
|
wr::ToLayoutVector2D(aShadowOffset * aDpiRatio),
|
|
wr::ToColorF(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)), stdDev,
|
|
/* aSpread = */ 0.0f,
|
|
wr::ToBorderRadius(gfx::RectCornerRadii(aBoxRect.Size().width)),
|
|
wr::BoxShadowClipMode::Outset);
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintCircleShadow(DrawTarget& aDrawTarget,
|
|
const LayoutDeviceRect& aBoxRect,
|
|
const LayoutDeviceRect& aClipRect,
|
|
float aShadowAlpha,
|
|
const CSSPoint& aShadowOffset,
|
|
CSSCoord aShadowBlurStdDev,
|
|
DPIRatio aDpiRatio) {
|
|
Float stdDev = aShadowBlurStdDev * aDpiRatio;
|
|
Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint();
|
|
|
|
RefPtr<FilterNode> blurFilter =
|
|
aDrawTarget.CreateFilter(FilterType::GAUSSIAN_BLUR);
|
|
if (!blurFilter) {
|
|
return;
|
|
}
|
|
|
|
blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
|
|
|
|
IntSize inflation =
|
|
gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
|
|
Rect inflatedRect = aBoxRect.ToUnknownRect();
|
|
inflatedRect.Inflate(inflation.width, inflation.height);
|
|
Rect sourceRectInFilterSpace =
|
|
inflatedRect - aBoxRect.TopLeft().ToUnknownPoint();
|
|
Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset;
|
|
|
|
IntSize dtSize = RoundedToInt(aBoxRect.Size().ToUnknownSize());
|
|
RefPtr<DrawTarget> ellipseDT = aDrawTarget.CreateSimilarDrawTargetForFilter(
|
|
dtSize, SurfaceFormat::A8, blurFilter, blurFilter,
|
|
sourceRectInFilterSpace, destinationPointOfSourceRect);
|
|
if (!ellipseDT) {
|
|
return;
|
|
}
|
|
|
|
AutoClipRect clipRect(aDrawTarget, aClipRect);
|
|
|
|
RefPtr<Path> ellipse = MakePathForEllipse(
|
|
*ellipseDT, (aBoxRect - aBoxRect.TopLeft()).Center().ToUnknownPoint(),
|
|
aBoxRect.Size().ToUnknownSize());
|
|
ellipseDT->Fill(ellipse,
|
|
ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)));
|
|
RefPtr<SourceSurface> ellipseSurface = ellipseDT->Snapshot();
|
|
|
|
blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface);
|
|
aDrawTarget.DrawFilter(blurFilter, sourceRectInFilterSpace,
|
|
destinationPointOfSourceRect);
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintRadioControl(PaintBackendData& aPaintData,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
auto [backgroundColor, borderColor] =
|
|
ComputeCheckboxColors(aState, StyleAppearance::Radio, aColors);
|
|
{
|
|
CSSCoord borderWidth = kCheckboxRadioBorderWidth;
|
|
if (backgroundColor == borderColor) {
|
|
borderWidth = 0.0f;
|
|
}
|
|
PaintStrokedCircle(aPaintData, aRect, backgroundColor, borderColor,
|
|
borderWidth, aDpiRatio);
|
|
}
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_CHECKED)) {
|
|
LayoutDeviceRect rect(aRect);
|
|
rect.Deflate(SnapBorderWidth(kCheckboxRadioBorderWidth, aDpiRatio));
|
|
|
|
auto checkColor = ComputeCheckmarkColor(aState, aColors);
|
|
PaintStrokedCircle(aPaintData, rect, backgroundColor, checkColor,
|
|
kCheckboxRadioBorderWidth, aDpiRatio);
|
|
}
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
|
|
PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, 5.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintTextField(PaintBackendData& aPaintData,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
auto [backgroundColor, borderColor] =
|
|
ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::Yes);
|
|
|
|
const CSSCoord radius = 2.0f;
|
|
|
|
PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor, borderColor,
|
|
kTextFieldBorderWidth, radius, aDpiRatio);
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
|
|
PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
|
|
radius + kTextFieldBorderWidth,
|
|
-kTextFieldBorderWidth);
|
|
}
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintListbox(PaintBackendData& aPaintData,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
const CSSCoord radius = 2.0f;
|
|
auto [backgroundColor, borderColor] =
|
|
ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::Yes);
|
|
|
|
PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor, borderColor,
|
|
kMenulistBorderWidth, radius, aDpiRatio);
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
|
|
PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
|
|
radius + kMenulistBorderWidth, -kMenulistBorderWidth);
|
|
}
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintMenulist(PaintBackendData& aDrawTarget,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
const CSSCoord radius = 4.0f;
|
|
auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
|
|
|
|
PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
|
|
kMenulistBorderWidth, radius, aDpiRatio);
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
|
|
PaintRoundedFocusRect(aDrawTarget, aRect, aColors, aDpiRatio,
|
|
radius + kMenulistBorderWidth, -kMenulistBorderWidth);
|
|
}
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintArrow(DrawTarget& aDrawTarget,
|
|
const LayoutDeviceRect& aRect,
|
|
const float aArrowPolygonX[],
|
|
const float aArrowPolygonY[],
|
|
const float aArrowPolygonSize,
|
|
const int32_t aArrowNumPoints,
|
|
const sRGBColor aFillColor) {
|
|
const float scale = ScaleToFillRect(aRect, aArrowPolygonSize);
|
|
|
|
auto center = aRect.Center().ToUnknownPoint();
|
|
|
|
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
|
|
Point p =
|
|
center + Point(aArrowPolygonX[0] * scale, aArrowPolygonY[0] * scale);
|
|
builder->MoveTo(p);
|
|
for (int32_t i = 1; i < aArrowNumPoints; i++) {
|
|
p = center + Point(aArrowPolygonX[i] * scale, aArrowPolygonY[i] * scale);
|
|
builder->LineTo(p);
|
|
}
|
|
RefPtr<Path> path = builder->Finish();
|
|
|
|
aDrawTarget.Fill(path, ColorPattern(ToDeviceColor(aFillColor)));
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintMenulistArrowButton(nsIFrame* aFrame,
|
|
DrawTarget& aDrawTarget,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState) {
|
|
const float kPolygonX[] = {-4.0f, -0.5f, 0.5f, 4.0f, 4.0f,
|
|
3.0f, 0.0f, 0.0f, -3.0f, -4.0f};
|
|
const float kPolygonY[] = {-1, 3.0f, 3.0f, -1.0f, -2.0f,
|
|
-2.0f, 1.5f, 1.5f, -2.0f, -2.0f};
|
|
|
|
const float kPolygonSize = kMinimumDropdownArrowButtonWidth;
|
|
|
|
const auto arrowColor = sRGBColor::FromABGR(
|
|
nsLayoutUtils::GetColor(aFrame, &nsStyleText::mWebkitTextFillColor));
|
|
PaintArrow(aDrawTarget, aRect, kPolygonX, kPolygonY, kPolygonSize,
|
|
ArrayLength(kPolygonX), arrowColor);
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintSpinnerButton(
|
|
nsIFrame* aFrame, DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
|
|
const EventStates& aState, StyleAppearance aAppearance,
|
|
const Colors& aColors, DPIRatio aDpiRatio) {
|
|
auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
|
|
|
|
aDrawTarget.FillRect(aRect.ToUnknownRect(),
|
|
ColorPattern(ToDeviceColor(backgroundColor)));
|
|
|
|
const float kPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
|
|
2.5f, 0.0f, 0.0f, -2.5f, -3.5f};
|
|
float polygonY[] = {-1.5f, 1.5f, 1.5f, -1.5f, -2.5f,
|
|
-2.5f, 0.0f, 0.0f, -2.5f, -2.5f};
|
|
|
|
const float kPolygonSize = kMinimumSpinnerButtonHeight;
|
|
if (aAppearance == StyleAppearance::SpinnerUpbutton) {
|
|
for (auto& coord : polygonY) {
|
|
coord = -coord;
|
|
}
|
|
}
|
|
|
|
PaintArrow(aDrawTarget, aRect, kPolygonX, polygonY, kPolygonSize,
|
|
ArrayLength(kPolygonX), borderColor);
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintRange(nsIFrame* aFrame,
|
|
PaintBackendData& aPaintData,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors, DPIRatio aDpiRatio,
|
|
bool aHorizontal) {
|
|
nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
|
|
if (!rangeFrame) {
|
|
return;
|
|
}
|
|
|
|
double progress = rangeFrame->GetValueAsFractionOfRange();
|
|
auto rect = aRect;
|
|
LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
|
|
kMinimumRangeThumbSize * aDpiRatio);
|
|
LayoutDeviceRect progressClipRect(aRect);
|
|
LayoutDeviceRect trackClipRect(aRect);
|
|
const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
|
|
if (aHorizontal) {
|
|
rect.height = verticalSize;
|
|
rect.y = aRect.y + (aRect.height - rect.height) / 2;
|
|
thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
|
|
|
|
if (IsFrameRTL(aFrame)) {
|
|
thumbRect.x =
|
|
aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
|
|
float midPoint = thumbRect.Center().X();
|
|
trackClipRect.SetBoxX(aRect.X(), midPoint);
|
|
progressClipRect.SetBoxX(midPoint, aRect.XMost());
|
|
} else {
|
|
thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
|
|
float midPoint = thumbRect.Center().X();
|
|
progressClipRect.SetBoxX(aRect.X(), midPoint);
|
|
trackClipRect.SetBoxX(midPoint, aRect.XMost());
|
|
}
|
|
} else {
|
|
rect.width = verticalSize;
|
|
rect.x = aRect.x + (aRect.width - rect.width) / 2;
|
|
thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
|
|
|
|
thumbRect.y =
|
|
aRect.y + (aRect.height - thumbRect.height) * (1.0 - progress);
|
|
float midPoint = thumbRect.Center().Y();
|
|
trackClipRect.SetBoxY(aRect.Y(), midPoint);
|
|
progressClipRect.SetBoxY(midPoint, aRect.YMost());
|
|
}
|
|
|
|
const CSSCoord borderWidth = 1.0f;
|
|
const CSSCoord radius = 3.0f;
|
|
|
|
auto [progressColor, progressBorderColor] =
|
|
ComputeRangeProgressColors(aState, aColors);
|
|
auto [trackColor, trackBorderColor] =
|
|
ComputeRangeTrackColors(aState, aColors);
|
|
|
|
PaintRoundedRectWithRadius(aPaintData, rect, progressClipRect, progressColor,
|
|
progressBorderColor, borderWidth, radius,
|
|
aDpiRatio);
|
|
|
|
PaintRoundedRectWithRadius(aPaintData, rect, trackClipRect, trackColor,
|
|
trackBorderColor, borderWidth, radius, aDpiRatio);
|
|
|
|
if (!aState.HasState(NS_EVENT_STATE_DISABLED)) {
|
|
// Ensure the shadow doesn't expand outside of our overflow rect declared in
|
|
// GetWidgetOverflow().
|
|
auto overflowRect = aRect;
|
|
overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio);
|
|
// Thumb shadow
|
|
PaintCircleShadow(aPaintData, thumbRect, overflowRect, 0.3f,
|
|
CSSPoint(0.0f, 2.0f), 2.0f, aDpiRatio);
|
|
}
|
|
|
|
// Draw the thumb on top.
|
|
const CSSCoord thumbBorderWidth = 2.0f;
|
|
auto [thumbColor, thumbBorderColor] =
|
|
ComputeRangeThumbColors(aState, aColors);
|
|
|
|
PaintStrokedCircle(aPaintData, thumbRect, thumbColor, thumbBorderColor,
|
|
thumbBorderWidth, aDpiRatio);
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
|
|
PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, radius, 1.0f);
|
|
}
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintProgress(nsIFrame* aFrame,
|
|
PaintBackendData& aPaintData,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio, bool aIsMeter) {
|
|
const CSSCoord borderWidth = 1.0f;
|
|
const CSSCoord radius = aIsMeter ? 6.0f : 3.0f;
|
|
|
|
LayoutDeviceRect rect(aRect);
|
|
const LayoutDeviceCoord thickness =
|
|
(aIsMeter ? kMeterHeight : kProgressbarHeight) * aDpiRatio;
|
|
|
|
const bool isHorizontal = !nsNativeTheme::IsVerticalProgress(aFrame);
|
|
if (isHorizontal) {
|
|
// Center it vertically.
|
|
rect.y += (rect.height - thickness) / 2;
|
|
rect.height = thickness;
|
|
} else {
|
|
// Center it horizontally.
|
|
rect.x += (rect.width - thickness) / 2;
|
|
rect.width = thickness;
|
|
}
|
|
|
|
{
|
|
// Paint the track, unclipped.
|
|
auto [backgroundColor, borderColor] = ComputeProgressTrackColors(aColors);
|
|
PaintRoundedRectWithRadius(aPaintData, rect, rect, backgroundColor,
|
|
borderColor, borderWidth, radius, aDpiRatio);
|
|
}
|
|
|
|
// Now paint the chunk, clipped as needed.
|
|
LayoutDeviceRect clipRect = rect;
|
|
if (aState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
|
|
// For indeterminate progress, we paint an animated chunk of 1/3 of the
|
|
// progress size.
|
|
//
|
|
// Animation speed and math borrowed from GTK.
|
|
const LayoutDeviceCoord size = isHorizontal ? rect.width : rect.height;
|
|
const LayoutDeviceCoord barSize = size * 0.3333f;
|
|
const LayoutDeviceCoord travel = 2.0f * (size - barSize);
|
|
|
|
// Period equals to travel / pixelsPerMillisecond where pixelsPerMillisecond
|
|
// equals progressSize / 1000.0. This is equivalent to 1600.
|
|
const unsigned kPeriod = 1600;
|
|
|
|
const int t = PR_IntervalToMilliseconds(PR_IntervalNow()) % kPeriod;
|
|
const LayoutDeviceCoord dx = travel * float(t) / float(kPeriod);
|
|
if (isHorizontal) {
|
|
rect.width = barSize;
|
|
rect.x += (dx < travel * .5f) ? dx : travel - dx;
|
|
} else {
|
|
rect.height = barSize;
|
|
rect.y += (dx < travel * .5f) ? dx : travel - dx;
|
|
}
|
|
clipRect = rect;
|
|
// Queue the next frame if needed.
|
|
if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
|
|
NS_WARNING("Couldn't refresh indeterminate <progress>");
|
|
}
|
|
} else {
|
|
// This is the progress chunk, clip it to the right amount.
|
|
double position = [&] {
|
|
if (aIsMeter) {
|
|
auto* meter = dom::HTMLMeterElement::FromNode(aFrame->GetContent());
|
|
if (!meter) {
|
|
return 0.0;
|
|
}
|
|
return meter->Value() / meter->Max();
|
|
}
|
|
auto* progress = dom::HTMLProgressElement::FromNode(aFrame->GetContent());
|
|
if (!progress) {
|
|
return 0.0;
|
|
}
|
|
return progress->Value() / progress->Max();
|
|
}();
|
|
if (isHorizontal) {
|
|
double clipWidth = rect.width * position;
|
|
clipRect.width = clipWidth;
|
|
if (IsFrameRTL(aFrame)) {
|
|
clipRect.x += rect.width - clipWidth;
|
|
}
|
|
} else {
|
|
double clipHeight = rect.height * position;
|
|
clipRect.height = clipHeight;
|
|
clipRect.y += rect.height - clipHeight;
|
|
}
|
|
}
|
|
|
|
auto [backgroundColor, borderColor] =
|
|
aIsMeter ? ComputeMeterchunkColors(aState, aColors)
|
|
: ComputeProgressColors(aColors);
|
|
PaintRoundedRectWithRadius(aPaintData, rect, clipRect, backgroundColor,
|
|
borderColor, borderWidth, radius, aDpiRatio);
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintButton(nsIFrame* aFrame,
|
|
PaintBackendData& aPaintData,
|
|
const LayoutDeviceRect& aRect,
|
|
const EventStates& aState,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
const CSSCoord radius = 4.0f;
|
|
auto [backgroundColor, borderColor] =
|
|
ComputeButtonColors(aState, aColors, aFrame);
|
|
|
|
PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor, borderColor,
|
|
kButtonBorderWidth, radius, aDpiRatio);
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
|
|
PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
|
|
radius + kButtonBorderWidth, -kButtonBorderWidth);
|
|
}
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
bool nsNativeBasicTheme::DoPaintDefaultScrollbarThumb(
|
|
PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
|
|
bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aElementState, const EventStates& aDocumentState,
|
|
const Colors& aColors, DPIRatio aDpiRatio) {
|
|
sRGBColor thumbColor = ComputeScrollbarThumbColor(
|
|
aFrame, aStyle, aElementState, aDocumentState, aColors);
|
|
FillRect(aPaintData, aRect, thumbColor);
|
|
return true;
|
|
}
|
|
|
|
bool nsNativeBasicTheme::PaintScrollbarThumb(
|
|
DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
|
|
nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aElementState, const EventStates& aDocumentState,
|
|
const Colors& aColors, DPIRatio aDpiRatio) {
|
|
return DoPaintDefaultScrollbarThumb(aDrawTarget, aRect, aHorizontal, aFrame,
|
|
aStyle, aElementState, aDocumentState,
|
|
aColors, aDpiRatio);
|
|
}
|
|
|
|
bool nsNativeBasicTheme::PaintScrollbarThumb(
|
|
WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
|
|
bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aElementState, const EventStates& aDocumentState,
|
|
const Colors& aColors, DPIRatio aDpiRatio) {
|
|
return DoPaintDefaultScrollbarThumb(aWrData, aRect, aHorizontal, aFrame,
|
|
aStyle, aElementState, aDocumentState,
|
|
aColors, aDpiRatio);
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
bool nsNativeBasicTheme::DoPaintDefaultScrollbar(
|
|
PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
|
|
bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aElementState, const EventStates& aDocumentState,
|
|
const Colors& aColors, DPIRatio aDpiRatio) {
|
|
if (aFrame->PresContext()->UseOverlayScrollbars() &&
|
|
!aElementState.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER |
|
|
NS_EVENT_STATE_ACTIVE)) {
|
|
return true;
|
|
}
|
|
auto scrollbarColor =
|
|
ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
|
|
FillRect(aPaintData, aRect, scrollbarColor);
|
|
return true;
|
|
}
|
|
|
|
bool nsNativeBasicTheme::PaintScrollbar(
|
|
DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
|
|
nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aElementState, const EventStates& aDocumentState,
|
|
const Colors& aColors, DPIRatio aDpiRatio) {
|
|
return DoPaintDefaultScrollbar(aDrawTarget, aRect, aHorizontal, aFrame,
|
|
aStyle, aElementState, aDocumentState, aColors,
|
|
aDpiRatio);
|
|
}
|
|
|
|
bool nsNativeBasicTheme::PaintScrollbar(
|
|
WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
|
|
bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aElementState, const EventStates& aDocumentState,
|
|
const Colors& aColors, DPIRatio aDpiRatio) {
|
|
return DoPaintDefaultScrollbar(aWrData, aRect, aHorizontal, aFrame, aStyle,
|
|
aElementState, aDocumentState, aColors,
|
|
aDpiRatio);
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
bool nsNativeBasicTheme::DoPaintDefaultScrollCorner(
|
|
PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
|
|
nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const EventStates& aDocumentState, const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
auto scrollbarColor =
|
|
ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
|
|
FillRect(aPaintData, aRect, scrollbarColor);
|
|
return true;
|
|
}
|
|
|
|
bool nsNativeBasicTheme::PaintScrollCorner(
|
|
DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect, nsIFrame* aFrame,
|
|
const ComputedStyle& aStyle, const EventStates& aDocumentState,
|
|
const Colors& aColors, DPIRatio aDpiRatio) {
|
|
return DoPaintDefaultScrollCorner(aDrawTarget, aRect, aFrame, aStyle,
|
|
aDocumentState, aColors, aDpiRatio);
|
|
}
|
|
|
|
bool nsNativeBasicTheme::PaintScrollCorner(WebRenderBackendData& aWrData,
|
|
const LayoutDeviceRect& aRect,
|
|
nsIFrame* aFrame,
|
|
const ComputedStyle& aStyle,
|
|
const EventStates& aDocumentState,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
return DoPaintDefaultScrollCorner(aWrData, aRect, aFrame, aStyle,
|
|
aDocumentState, aColors, aDpiRatio);
|
|
}
|
|
|
|
void nsNativeBasicTheme::PaintScrollbarButton(
|
|
DrawTarget& aDrawTarget, StyleAppearance aAppearance,
|
|
const LayoutDeviceRect& aRect, nsIFrame* aFrame,
|
|
const ComputedStyle& aStyle, const EventStates& aElementState,
|
|
const EventStates& aDocumentState, const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
auto [buttonColor, arrowColor] = ComputeScrollbarButtonColors(
|
|
aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors);
|
|
aDrawTarget.FillRect(aRect.ToUnknownRect(),
|
|
ColorPattern(ToDeviceColor(buttonColor)));
|
|
|
|
// Start with Up arrow.
|
|
float arrowPolygonX[] = {-4.0f, 0.0f, 4.0f, 4.0f, 0.0f, -4.0f};
|
|
float arrowPolygonY[] = {0.0f, -4.0f, 0.0f, 3.0f, -1.0f, 3.0f};
|
|
|
|
const float kPolygonSize = 17;
|
|
|
|
const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
|
|
switch (aAppearance) {
|
|
case StyleAppearance::ScrollbarbuttonUp:
|
|
break;
|
|
case StyleAppearance::ScrollbarbuttonDown:
|
|
for (int32_t i = 0; i < arrowNumPoints; i++) {
|
|
arrowPolygonY[i] *= -1;
|
|
}
|
|
break;
|
|
case StyleAppearance::ScrollbarbuttonLeft:
|
|
for (int32_t i = 0; i < arrowNumPoints; i++) {
|
|
int32_t temp = arrowPolygonX[i];
|
|
arrowPolygonX[i] = arrowPolygonY[i];
|
|
arrowPolygonY[i] = temp;
|
|
}
|
|
break;
|
|
case StyleAppearance::ScrollbarbuttonRight:
|
|
for (int32_t i = 0; i < arrowNumPoints; i++) {
|
|
int32_t temp = arrowPolygonX[i];
|
|
arrowPolygonX[i] = arrowPolygonY[i] * -1;
|
|
arrowPolygonY[i] = temp;
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, kPolygonSize,
|
|
arrowNumPoints, arrowColor);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeBasicTheme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
const nsRect& aRect,
|
|
const nsRect& /* aDirtyRect */,
|
|
DrawOverflow aDrawOverflow) {
|
|
if (!DoDrawWidgetBackground(*aContext->GetDrawTarget(), aFrame, aAppearance,
|
|
aRect, aDrawOverflow)) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsNativeBasicTheme::CreateWebRenderCommandsForWidget(
|
|
mozilla::wr::DisplayListBuilder& aBuilder,
|
|
mozilla::wr::IpcResourceUpdateQueue& aResources,
|
|
const mozilla::layers::StackingContextHelper& aSc,
|
|
mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
|
|
StyleAppearance aAppearance, const nsRect& aRect) {
|
|
if (!StaticPrefs::widget_non_native_theme_webrender()) {
|
|
return false;
|
|
}
|
|
WebRenderBackendData data{aBuilder, aResources, aSc, aManager};
|
|
return DoDrawWidgetBackground(data, aFrame, aAppearance, aRect,
|
|
DrawOverflow::Yes);
|
|
}
|
|
|
|
static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
|
|
nscoord aTwipsPerPixel, DrawTarget& aDt) {
|
|
return LayoutDeviceRect::FromUnknownRect(
|
|
NSRectToSnappedRect(aRect, aTwipsPerPixel, aDt));
|
|
}
|
|
|
|
static LayoutDeviceRect ToSnappedRect(
|
|
const nsRect& aRect, nscoord aTwipsPerPixel,
|
|
nsNativeBasicTheme::WebRenderBackendData& aDt) {
|
|
// TODO: Do we need to do any more snapping here?
|
|
return LayoutDeviceRect::FromAppUnits(aRect, aTwipsPerPixel);
|
|
}
|
|
|
|
bool nsNativeBasicTheme::ShouldBeHighContrast(const nsPresContext& aPc) {
|
|
// 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.
|
|
return aPc.GetBackgroundColorDraw() &&
|
|
PreferenceSheet::PrefsFor(*aPc.Document())
|
|
.NonNativeThemeShouldBeHighContrast();
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
bool nsNativeBasicTheme::DoDrawWidgetBackground(PaintBackendData& aPaintData,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
const nsRect& aRect,
|
|
DrawOverflow aDrawOverflow) {
|
|
static_assert(std::is_same_v<PaintBackendData, DrawTarget> ||
|
|
std::is_same_v<PaintBackendData, WebRenderBackendData>);
|
|
|
|
const nsPresContext* pc = aFrame->PresContext();
|
|
const nscoord twipsPerPixel = pc->AppUnitsPerDevPixel();
|
|
const auto devPxRect = ToSnappedRect(aRect, twipsPerPixel, aPaintData);
|
|
|
|
const EventStates docState = pc->Document()->GetDocumentState();
|
|
EventStates eventState = GetContentState(aFrame, aAppearance);
|
|
if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
|
|
bool isHTML = IsHTMLContent(aFrame);
|
|
nsIFrame* parentFrame = aFrame->GetParent();
|
|
bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
|
|
// HTML select and XUL menulist dropdown buttons get state from the
|
|
// parent.
|
|
if (isHTML || isMenulist) {
|
|
aFrame = parentFrame;
|
|
eventState = GetContentState(parentFrame, aAppearance);
|
|
}
|
|
}
|
|
|
|
// Don't paint the outline if we're asked not to draw overflow, or if the
|
|
// author has specified another kind of outline on focus.
|
|
if (aDrawOverflow == DrawOverflow::No ||
|
|
!aFrame->StyleOutline()->mOutlineStyle.IsAuto()) {
|
|
eventState &= ~NS_EVENT_STATE_FOCUSRING;
|
|
}
|
|
|
|
// Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
|
|
// overflow devPxRect.
|
|
Maybe<AutoClipRect> maybeClipRect;
|
|
if constexpr (std::is_same_v<PaintBackendData, DrawTarget>) {
|
|
if (aAppearance != StyleAppearance::FocusOutline &&
|
|
aAppearance != StyleAppearance::Range &&
|
|
!eventState.HasState(NS_EVENT_STATE_FOCUSRING)) {
|
|
maybeClipRect.emplace(aPaintData, devPxRect);
|
|
}
|
|
}
|
|
|
|
const Colors colors(aFrame);
|
|
DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
|
|
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Radio: {
|
|
auto rect = CheckBoxRadioRect(devPxRect);
|
|
PaintRadioControl(aPaintData, rect, eventState, colors, dpiRatio);
|
|
break;
|
|
}
|
|
case StyleAppearance::Checkbox: {
|
|
if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
|
|
// TODO: Need to figure out how to best draw this using WR.
|
|
return false;
|
|
} else {
|
|
auto rect = CheckBoxRadioRect(devPxRect);
|
|
PaintCheckboxControl(aPaintData, rect, eventState, colors, dpiRatio);
|
|
}
|
|
break;
|
|
}
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::NumberInput:
|
|
PaintTextField(aPaintData, devPxRect, eventState, colors, dpiRatio);
|
|
break;
|
|
case StyleAppearance::Listbox:
|
|
PaintListbox(aPaintData, devPxRect, eventState, colors, dpiRatio);
|
|
break;
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::Menulist:
|
|
PaintMenulist(aPaintData, devPxRect, eventState, colors, dpiRatio);
|
|
break;
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
|
|
// TODO: Need to figure out how to best draw this using WR.
|
|
return false;
|
|
} else {
|
|
PaintMenulistArrowButton(aFrame, aPaintData, devPxRect, eventState);
|
|
}
|
|
break;
|
|
case StyleAppearance::SpinnerUpbutton:
|
|
case StyleAppearance::SpinnerDownbutton:
|
|
if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
|
|
// TODO: Need to figure out how to best draw this using WR.
|
|
return false;
|
|
} else {
|
|
PaintSpinnerButton(aFrame, aPaintData, devPxRect, eventState,
|
|
aAppearance, colors, dpiRatio);
|
|
}
|
|
break;
|
|
case StyleAppearance::Range:
|
|
PaintRange(aFrame, aPaintData, devPxRect, eventState, colors, dpiRatio,
|
|
IsRangeHorizontal(aFrame));
|
|
break;
|
|
case StyleAppearance::RangeThumb:
|
|
// Painted as part of StyleAppearance::Range.
|
|
break;
|
|
case StyleAppearance::ProgressBar:
|
|
PaintProgress(aFrame, aPaintData, devPxRect, eventState, colors, dpiRatio,
|
|
/* aIsMeter = */ false);
|
|
break;
|
|
case StyleAppearance::Progresschunk:
|
|
/* Painted as part of the progress bar */
|
|
break;
|
|
case StyleAppearance::Meter:
|
|
PaintProgress(aFrame, aPaintData, devPxRect, eventState, colors, dpiRatio,
|
|
/* aIsMeter = */ true);
|
|
break;
|
|
case StyleAppearance::Meterchunk:
|
|
/* Painted as part of the meter bar */
|
|
break;
|
|
case StyleAppearance::ScrollbarthumbHorizontal:
|
|
case StyleAppearance::ScrollbarthumbVertical: {
|
|
bool isHorizontal =
|
|
aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
|
|
return PaintScrollbarThumb(aPaintData, devPxRect, isHorizontal, aFrame,
|
|
*nsLayoutUtils::StyleForScrollbar(aFrame),
|
|
eventState, docState, colors, dpiRatio);
|
|
}
|
|
case StyleAppearance::ScrollbartrackHorizontal:
|
|
case StyleAppearance::ScrollbartrackVertical: {
|
|
bool isHorizontal =
|
|
aAppearance == StyleAppearance::ScrollbartrackHorizontal;
|
|
return PaintScrollbarTrack(aPaintData, devPxRect, isHorizontal, aFrame,
|
|
*nsLayoutUtils::StyleForScrollbar(aFrame),
|
|
docState, colors, dpiRatio);
|
|
}
|
|
case StyleAppearance::ScrollbarHorizontal:
|
|
case StyleAppearance::ScrollbarVertical: {
|
|
bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
|
|
return PaintScrollbar(aPaintData, devPxRect, isHorizontal, aFrame,
|
|
*nsLayoutUtils::StyleForScrollbar(aFrame),
|
|
eventState, docState, colors, dpiRatio);
|
|
}
|
|
case StyleAppearance::Scrollcorner:
|
|
return PaintScrollCorner(aPaintData, devPxRect, aFrame,
|
|
*nsLayoutUtils::StyleForScrollbar(aFrame),
|
|
docState, colors, dpiRatio);
|
|
case StyleAppearance::ScrollbarbuttonUp:
|
|
case StyleAppearance::ScrollbarbuttonDown:
|
|
case StyleAppearance::ScrollbarbuttonLeft:
|
|
case StyleAppearance::ScrollbarbuttonRight:
|
|
// For scrollbar-width:thin, we don't display the buttons.
|
|
if (!IsScrollbarWidthThin(aFrame)) {
|
|
if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
|
|
// TODO: Need to figure out how to best draw this using WR.
|
|
return false;
|
|
} else {
|
|
PaintScrollbarButton(aPaintData, aAppearance, devPxRect, aFrame,
|
|
*nsLayoutUtils::StyleForScrollbar(aFrame),
|
|
eventState, docState, colors, dpiRatio);
|
|
}
|
|
}
|
|
break;
|
|
case StyleAppearance::Button:
|
|
PaintButton(aFrame, aPaintData, devPxRect, eventState, colors, dpiRatio);
|
|
break;
|
|
case StyleAppearance::FocusOutline:
|
|
PaintAutoStyleOutline(aFrame, aPaintData, devPxRect, colors, dpiRatio);
|
|
break;
|
|
default:
|
|
// Various appearance values are used for XUL elements. Normally these
|
|
// will not be available in content documents (and thus in the content
|
|
// processes where the native basic theme can be used), but tests are
|
|
// run with the remote XUL pref enabled and so we can get in here. So
|
|
// we just return an error rather than assert.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
void nsNativeBasicTheme::PaintAutoStyleOutline(nsIFrame* aFrame,
|
|
PaintBackendData& aPaintData,
|
|
const LayoutDeviceRect& aRect,
|
|
const Colors& aColors,
|
|
DPIRatio aDpiRatio) {
|
|
auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors(aColors);
|
|
Unused << middleColor;
|
|
Unused << outerColor;
|
|
|
|
LayoutDeviceRect rect(aRect);
|
|
auto width =
|
|
LayoutDeviceCoord(SnapBorderWidth(kInnerFocusOutlineWidth, aDpiRatio));
|
|
rect.Inflate(width);
|
|
|
|
const nscoord offset = aFrame->StyleOutline()->mOutlineOffset.ToAppUnits();
|
|
nscoord cssRadii[8];
|
|
if (!aFrame->GetBorderRadii(cssRadii)) {
|
|
const CSSCoord cssOffset = CSSCoord::FromAppUnits(offset);
|
|
const CSSCoord radius =
|
|
cssOffset >= 0.0f
|
|
? kInnerFocusOutlineWidth
|
|
: std::max(kInnerFocusOutlineWidth + cssOffset, CSSCoord(0.0f));
|
|
return PaintRoundedRectWithRadius(aPaintData, rect, sRGBColor::White(0.0f),
|
|
innerColor, kInnerFocusOutlineWidth,
|
|
radius, aDpiRatio);
|
|
}
|
|
|
|
nsPresContext* pc = aFrame->PresContext();
|
|
const Float devPixelOffset = pc->AppUnitsToFloatDevPixels(offset);
|
|
|
|
RectCornerRadii innerRadii;
|
|
nsCSSRendering::ComputePixelRadii(cssRadii, pc->AppUnitsPerDevPixel(),
|
|
&innerRadii);
|
|
|
|
const auto borderColor = ToDeviceColor(innerColor);
|
|
// NOTE(emilio): This doesn't use PaintRoundedRectWithRadius because we need
|
|
// to support arbitrary radii.
|
|
RectCornerRadii outerRadii;
|
|
if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
|
|
const Float widths[4] = {width + devPixelOffset, width + devPixelOffset,
|
|
width + devPixelOffset, width + devPixelOffset};
|
|
nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
|
|
|
|
const auto dest = wr::ToLayoutRect(rect);
|
|
const auto side = wr::ToBorderSide(borderColor, StyleBorderStyle::Solid);
|
|
const wr::BorderSide sides[4] = {side, side, side, side};
|
|
const bool kBackfaceIsVisible = true;
|
|
const auto wrWidths = wr::ToBorderWidths(width, width, width, width);
|
|
const auto wrRadius = wr::ToBorderRadius(outerRadii);
|
|
aPaintData.mBuilder.PushBorder(dest, dest, kBackfaceIsVisible, wrWidths,
|
|
{sides, 4}, wrRadius);
|
|
} else {
|
|
const LayoutDeviceCoord halfWidth = width * 0.5f;
|
|
rect.Deflate(halfWidth);
|
|
const Float widths[4] = {
|
|
halfWidth + devPixelOffset, halfWidth + devPixelOffset,
|
|
halfWidth + devPixelOffset, halfWidth + devPixelOffset};
|
|
nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
|
|
RefPtr<Path> path =
|
|
MakePathForRoundedRect(aPaintData, rect.ToUnknownRect(), outerRadii);
|
|
aPaintData.Stroke(path, ColorPattern(borderColor), StrokeOptions(width));
|
|
}
|
|
}
|
|
|
|
LayoutDeviceIntMargin nsNativeBasicTheme::GetWidgetBorder(
|
|
nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Listbox:
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::Button:
|
|
// Return the border size from the UA sheet, even though what we paint
|
|
// doesn't actually match that. We know this is the UA sheet border
|
|
// because we disable native theming when different border widths are
|
|
// specified by authors, see nsNativeBasicTheme::IsWidgetStyled.
|
|
//
|
|
// The Rounded() bit is technically redundant, but needed to appease the
|
|
// type system, we should always end up with full device pixels due to
|
|
// round_border_to_device_pixels at style time.
|
|
return LayoutDeviceIntMargin::FromAppUnits(
|
|
aFrame->StyleBorder()->GetComputedBorder(),
|
|
aFrame->PresContext()->AppUnitsPerDevPixel())
|
|
.Rounded();
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::Radio: {
|
|
DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
|
|
LayoutDeviceIntCoord w =
|
|
SnapBorderWidth(kCheckboxRadioBorderWidth, dpiRatio);
|
|
return LayoutDeviceIntMargin(w, w, w, w);
|
|
}
|
|
default:
|
|
return LayoutDeviceIntMargin();
|
|
}
|
|
}
|
|
|
|
bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext* aContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
LayoutDeviceIntMargin* aResult) {
|
|
switch (aAppearance) {
|
|
// Radios and checkboxes return a fixed size in GetMinimumWidgetSize
|
|
// and have a meaningful baseline, so they can't have
|
|
// author-specified padding.
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Checkbox:
|
|
aResult->SizeTo(0, 0, 0, 0);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext* aContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
nsRect* aOverflowRect) {
|
|
nsIntMargin overflow;
|
|
switch (aAppearance) {
|
|
case StyleAppearance::FocusOutline:
|
|
// 2px * one segment
|
|
overflow.SizeTo(2, 2, 2, 2);
|
|
break;
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::Range:
|
|
// 2px for each outline segment, plus 1px separation, plus we paint with a
|
|
// 1px extra offset, so 6px.
|
|
overflow.SizeTo(6, 6, 6, 6);
|
|
break;
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::Listbox:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::Button:
|
|
// 2px for each segment, plus 1px separation, but we paint 1px inside
|
|
// the border area so 4px overflow.
|
|
overflow.SizeTo(4, 4, 4, 4);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// TODO: This should convert from device pixels to app units, not from CSS
|
|
// pixels. And it should take the dpi ratio into account.
|
|
// Using CSS pixels can cause the overflow to be too small if the page is
|
|
// zoomed out.
|
|
aOverflowRect->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow.top),
|
|
CSSPixel::ToAppUnits(overflow.right),
|
|
CSSPixel::ToAppUnits(overflow.bottom),
|
|
CSSPixel::ToAppUnits(overflow.left)));
|
|
|
|
return true;
|
|
}
|
|
|
|
auto nsNativeBasicTheme::GetScrollbarSizes(nsPresContext* aPresContext,
|
|
StyleScrollbarWidth aWidth, Overlay)
|
|
-> ScrollbarSizes {
|
|
CSSIntCoord h = sHorizontalScrollbarHeight;
|
|
CSSIntCoord w = sVerticalScrollbarWidth;
|
|
if (aWidth == StyleScrollbarWidth::Thin) {
|
|
h /= 2;
|
|
w /= 2;
|
|
}
|
|
auto dpi = GetDPIRatioForScrollbarPart(aPresContext);
|
|
return {(CSSCoord(w) * dpi).Rounded(), (CSSCoord(h) * dpi).Rounded()};
|
|
}
|
|
|
|
nscoord nsNativeBasicTheme::GetCheckboxRadioPrefSize() {
|
|
return CSSPixel::ToAppUnits(kCheckboxRadioContentBoxSize);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext* aPresContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
LayoutDeviceIntSize* aResult,
|
|
bool* aIsOverridable) {
|
|
DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
|
|
|
|
aResult->width = aResult->height = 0;
|
|
*aIsOverridable = true;
|
|
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Button:
|
|
if (aFrame->IsColorControlFrame()) {
|
|
aResult->height = (kMinimumColorPickerHeight * dpiRatio).Rounded();
|
|
}
|
|
break;
|
|
case StyleAppearance::RangeThumb:
|
|
aResult->SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
|
|
(kMinimumRangeThumbSize * dpiRatio).Rounded());
|
|
break;
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
aResult->width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
|
|
break;
|
|
case StyleAppearance::SpinnerUpbutton:
|
|
case StyleAppearance::SpinnerDownbutton:
|
|
aResult->width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
|
|
aResult->height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
|
|
break;
|
|
case StyleAppearance::ScrollbarbuttonUp:
|
|
case StyleAppearance::ScrollbarbuttonDown:
|
|
case StyleAppearance::ScrollbarbuttonLeft:
|
|
case StyleAppearance::ScrollbarbuttonRight:
|
|
// For scrollbar-width:thin, we don't display the buttons.
|
|
if (IsScrollbarWidthThin(aFrame)) {
|
|
aResult->SizeTo(0, 0);
|
|
break;
|
|
}
|
|
[[fallthrough]];
|
|
case StyleAppearance::ScrollbarthumbVertical:
|
|
case StyleAppearance::ScrollbarthumbHorizontal: {
|
|
auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
|
|
auto width = style->StyleUIReset()->mScrollbarWidth;
|
|
auto sizes = GetScrollbarSizes(aPresContext, width, Overlay::No);
|
|
// TODO: for short scrollbars it could be nice if the thumb could shrink
|
|
// under this size.
|
|
const bool isHorizontal =
|
|
aAppearance == StyleAppearance::ScrollbarthumbHorizontal ||
|
|
aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
|
|
aAppearance == StyleAppearance::ScrollbarbuttonRight;
|
|
const auto size = isHorizontal ? sizes.mHorizontal : sizes.mVertical;
|
|
aResult->SizeTo(size, size);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsITheme::Transparency nsNativeBasicTheme::GetWidgetTransparency(
|
|
nsIFrame*, StyleAppearance) {
|
|
return eUnknownTransparency;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeBasicTheme::WidgetStateChanged(nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
nsAtom* aAttribute, bool* aShouldRepaint,
|
|
const nsAttrValue* aOldValue) {
|
|
if (!aAttribute) {
|
|
// Hover/focus/active changed. Always repaint.
|
|
*aShouldRepaint = true;
|
|
} else {
|
|
// Check the attribute to see if it's relevant.
|
|
// disabled, checked, dlgtype, default, etc.
|
|
*aShouldRepaint = false;
|
|
if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
|
|
aAttribute == nsGkAtoms::selected ||
|
|
aAttribute == nsGkAtoms::visuallyselected ||
|
|
aAttribute == nsGkAtoms::menuactive ||
|
|
aAttribute == nsGkAtoms::sortDirection ||
|
|
aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
|
|
aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover) {
|
|
*aShouldRepaint = true;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeBasicTheme::ThemeChanged() { return NS_OK; }
|
|
|
|
bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(
|
|
StyleAppearance aAppearance) {
|
|
return IsWidgetScrollbarPart(aAppearance);
|
|
}
|
|
|
|
nsITheme::ThemeGeometryType nsNativeBasicTheme::ThemeGeometryTypeForWidget(
|
|
nsIFrame* aFrame, StyleAppearance aAppearance) {
|
|
return eThemeGeometryTypeUnknown;
|
|
}
|
|
|
|
bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext* aPresContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::FocusOutline:
|
|
case StyleAppearance::Textarea:
|
|
case StyleAppearance::Textfield:
|
|
case StyleAppearance::Range:
|
|
case StyleAppearance::RangeThumb:
|
|
case StyleAppearance::ProgressBar:
|
|
case StyleAppearance::Progresschunk:
|
|
case StyleAppearance::Meter:
|
|
case StyleAppearance::Meterchunk:
|
|
case StyleAppearance::ScrollbarbuttonUp:
|
|
case StyleAppearance::ScrollbarbuttonDown:
|
|
case StyleAppearance::ScrollbarbuttonLeft:
|
|
case StyleAppearance::ScrollbarbuttonRight:
|
|
case StyleAppearance::ScrollbarthumbHorizontal:
|
|
case StyleAppearance::ScrollbarthumbVertical:
|
|
case StyleAppearance::ScrollbartrackHorizontal:
|
|
case StyleAppearance::ScrollbartrackVertical:
|
|
case StyleAppearance::ScrollbarHorizontal:
|
|
case StyleAppearance::ScrollbarVertical:
|
|
case StyleAppearance::Scrollcorner:
|
|
case StyleAppearance::Button:
|
|
case StyleAppearance::Listbox:
|
|
case StyleAppearance::Menulist:
|
|
case StyleAppearance::MenulistButton:
|
|
case StyleAppearance::NumberInput:
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
case StyleAppearance::SpinnerUpbutton:
|
|
case StyleAppearance::SpinnerDownbutton:
|
|
return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::MozMenulistArrowButton:
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Checkbox:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
|
|
return true;
|
|
}
|
|
|
|
bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }
|