mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
49f269b507
This is to make GetDPIRatioForScrollbarPart thread-safe for stylo usage. This was using the widget for bug 1727289, but just looking at the print preview scale is enough to fix that. Reviewed in: https://phabricator.services.mozilla.com/D168148
425 lines
16 KiB
C++
425 lines
16 KiB
C++
/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
|
|
/* vim: set sw=2 ts=8 et tw=80 : */
|
|
/* 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 "ScrollbarDrawing.h"
|
|
|
|
#include "mozilla/RelativeLuminanceUtils.h"
|
|
#include "mozilla/StaticPrefs_widget.h"
|
|
#include "nsContainerFrame.h"
|
|
#include "nsDeviceContext.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsLookAndFeel.h"
|
|
#include "nsNativeTheme.h"
|
|
|
|
using namespace mozilla::gfx;
|
|
|
|
namespace mozilla::widget {
|
|
|
|
using mozilla::RelativeLuminanceUtils;
|
|
|
|
/* static */
|
|
auto ScrollbarDrawing::GetDPIRatioForScrollbarPart(const nsPresContext* aPc)
|
|
-> DPIRatio {
|
|
DPIRatio ratio(
|
|
float(AppUnitsPerCSSPixel()) /
|
|
float(aPc->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()));
|
|
if (aPc->IsPrintPreview()) {
|
|
ratio.scale *= aPc->GetPrintPreviewScaleForSequenceFrameOrScrollbars();
|
|
}
|
|
if (mKind == Kind::Cocoa) {
|
|
return DPIRatio(ratio.scale >= 2.0f ? 2.0f : 1.0f);
|
|
}
|
|
return ratio;
|
|
}
|
|
|
|
/*static*/
|
|
nsIFrame* ScrollbarDrawing::GetParentScrollbarFrame(nsIFrame* aFrame) {
|
|
// Walk our parents to find a scrollbar frame
|
|
nsIFrame* scrollbarFrame = aFrame;
|
|
do {
|
|
if (scrollbarFrame->IsScrollbarFrame()) {
|
|
break;
|
|
}
|
|
} while ((scrollbarFrame = scrollbarFrame->GetParent()));
|
|
|
|
// We return null if we can't find a parent scrollbar frame
|
|
return scrollbarFrame;
|
|
}
|
|
|
|
/*static*/
|
|
bool ScrollbarDrawing::IsParentScrollbarRolledOver(nsIFrame* aFrame) {
|
|
nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
|
|
return aFrame->PresContext()->UseOverlayScrollbars()
|
|
? nsNativeTheme::CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
|
|
: nsNativeTheme::GetContentState(scrollbarFrame,
|
|
StyleAppearance::None)
|
|
.HasState(ElementState::HOVER);
|
|
}
|
|
|
|
/*static*/
|
|
bool ScrollbarDrawing::IsParentScrollbarHoveredOrActive(nsIFrame* aFrame) {
|
|
nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
|
|
return scrollbarFrame &&
|
|
scrollbarFrame->GetContent()
|
|
->AsElement()
|
|
->State()
|
|
.HasAtLeastOneOfStates(ElementState::HOVER | ElementState::ACTIVE);
|
|
}
|
|
|
|
/*static*/
|
|
bool ScrollbarDrawing::IsScrollbarWidthThin(const ComputedStyle& aStyle) {
|
|
auto scrollbarWidth = aStyle.StyleUIReset()->ScrollbarWidth();
|
|
return scrollbarWidth == StyleScrollbarWidth::Thin;
|
|
}
|
|
|
|
/*static*/
|
|
bool ScrollbarDrawing::IsScrollbarWidthThin(nsIFrame* aFrame) {
|
|
ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
|
|
return IsScrollbarWidthThin(*style);
|
|
}
|
|
|
|
CSSIntCoord ScrollbarDrawing::GetCSSScrollbarSize(StyleScrollbarWidth aWidth,
|
|
Overlay aOverlay) const {
|
|
return mScrollbarSize[aWidth == StyleScrollbarWidth::Thin]
|
|
[aOverlay == Overlay::Yes];
|
|
}
|
|
|
|
void ScrollbarDrawing::ConfigureScrollbarSize(StyleScrollbarWidth aWidth,
|
|
Overlay aOverlay,
|
|
CSSIntCoord aSize) {
|
|
mScrollbarSize[aWidth == StyleScrollbarWidth::Thin]
|
|
[aOverlay == Overlay::Yes] = aSize;
|
|
}
|
|
|
|
void ScrollbarDrawing::ConfigureScrollbarSize(CSSIntCoord aSize) {
|
|
ConfigureScrollbarSize(StyleScrollbarWidth::Auto, Overlay::No, aSize);
|
|
ConfigureScrollbarSize(StyleScrollbarWidth::Auto, Overlay::Yes, aSize);
|
|
ConfigureScrollbarSize(StyleScrollbarWidth::Thin, Overlay::No, aSize / 2);
|
|
ConfigureScrollbarSize(StyleScrollbarWidth::Thin, Overlay::Yes, aSize / 2);
|
|
}
|
|
|
|
LayoutDeviceIntCoord ScrollbarDrawing::GetScrollbarSize(
|
|
const nsPresContext* aPresContext, StyleScrollbarWidth aWidth,
|
|
Overlay aOverlay) {
|
|
return (CSSCoord(GetCSSScrollbarSize(aWidth, aOverlay)) *
|
|
GetDPIRatioForScrollbarPart(aPresContext))
|
|
.Rounded();
|
|
}
|
|
|
|
LayoutDeviceIntCoord ScrollbarDrawing::GetScrollbarSize(
|
|
const nsPresContext* aPresContext, nsIFrame* aFrame) {
|
|
auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
|
|
auto width = style->StyleUIReset()->ScrollbarWidth();
|
|
auto overlay =
|
|
aPresContext->UseOverlayScrollbars() ? Overlay::Yes : Overlay::No;
|
|
return GetScrollbarSize(aPresContext, width, overlay);
|
|
}
|
|
|
|
bool ScrollbarDrawing::IsScrollbarTrackOpaque(nsIFrame* aFrame) {
|
|
auto trackColor = ComputeScrollbarTrackColor(
|
|
aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame),
|
|
aFrame->PresContext()->Document()->GetDocumentState(),
|
|
Colors(aFrame, StyleAppearance::ScrollbartrackVertical));
|
|
return trackColor.a == 1.0f;
|
|
}
|
|
|
|
sRGBColor ScrollbarDrawing::ComputeScrollbarTrackColor(
|
|
nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const DocumentState& aDocumentState, const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
return aColors.System(StyleSystemColor::Window);
|
|
}
|
|
const nsStyleUI* ui = aStyle.StyleUI();
|
|
if (ui->mScrollbarColor.IsColors()) {
|
|
return sRGBColor::FromABGR(
|
|
ui->mScrollbarColor.AsColors().track.CalcColor(aStyle));
|
|
}
|
|
static constexpr sRGBColor sDefaultDarkTrackColor =
|
|
sRGBColor::FromU8(20, 20, 25, 77);
|
|
static constexpr sRGBColor sDefaultTrackColor(
|
|
gfx::sRGBColor::UnusualFromARGB(0xfff0f0f0));
|
|
|
|
auto systemColor = aDocumentState.HasAllStates(DocumentState::WINDOW_INACTIVE)
|
|
? StyleSystemColor::ThemedScrollbarInactive
|
|
: StyleSystemColor::ThemedScrollbar;
|
|
return aColors.SystemOrElse(systemColor, [&] {
|
|
return aColors.IsDark() ? sDefaultDarkTrackColor : sDefaultTrackColor;
|
|
});
|
|
}
|
|
|
|
// 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.
|
|
sRGBColor ScrollbarDrawing::ComputeScrollbarThumbColor(
|
|
nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const ElementState& aElementState, const DocumentState& aDocumentState,
|
|
const Colors& aColors) {
|
|
const nsStyleUI* ui = aStyle.StyleUI();
|
|
if (ui->mScrollbarColor.IsColors()) {
|
|
return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
|
|
ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle), aElementState));
|
|
}
|
|
|
|
auto systemColor = [&] {
|
|
if (aDocumentState.HasState(DocumentState::WINDOW_INACTIVE)) {
|
|
return StyleSystemColor::ThemedScrollbarThumbInactive;
|
|
}
|
|
if (aElementState.HasState(ElementState::ACTIVE)) {
|
|
if (aColors.HighContrast()) {
|
|
return StyleSystemColor::Selecteditem;
|
|
}
|
|
return StyleSystemColor::ThemedScrollbarThumbActive;
|
|
}
|
|
if (aElementState.HasState(ElementState::HOVER)) {
|
|
if (aColors.HighContrast()) {
|
|
return StyleSystemColor::Selecteditem;
|
|
}
|
|
return StyleSystemColor::ThemedScrollbarThumbHover;
|
|
}
|
|
if (aColors.HighContrast()) {
|
|
return StyleSystemColor::Windowtext;
|
|
}
|
|
return StyleSystemColor::ThemedScrollbarThumb;
|
|
}();
|
|
|
|
return aColors.SystemOrElse(systemColor, [&] {
|
|
const nscolor unthemedColor = aColors.IsDark() ? NS_RGBA(249, 249, 250, 102)
|
|
: NS_RGB(0xcd, 0xcd, 0xcd);
|
|
|
|
return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
|
|
unthemedColor, aElementState));
|
|
});
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
bool ScrollbarDrawing::DoPaintDefaultScrollbar(
|
|
PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
|
|
ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const ElementState& aElementState, const DocumentState& aDocumentState,
|
|
const Colors& aColors, const DPIRatio& aDpiRatio) {
|
|
const bool overlay = aFrame->PresContext()->UseOverlayScrollbars();
|
|
if (overlay && !aElementState.HasAtLeastOneOfStates(ElementState::HOVER |
|
|
ElementState::ACTIVE)) {
|
|
return true;
|
|
}
|
|
const auto color =
|
|
ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
|
|
if (overlay && mKind == Kind::Win11) {
|
|
LayoutDeviceCoord radius =
|
|
(aScrollbarKind == ScrollbarKind::Horizontal ? aRect.height
|
|
: aRect.width) /
|
|
2.0f;
|
|
ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, color,
|
|
sRGBColor(), 0, radius / aDpiRatio,
|
|
aDpiRatio);
|
|
} else {
|
|
ThemeDrawing::FillRect(aPaintData, aRect, color);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ScrollbarDrawing::PaintScrollbar(
|
|
DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
|
|
ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const ElementState& aElementState, const DocumentState& aDocumentState,
|
|
const Colors& aColors, const DPIRatio& aDpiRatio) {
|
|
return DoPaintDefaultScrollbar(aDrawTarget, aRect, aScrollbarKind, aFrame,
|
|
aStyle, aElementState, aDocumentState, aColors,
|
|
aDpiRatio);
|
|
}
|
|
|
|
bool ScrollbarDrawing::PaintScrollbar(
|
|
WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
|
|
ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const ElementState& aElementState, const DocumentState& aDocumentState,
|
|
const Colors& aColors, const DPIRatio& aDpiRatio) {
|
|
return DoPaintDefaultScrollbar(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
|
|
aElementState, aDocumentState, aColors,
|
|
aDpiRatio);
|
|
}
|
|
|
|
template <typename PaintBackendData>
|
|
bool ScrollbarDrawing::DoPaintDefaultScrollCorner(
|
|
PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
|
|
ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const DocumentState& aDocumentState, const Colors& aColors,
|
|
const DPIRatio& aDpiRatio) {
|
|
auto scrollbarColor =
|
|
ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
|
|
ThemeDrawing::FillRect(aPaintData, aRect, scrollbarColor);
|
|
return true;
|
|
}
|
|
|
|
bool ScrollbarDrawing::PaintScrollCorner(
|
|
DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
|
|
ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const DocumentState& aDocumentState, const Colors& aColors,
|
|
const DPIRatio& aDpiRatio) {
|
|
return DoPaintDefaultScrollCorner(aDrawTarget, aRect, aScrollbarKind, aFrame,
|
|
aStyle, aDocumentState, aColors, aDpiRatio);
|
|
}
|
|
|
|
bool ScrollbarDrawing::PaintScrollCorner(
|
|
WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
|
|
ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const DocumentState& aDocumentState, const Colors& aColors,
|
|
const DPIRatio& aDpiRatio) {
|
|
return DoPaintDefaultScrollCorner(aWrData, aRect, aScrollbarKind, aFrame,
|
|
aStyle, aDocumentState, aColors, aDpiRatio);
|
|
}
|
|
|
|
nscolor ScrollbarDrawing::GetScrollbarButtonColor(nscolor aTrackColor,
|
|
ElementState aStates) {
|
|
// See numbers in GetScrollbarArrowColor.
|
|
// This function is written based on ratios between values listed there.
|
|
|
|
bool isActive = aStates.HasState(ElementState::ACTIVE);
|
|
bool isHover = aStates.HasState(ElementState::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);
|
|
}
|
|
|
|
Maybe<nscolor> ScrollbarDrawing::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)));
|
|
}
|
|
|
|
std::pair<sRGBColor, sRGBColor> ScrollbarDrawing::ComputeScrollbarButtonColors(
|
|
nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
|
|
const ElementState& aElementState, const DocumentState& aDocumentState,
|
|
const Colors& aColors) {
|
|
if (aColors.HighContrast()) {
|
|
if (aElementState.HasAtLeastOneOfStates(ElementState::ACTIVE |
|
|
ElementState::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};
|
|
}
|
|
|
|
bool ScrollbarDrawing::PaintScrollbarButton(
|
|
DrawTarget& aDrawTarget, StyleAppearance aAppearance,
|
|
const LayoutDeviceRect& aRect, ScrollbarKind aScrollbarKind,
|
|
nsIFrame* aFrame, const ComputedStyle& aStyle,
|
|
const ElementState& aElementState, const DocumentState& aDocumentState,
|
|
const Colors& aColors, const DPIRatio&) {
|
|
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++) {
|
|
float temp = arrowPolygonX[i];
|
|
arrowPolygonX[i] = arrowPolygonY[i];
|
|
arrowPolygonY[i] = temp;
|
|
}
|
|
break;
|
|
case StyleAppearance::ScrollbarbuttonRight:
|
|
for (int32_t i = 0; i < arrowNumPoints; i++) {
|
|
float temp = arrowPolygonX[i];
|
|
arrowPolygonX[i] = arrowPolygonY[i] * -1;
|
|
arrowPolygonY[i] = temp;
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
ThemeDrawing::PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY,
|
|
kPolygonSize, arrowNumPoints, arrowColor);
|
|
return true;
|
|
}
|
|
|
|
} // namespace mozilla::widget
|