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(const nsPresContext
* aPc
)
28 float(AppUnitsPerCSSPixel()) /
29 float(aPc
->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom()));
30 if (aPc
->IsPrintPreview()) {
31 ratio
.scale
*= aPc
->GetPrintPreviewScaleForSequenceFrameOrScrollbars();
33 if (mKind
== Kind::Cocoa
) {
34 return DPIRatio(ratio
.scale
>= 2.0f
? 2.0f
: 1.0f
);
40 nsIFrame
* ScrollbarDrawing::GetParentScrollbarFrame(nsIFrame
* aFrame
) {
41 // Walk our parents to find a scrollbar frame
42 nsIFrame
* scrollbarFrame
= aFrame
;
44 if (scrollbarFrame
->IsScrollbarFrame()) {
47 } while ((scrollbarFrame
= scrollbarFrame
->GetParent()));
49 // We return null if we can't find a parent scrollbar frame
50 return scrollbarFrame
;
54 bool ScrollbarDrawing::IsParentScrollbarRolledOver(nsIFrame
* aFrame
) {
55 nsIFrame
* scrollbarFrame
= GetParentScrollbarFrame(aFrame
);
56 return aFrame
->PresContext()->UseOverlayScrollbars()
57 ? nsNativeTheme::CheckBooleanAttr(scrollbarFrame
, nsGkAtoms::hover
)
58 : nsNativeTheme::GetContentState(scrollbarFrame
,
59 StyleAppearance::None
)
60 .HasState(ElementState::HOVER
);
64 bool ScrollbarDrawing::IsParentScrollbarHoveredOrActive(nsIFrame
* aFrame
) {
65 nsIFrame
* scrollbarFrame
= GetParentScrollbarFrame(aFrame
);
66 return scrollbarFrame
&&
67 scrollbarFrame
->GetContent()
70 .HasAtLeastOneOfStates(ElementState::HOVER
| ElementState::ACTIVE
);
74 bool ScrollbarDrawing::IsScrollbarWidthThin(const ComputedStyle
& aStyle
) {
75 auto scrollbarWidth
= aStyle
.StyleUIReset()->ScrollbarWidth();
76 return scrollbarWidth
== StyleScrollbarWidth::Thin
;
80 bool ScrollbarDrawing::IsScrollbarWidthThin(nsIFrame
* aFrame
) {
81 ComputedStyle
* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
82 return IsScrollbarWidthThin(*style
);
85 CSSIntCoord
ScrollbarDrawing::GetCSSScrollbarSize(StyleScrollbarWidth aWidth
,
86 Overlay aOverlay
) const {
87 return mScrollbarSize
[aWidth
== StyleScrollbarWidth::Thin
]
88 [aOverlay
== Overlay::Yes
];
91 void ScrollbarDrawing::ConfigureScrollbarSize(StyleScrollbarWidth aWidth
,
94 mScrollbarSize
[aWidth
== StyleScrollbarWidth::Thin
]
95 [aOverlay
== Overlay::Yes
] = aSize
;
98 void ScrollbarDrawing::ConfigureScrollbarSize(CSSIntCoord aSize
) {
99 ConfigureScrollbarSize(StyleScrollbarWidth::Auto
, Overlay::No
, aSize
);
100 ConfigureScrollbarSize(StyleScrollbarWidth::Auto
, Overlay::Yes
, aSize
);
101 ConfigureScrollbarSize(StyleScrollbarWidth::Thin
, Overlay::No
, aSize
/ 2);
102 ConfigureScrollbarSize(StyleScrollbarWidth::Thin
, Overlay::Yes
, aSize
/ 2);
105 LayoutDeviceIntCoord
ScrollbarDrawing::GetScrollbarSize(
106 const nsPresContext
* aPresContext
, StyleScrollbarWidth aWidth
,
108 return (CSSCoord(GetCSSScrollbarSize(aWidth
, aOverlay
)) *
109 GetDPIRatioForScrollbarPart(aPresContext
))
113 LayoutDeviceIntCoord
ScrollbarDrawing::GetScrollbarSize(
114 const nsPresContext
* aPresContext
, nsIFrame
* aFrame
) {
115 auto* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
116 auto width
= style
->StyleUIReset()->ScrollbarWidth();
118 aPresContext
->UseOverlayScrollbars() ? Overlay::Yes
: Overlay::No
;
119 return GetScrollbarSize(aPresContext
, width
, overlay
);
122 bool ScrollbarDrawing::IsScrollbarTrackOpaque(nsIFrame
* aFrame
) {
123 auto trackColor
= ComputeScrollbarTrackColor(
124 aFrame
, *nsLayoutUtils::StyleForScrollbar(aFrame
),
125 aFrame
->PresContext()->Document()->GetDocumentState(),
126 Colors(aFrame
, StyleAppearance::ScrollbartrackVertical
));
127 return trackColor
.a
== 1.0f
;
130 sRGBColor
ScrollbarDrawing::ComputeScrollbarTrackColor(
131 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
132 const DocumentState
& aDocumentState
, const Colors
& aColors
) {
133 if (aColors
.HighContrast()) {
134 return aColors
.System(StyleSystemColor::Window
);
136 const nsStyleUI
* ui
= aStyle
.StyleUI();
137 if (ui
->mScrollbarColor
.IsColors()) {
138 return sRGBColor::FromABGR(
139 ui
->mScrollbarColor
.AsColors().track
.CalcColor(aStyle
));
141 static constexpr sRGBColor sDefaultDarkTrackColor
=
142 sRGBColor::FromU8(20, 20, 25, 77);
143 static constexpr sRGBColor
sDefaultTrackColor(
144 gfx::sRGBColor::UnusualFromARGB(0xfff0f0f0));
146 auto systemColor
= aDocumentState
.HasAllStates(DocumentState::WINDOW_INACTIVE
)
147 ? StyleSystemColor::ThemedScrollbarInactive
148 : StyleSystemColor::ThemedScrollbar
;
149 return aColors
.SystemOrElse(systemColor
, [&] {
150 return aColors
.IsDark() ? sDefaultDarkTrackColor
: sDefaultTrackColor
;
154 // Don't use the theme color for dark scrollbars if it's not a color (if it's
155 // grey-ish), as that'd either lack enough contrast, or be close to what we'd do
156 // by default anyways.
157 sRGBColor
ScrollbarDrawing::ComputeScrollbarThumbColor(
158 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
159 const ElementState
& aElementState
, const DocumentState
& aDocumentState
,
160 const Colors
& aColors
) {
161 const nsStyleUI
* ui
= aStyle
.StyleUI();
162 if (ui
->mScrollbarColor
.IsColors()) {
163 return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
164 ui
->mScrollbarColor
.AsColors().thumb
.CalcColor(aStyle
), aElementState
));
167 auto systemColor
= [&] {
168 if (aDocumentState
.HasState(DocumentState::WINDOW_INACTIVE
)) {
169 return StyleSystemColor::ThemedScrollbarThumbInactive
;
171 if (aElementState
.HasState(ElementState::ACTIVE
)) {
172 if (aColors
.HighContrast()) {
173 return StyleSystemColor::Selecteditem
;
175 return StyleSystemColor::ThemedScrollbarThumbActive
;
177 if (aElementState
.HasState(ElementState::HOVER
)) {
178 if (aColors
.HighContrast()) {
179 return StyleSystemColor::Selecteditem
;
181 return StyleSystemColor::ThemedScrollbarThumbHover
;
183 if (aColors
.HighContrast()) {
184 return StyleSystemColor::Windowtext
;
186 return StyleSystemColor::ThemedScrollbarThumb
;
189 return aColors
.SystemOrElse(systemColor
, [&] {
190 const nscolor unthemedColor
= aColors
.IsDark() ? NS_RGBA(249, 249, 250, 102)
191 : NS_RGB(0xcd, 0xcd, 0xcd);
193 return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
194 unthemedColor
, aElementState
));
198 template <typename PaintBackendData
>
199 bool ScrollbarDrawing::DoPaintDefaultScrollbar(
200 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
201 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
202 const ElementState
& aElementState
, const DocumentState
& aDocumentState
,
203 const Colors
& aColors
, const DPIRatio
& aDpiRatio
) {
204 const bool overlay
= aFrame
->PresContext()->UseOverlayScrollbars();
205 if (overlay
&& !aElementState
.HasAtLeastOneOfStates(ElementState::HOVER
|
206 ElementState::ACTIVE
)) {
210 ComputeScrollbarTrackColor(aFrame
, aStyle
, aDocumentState
, aColors
);
211 if (overlay
&& mKind
== Kind::Win11
) {
212 LayoutDeviceCoord radius
=
213 (aScrollbarKind
== ScrollbarKind::Horizontal
? aRect
.height
216 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, aRect
, color
,
217 sRGBColor(), 0, radius
/ aDpiRatio
,
220 ThemeDrawing::FillRect(aPaintData
, aRect
, color
);
225 bool ScrollbarDrawing::PaintScrollbar(
226 DrawTarget
& aDrawTarget
, const LayoutDeviceRect
& aRect
,
227 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
228 const ElementState
& aElementState
, const DocumentState
& aDocumentState
,
229 const Colors
& aColors
, const DPIRatio
& aDpiRatio
) {
230 return DoPaintDefaultScrollbar(aDrawTarget
, aRect
, aScrollbarKind
, aFrame
,
231 aStyle
, aElementState
, aDocumentState
, aColors
,
235 bool ScrollbarDrawing::PaintScrollbar(
236 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
237 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
238 const ElementState
& aElementState
, const DocumentState
& aDocumentState
,
239 const Colors
& aColors
, const DPIRatio
& aDpiRatio
) {
240 return DoPaintDefaultScrollbar(aWrData
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
241 aElementState
, aDocumentState
, aColors
,
245 template <typename PaintBackendData
>
246 bool ScrollbarDrawing::DoPaintDefaultScrollCorner(
247 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
248 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
249 const DocumentState
& aDocumentState
, const Colors
& aColors
,
250 const DPIRatio
& aDpiRatio
) {
251 auto scrollbarColor
=
252 ComputeScrollbarTrackColor(aFrame
, aStyle
, aDocumentState
, aColors
);
253 ThemeDrawing::FillRect(aPaintData
, aRect
, scrollbarColor
);
257 bool ScrollbarDrawing::PaintScrollCorner(
258 DrawTarget
& aDrawTarget
, const LayoutDeviceRect
& aRect
,
259 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
260 const DocumentState
& aDocumentState
, const Colors
& aColors
,
261 const DPIRatio
& aDpiRatio
) {
262 return DoPaintDefaultScrollCorner(aDrawTarget
, aRect
, aScrollbarKind
, aFrame
,
263 aStyle
, aDocumentState
, aColors
, aDpiRatio
);
266 bool ScrollbarDrawing::PaintScrollCorner(
267 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
268 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
269 const DocumentState
& aDocumentState
, const Colors
& aColors
,
270 const DPIRatio
& aDpiRatio
) {
271 return DoPaintDefaultScrollCorner(aWrData
, aRect
, aScrollbarKind
, aFrame
,
272 aStyle
, aDocumentState
, aColors
, aDpiRatio
);
275 nscolor
ScrollbarDrawing::GetScrollbarButtonColor(nscolor aTrackColor
,
276 ElementState aStates
) {
277 // See numbers in GetScrollbarArrowColor.
278 // This function is written based on ratios between values listed there.
280 bool isActive
= aStates
.HasState(ElementState::ACTIVE
);
281 bool isHover
= aStates
.HasState(ElementState::HOVER
);
282 if (!isActive
&& !isHover
) {
285 float luminance
= RelativeLuminanceUtils::Compute(aTrackColor
);
287 if (luminance
>= 0.18f
) {
291 luminance
= std::min(luminance
, 1.0f
);
294 if (luminance
>= 0.18f
) {
300 return RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
);
303 Maybe
<nscolor
> ScrollbarDrawing::GetScrollbarArrowColor(nscolor aButtonColor
) {
304 // In Windows 10 scrollbar, there are several gray colors used:
306 // State | Background (lum) | Arrow | Contrast
307 // -------+------------------+---------+---------
308 // Normal | Gray 240 (87.1%) | Gray 96 | 5.5
309 // Hover | Gray 218 (70.1%) | Black | 15.0
310 // Active | Gray 96 (11.7%) | White | 6.3
312 // Contrast value is computed based on the definition in
313 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef
315 // This function is written based on these values.
317 if (NS_GET_A(aButtonColor
) == 0) {
318 // If the button color is transparent, because of e.g.
319 // scrollbar-color: <something> transparent, then use
320 // the thumb color, which is expected to have enough
325 float luminance
= RelativeLuminanceUtils::Compute(aButtonColor
);
326 // Color with luminance larger than 0.72 has contrast ratio over 4.6
327 // to color with luminance of gray 96, so this value is chosen for
328 // this range. It is the luminance of gray 221.
329 if (luminance
>= 0.72) {
330 // ComputeRelativeLuminanceFromComponents(96). That function cannot
331 // be constexpr because of std::pow.
332 const float GRAY96_LUMINANCE
= 0.117f
;
333 return Some(RelativeLuminanceUtils::Adjust(aButtonColor
, GRAY96_LUMINANCE
));
335 // The contrast ratio of a color to black equals that to white when its
336 // luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
337 // thus the value below. It's the lumanince of gray 118.
339 // TODO(emilio): Maybe the button alpha is not the best thing to use here and
340 // we should use the thumb alpha? It seems weird that the color of the arrow
341 // depends on the opacity of the scrollbar thumb...
342 if (luminance
>= 0.18) {
343 return Some(NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor
)));
345 return Some(NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor
)));
348 std::pair
<sRGBColor
, sRGBColor
> ScrollbarDrawing::ComputeScrollbarButtonColors(
349 nsIFrame
* aFrame
, StyleAppearance aAppearance
, const ComputedStyle
& aStyle
,
350 const ElementState
& aElementState
, const DocumentState
& aDocumentState
,
351 const Colors
& aColors
) {
352 if (aColors
.HighContrast()) {
353 if (aElementState
.HasAtLeastOneOfStates(ElementState::ACTIVE
|
354 ElementState::HOVER
)) {
355 return aColors
.SystemPair(StyleSystemColor::Selecteditem
,
356 StyleSystemColor::Buttonface
);
358 return aColors
.SystemPair(StyleSystemColor::Window
,
359 StyleSystemColor::Windowtext
);
363 ComputeScrollbarTrackColor(aFrame
, aStyle
, aDocumentState
, aColors
);
364 nscolor buttonColor
=
365 GetScrollbarButtonColor(trackColor
.ToABGR(), aElementState
);
367 GetScrollbarArrowColor(buttonColor
)
368 .map(sRGBColor::FromABGR
)
370 return ComputeScrollbarThumbColor(aFrame
, aStyle
, aElementState
,
371 aDocumentState
, aColors
);
373 return {sRGBColor::FromABGR(buttonColor
), arrowColor
};
376 bool ScrollbarDrawing::PaintScrollbarButton(
377 DrawTarget
& aDrawTarget
, StyleAppearance aAppearance
,
378 const LayoutDeviceRect
& aRect
, ScrollbarKind aScrollbarKind
,
379 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
380 const ElementState
& aElementState
, const DocumentState
& aDocumentState
,
381 const Colors
& aColors
, const DPIRatio
&) {
382 auto [buttonColor
, arrowColor
] = ComputeScrollbarButtonColors(
383 aFrame
, aAppearance
, aStyle
, aElementState
, aDocumentState
, aColors
);
384 aDrawTarget
.FillRect(aRect
.ToUnknownRect(),
385 ColorPattern(ToDeviceColor(buttonColor
)));
387 // Start with Up arrow.
388 float arrowPolygonX
[] = {-4.0f
, 0.0f
, 4.0f
, 4.0f
, 0.0f
, -4.0f
};
389 float arrowPolygonY
[] = {0.0f
, -4.0f
, 0.0f
, 3.0f
, -1.0f
, 3.0f
};
391 const float kPolygonSize
= 17;
393 const int32_t arrowNumPoints
= ArrayLength(arrowPolygonX
);
394 switch (aAppearance
) {
395 case StyleAppearance::ScrollbarbuttonUp
:
397 case StyleAppearance::ScrollbarbuttonDown
:
398 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
399 arrowPolygonY
[i
] *= -1;
402 case StyleAppearance::ScrollbarbuttonLeft
:
403 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
404 float temp
= arrowPolygonX
[i
];
405 arrowPolygonX
[i
] = arrowPolygonY
[i
];
406 arrowPolygonY
[i
] = temp
;
409 case StyleAppearance::ScrollbarbuttonRight
:
410 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
411 float temp
= arrowPolygonX
[i
];
412 arrowPolygonX
[i
] = arrowPolygonY
[i
] * -1;
413 arrowPolygonY
[i
] = temp
;
419 ThemeDrawing::PaintArrow(aDrawTarget
, aRect
, arrowPolygonX
, arrowPolygonY
,
420 kPolygonSize
, arrowNumPoints
, arrowColor
);
424 } // namespace mozilla::widget