1 /* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2; -*- */
2 /* vim: set sw=2 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "ScrollbarDrawing.h"
9 #include "mozilla/RelativeLuminanceUtils.h"
10 #include "mozilla/StaticPrefs_widget.h"
11 #include "nsContainerFrame.h"
12 #include "nsDeviceContext.h"
14 #include "nsLayoutUtils.h"
15 #include "nsLookAndFeel.h"
16 #include "nsNativeTheme.h"
18 using namespace mozilla::gfx
;
20 namespace mozilla::widget
{
22 using mozilla::RelativeLuminanceUtils
;
25 auto ScrollbarDrawing::GetDPIRatioForScrollbarPart(nsPresContext
* aPc
)
27 if (auto* rootPc
= aPc
->GetRootPresContext()) {
28 if (nsCOMPtr
<nsIWidget
> widget
= rootPc
->GetRootWidget()) {
29 return widget
->GetDefaultScale();
33 float(AppUnitsPerCSSPixel()) /
34 float(aPc
->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()));
38 nsIFrame
* ScrollbarDrawing::GetParentScrollbarFrame(nsIFrame
* aFrame
) {
39 // Walk our parents to find a scrollbar frame
40 nsIFrame
* scrollbarFrame
= aFrame
;
42 if (scrollbarFrame
->IsScrollbarFrame()) {
45 } while ((scrollbarFrame
= scrollbarFrame
->GetParent()));
47 // We return null if we can't find a parent scrollbar frame
48 return scrollbarFrame
;
52 bool ScrollbarDrawing::IsParentScrollbarRolledOver(nsIFrame
* aFrame
) {
53 nsIFrame
* scrollbarFrame
= GetParentScrollbarFrame(aFrame
);
54 return aFrame
->PresContext()->UseOverlayScrollbars()
55 ? nsNativeTheme::CheckBooleanAttr(scrollbarFrame
, nsGkAtoms::hover
)
56 : nsNativeTheme::GetContentState(scrollbarFrame
,
57 StyleAppearance::None
)
58 .HasState(NS_EVENT_STATE_HOVER
);
62 bool ScrollbarDrawing::IsParentScrollbarHoveredOrActive(nsIFrame
* aFrame
) {
63 nsIFrame
* scrollbarFrame
= GetParentScrollbarFrame(aFrame
);
64 return scrollbarFrame
&& scrollbarFrame
->GetContent()
67 .HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER
|
68 NS_EVENT_STATE_ACTIVE
);
72 bool ScrollbarDrawing::IsScrollbarWidthThin(const ComputedStyle
& aStyle
) {
73 auto scrollbarWidth
= aStyle
.StyleUIReset()->ScrollbarWidth();
74 return scrollbarWidth
== StyleScrollbarWidth::Thin
;
78 bool ScrollbarDrawing::IsScrollbarWidthThin(nsIFrame
* aFrame
) {
79 ComputedStyle
* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
80 return IsScrollbarWidthThin(*style
);
83 auto ScrollbarDrawing::GetScrollbarSizes(nsPresContext
* aPresContext
,
84 StyleScrollbarWidth aWidth
, Overlay
)
86 uint32_t h
= GetHorizontalScrollbarHeight();
87 uint32_t w
= GetVerticalScrollbarWidth();
88 if (aWidth
== StyleScrollbarWidth::Thin
) {
92 auto dpi
= GetDPIRatioForScrollbarPart(aPresContext
);
93 return {(CSSCoord(w
) * dpi
).Rounded(), (CSSCoord(h
) * dpi
).Rounded()};
96 auto ScrollbarDrawing::GetScrollbarSizes(nsPresContext
* aPresContext
,
97 nsIFrame
* aFrame
) -> ScrollbarSizes
{
98 auto* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
99 auto width
= style
->StyleUIReset()->ScrollbarWidth();
101 aPresContext
->UseOverlayScrollbars() ? Overlay::Yes
: Overlay::No
;
102 return GetScrollbarSizes(aPresContext
, width
, overlay
);
105 bool ScrollbarDrawing::IsScrollbarTrackOpaque(nsIFrame
* aFrame
) {
106 auto trackColor
= ComputeScrollbarTrackColor(
107 aFrame
, *nsLayoutUtils::StyleForScrollbar(aFrame
),
108 aFrame
->PresContext()->Document()->GetDocumentState(),
109 Colors(aFrame
, StyleAppearance::ScrollbartrackVertical
));
110 return trackColor
.a
== 1.0f
;
113 sRGBColor
ScrollbarDrawing::ComputeScrollbarTrackColor(
114 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
115 const EventStates
& aDocumentState
, const Colors
& aColors
) {
116 if (aColors
.HighContrast()) {
117 return aColors
.System(StyleSystemColor::Window
);
119 const nsStyleUI
* ui
= aStyle
.StyleUI();
120 if (ui
->mScrollbarColor
.IsColors()) {
121 return sRGBColor::FromABGR(
122 ui
->mScrollbarColor
.AsColors().track
.CalcColor(aStyle
));
124 static constexpr sRGBColor sDefaultDarkTrackColor
=
125 sRGBColor::FromU8(20, 20, 25, 77);
126 static constexpr sRGBColor
sDefaultTrackColor(
127 gfx::sRGBColor::UnusualFromARGB(0xfff0f0f0));
130 aDocumentState
.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE
)
131 ? StyleSystemColor::ThemedScrollbarInactive
132 : StyleSystemColor::ThemedScrollbar
;
133 return aColors
.SystemOrElse(systemColor
, [&] {
134 return aColors
.IsDark() ? sDefaultDarkTrackColor
: sDefaultTrackColor
;
138 // Don't use the theme color for dark scrollbars if it's not a color (if it's
139 // grey-ish), as that'd either lack enough contrast, or be close to what we'd do
140 // by default anyways.
141 sRGBColor
ScrollbarDrawing::ComputeScrollbarThumbColor(
142 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
143 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
144 const Colors
& aColors
) {
145 const nsStyleUI
* ui
= aStyle
.StyleUI();
146 if (ui
->mScrollbarColor
.IsColors()) {
147 return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
148 ui
->mScrollbarColor
.AsColors().thumb
.CalcColor(aStyle
), aElementState
));
151 auto systemColor
= [&] {
152 if (aDocumentState
.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE
)) {
153 return StyleSystemColor::ThemedScrollbarThumbInactive
;
155 if (aElementState
.HasState(NS_EVENT_STATE_ACTIVE
)) {
156 if (aColors
.HighContrast()) {
157 return StyleSystemColor::Selecteditem
;
159 return StyleSystemColor::ThemedScrollbarThumbActive
;
161 if (aElementState
.HasState(NS_EVENT_STATE_HOVER
)) {
162 if (aColors
.HighContrast()) {
163 return StyleSystemColor::Selecteditem
;
165 return StyleSystemColor::ThemedScrollbarThumbHover
;
167 if (aColors
.HighContrast()) {
168 return StyleSystemColor::Windowtext
;
170 return StyleSystemColor::ThemedScrollbarThumb
;
173 return aColors
.SystemOrElse(systemColor
, [&] {
174 const nscolor unthemedColor
= aColors
.IsDark() ? NS_RGBA(249, 249, 250, 102)
175 : NS_RGB(0xcd, 0xcd, 0xcd);
177 return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
178 unthemedColor
, aElementState
));
182 template <typename PaintBackendData
>
183 bool ScrollbarDrawing::DoPaintDefaultScrollbar(
184 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
185 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
186 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
187 const Colors
& aColors
, const DPIRatio
& aDpiRatio
) {
188 const bool overlay
= aFrame
->PresContext()->UseOverlayScrollbars();
189 if (overlay
&& !aElementState
.HasAtLeastOneOfStates(NS_EVENT_STATE_HOVER
|
190 NS_EVENT_STATE_ACTIVE
)) {
194 ComputeScrollbarTrackColor(aFrame
, aStyle
, aDocumentState
, aColors
);
195 if (overlay
&& mKind
== Kind::Win11
) {
196 LayoutDeviceCoord radius
=
197 (aScrollbarKind
== ScrollbarKind::Horizontal
? aRect
.height
200 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, aRect
, color
,
201 sRGBColor(), 0, radius
/ aDpiRatio
,
204 ThemeDrawing::FillRect(aPaintData
, aRect
, color
);
209 bool ScrollbarDrawing::PaintScrollbar(
210 DrawTarget
& aDrawTarget
, const LayoutDeviceRect
& aRect
,
211 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
212 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
213 const Colors
& aColors
, const DPIRatio
& aDpiRatio
) {
214 return DoPaintDefaultScrollbar(aDrawTarget
, aRect
, aScrollbarKind
, aFrame
,
215 aStyle
, aElementState
, aDocumentState
, aColors
,
219 bool ScrollbarDrawing::PaintScrollbar(
220 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
221 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
222 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
223 const Colors
& aColors
, const DPIRatio
& aDpiRatio
) {
224 return DoPaintDefaultScrollbar(aWrData
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
225 aElementState
, aDocumentState
, aColors
,
229 template <typename PaintBackendData
>
230 bool ScrollbarDrawing::DoPaintDefaultScrollCorner(
231 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
232 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
233 const EventStates
& aDocumentState
, const Colors
& aColors
,
234 const DPIRatio
& aDpiRatio
) {
235 auto scrollbarColor
=
236 ComputeScrollbarTrackColor(aFrame
, aStyle
, aDocumentState
, aColors
);
237 ThemeDrawing::FillRect(aPaintData
, aRect
, scrollbarColor
);
241 bool ScrollbarDrawing::PaintScrollCorner(
242 DrawTarget
& aDrawTarget
, const LayoutDeviceRect
& aRect
,
243 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
244 const EventStates
& aDocumentState
, const Colors
& aColors
,
245 const DPIRatio
& aDpiRatio
) {
246 return DoPaintDefaultScrollCorner(aDrawTarget
, aRect
, aScrollbarKind
, aFrame
,
247 aStyle
, aDocumentState
, aColors
, aDpiRatio
);
250 bool ScrollbarDrawing::PaintScrollCorner(
251 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
252 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
253 const EventStates
& aDocumentState
, const Colors
& aColors
,
254 const DPIRatio
& aDpiRatio
) {
255 return DoPaintDefaultScrollCorner(aWrData
, aRect
, aScrollbarKind
, aFrame
,
256 aStyle
, aDocumentState
, aColors
, aDpiRatio
);
259 nscolor
ScrollbarDrawing::GetScrollbarButtonColor(nscolor aTrackColor
,
260 EventStates aStates
) {
261 // See numbers in GetScrollbarArrowColor.
262 // This function is written based on ratios between values listed there.
264 bool isActive
= aStates
.HasState(NS_EVENT_STATE_ACTIVE
);
265 bool isHover
= aStates
.HasState(NS_EVENT_STATE_HOVER
);
266 if (!isActive
&& !isHover
) {
269 float luminance
= RelativeLuminanceUtils::Compute(aTrackColor
);
271 if (luminance
>= 0.18f
) {
275 luminance
= std::min(luminance
, 1.0f
);
278 if (luminance
>= 0.18f
) {
284 return RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
);
287 Maybe
<nscolor
> ScrollbarDrawing::GetScrollbarArrowColor(nscolor aButtonColor
) {
288 // In Windows 10 scrollbar, there are several gray colors used:
290 // State | Background (lum) | Arrow | Contrast
291 // -------+------------------+---------+---------
292 // Normal | Gray 240 (87.1%) | Gray 96 | 5.5
293 // Hover | Gray 218 (70.1%) | Black | 15.0
294 // Active | Gray 96 (11.7%) | White | 6.3
296 // Contrast value is computed based on the definition in
297 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef
299 // This function is written based on these values.
301 if (NS_GET_A(aButtonColor
) == 0) {
302 // If the button color is transparent, because of e.g.
303 // scrollbar-color: <something> transparent, then use
304 // the thumb color, which is expected to have enough
309 float luminance
= RelativeLuminanceUtils::Compute(aButtonColor
);
310 // Color with luminance larger than 0.72 has contrast ratio over 4.6
311 // to color with luminance of gray 96, so this value is chosen for
312 // this range. It is the luminance of gray 221.
313 if (luminance
>= 0.72) {
314 // ComputeRelativeLuminanceFromComponents(96). That function cannot
315 // be constexpr because of std::pow.
316 const float GRAY96_LUMINANCE
= 0.117f
;
317 return Some(RelativeLuminanceUtils::Adjust(aButtonColor
, GRAY96_LUMINANCE
));
319 // The contrast ratio of a color to black equals that to white when its
320 // luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
321 // thus the value below. It's the lumanince of gray 118.
323 // TODO(emilio): Maybe the button alpha is not the best thing to use here and
324 // we should use the thumb alpha? It seems weird that the color of the arrow
325 // depends on the opacity of the scrollbar thumb...
326 if (luminance
>= 0.18) {
327 return Some(NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor
)));
329 return Some(NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor
)));
332 std::pair
<sRGBColor
, sRGBColor
> ScrollbarDrawing::ComputeScrollbarButtonColors(
333 nsIFrame
* aFrame
, StyleAppearance aAppearance
, const ComputedStyle
& aStyle
,
334 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
335 const Colors
& aColors
) {
336 if (aColors
.HighContrast()) {
337 if (aElementState
.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE
|
338 NS_EVENT_STATE_HOVER
)) {
339 return aColors
.SystemPair(StyleSystemColor::Selecteditem
,
340 StyleSystemColor::Buttonface
);
342 return aColors
.SystemPair(StyleSystemColor::Window
,
343 StyleSystemColor::Windowtext
);
347 ComputeScrollbarTrackColor(aFrame
, aStyle
, aDocumentState
, aColors
);
348 nscolor buttonColor
=
349 GetScrollbarButtonColor(trackColor
.ToABGR(), aElementState
);
351 GetScrollbarArrowColor(buttonColor
)
352 .map(sRGBColor::FromABGR
)
354 return ComputeScrollbarThumbColor(aFrame
, aStyle
, aElementState
,
355 aDocumentState
, aColors
);
357 return {sRGBColor::FromABGR(buttonColor
), arrowColor
};
360 bool ScrollbarDrawing::PaintScrollbarButton(
361 DrawTarget
& aDrawTarget
, StyleAppearance aAppearance
,
362 const LayoutDeviceRect
& aRect
, ScrollbarKind aScrollbarKind
,
363 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
364 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
365 const Colors
& aColors
, const DPIRatio
&) {
366 auto [buttonColor
, arrowColor
] = ComputeScrollbarButtonColors(
367 aFrame
, aAppearance
, aStyle
, aElementState
, aDocumentState
, aColors
);
368 aDrawTarget
.FillRect(aRect
.ToUnknownRect(),
369 ColorPattern(ToDeviceColor(buttonColor
)));
371 // Start with Up arrow.
372 float arrowPolygonX
[] = {-4.0f
, 0.0f
, 4.0f
, 4.0f
, 0.0f
, -4.0f
};
373 float arrowPolygonY
[] = {0.0f
, -4.0f
, 0.0f
, 3.0f
, -1.0f
, 3.0f
};
375 const float kPolygonSize
= 17;
377 const int32_t arrowNumPoints
= ArrayLength(arrowPolygonX
);
378 switch (aAppearance
) {
379 case StyleAppearance::ScrollbarbuttonUp
:
381 case StyleAppearance::ScrollbarbuttonDown
:
382 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
383 arrowPolygonY
[i
] *= -1;
386 case StyleAppearance::ScrollbarbuttonLeft
:
387 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
388 float temp
= arrowPolygonX
[i
];
389 arrowPolygonX
[i
] = arrowPolygonY
[i
];
390 arrowPolygonY
[i
] = temp
;
393 case StyleAppearance::ScrollbarbuttonRight
:
394 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
395 float temp
= arrowPolygonX
[i
];
396 arrowPolygonX
[i
] = arrowPolygonY
[i
] * -1;
397 arrowPolygonY
[i
] = temp
;
403 ThemeDrawing::PaintArrow(aDrawTarget
, aRect
, arrowPolygonX
, arrowPolygonY
,
404 kPolygonSize
, arrowNumPoints
, arrowColor
);
408 } // namespace mozilla::widget