no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / widget / ScrollbarDrawing.cpp
blob33a78ddf1cc800ace4fa9c92bb8d04ff9cdf4e9f
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"
13 #include "nsIFrame.h"
14 #include "nsLayoutUtils.h"
15 #include "nsScrollbarFrame.h"
16 #include "nsNativeTheme.h"
18 using namespace mozilla::gfx;
20 namespace mozilla::widget {
22 using mozilla::RelativeLuminanceUtils;
24 /* static */
25 auto ScrollbarDrawing::GetDPIRatioForScrollbarPart(const nsPresContext* aPc)
26 -> DPIRatio {
27 DPIRatio ratio(
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);
36 return ratio;
39 /*static*/
40 nsScrollbarFrame* ScrollbarDrawing::GetParentScrollbarFrame(nsIFrame* aFrame) {
41 for (; aFrame; aFrame = aFrame->GetParent()) {
42 if (nsScrollbarFrame* f = do_QueryFrame(aFrame)) {
43 return f;
46 return nullptr;
49 /*static*/
50 bool ScrollbarDrawing::IsParentScrollbarRolledOver(nsIFrame* aFrame) {
51 if (nsScrollbarFrame* f = GetParentScrollbarFrame(aFrame)) {
52 if (f->PresContext()->UseOverlayScrollbars()) {
53 return f->HasBeenHovered();
55 return f->GetContent()->AsElement()->State().HasState(ElementState::HOVER);
57 return false;
60 /*static*/
61 bool ScrollbarDrawing::IsParentScrollbarHoveredOrActive(nsIFrame* aFrame) {
62 nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
63 return scrollbarFrame &&
64 scrollbarFrame->GetContent()
65 ->AsElement()
66 ->State()
67 .HasAtLeastOneOfStates(ElementState::HOVER | ElementState::ACTIVE);
70 /*static*/
71 bool ScrollbarDrawing::IsScrollbarWidthThin(const ComputedStyle& aStyle) {
72 auto scrollbarWidth = aStyle.StyleUIReset()->ScrollbarWidth();
73 return scrollbarWidth == StyleScrollbarWidth::Thin;
76 /*static*/
77 bool ScrollbarDrawing::IsScrollbarWidthThin(nsIFrame* aFrame) {
78 ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
79 return IsScrollbarWidthThin(*style);
82 CSSIntCoord ScrollbarDrawing::GetCSSScrollbarSize(StyleScrollbarWidth aWidth,
83 Overlay aOverlay) const {
84 return mScrollbarSize[aWidth == StyleScrollbarWidth::Thin]
85 [aOverlay == Overlay::Yes];
88 void ScrollbarDrawing::ConfigureScrollbarSize(StyleScrollbarWidth aWidth,
89 Overlay aOverlay,
90 CSSIntCoord aSize) {
91 mScrollbarSize[aWidth == StyleScrollbarWidth::Thin]
92 [aOverlay == Overlay::Yes] = aSize;
95 void ScrollbarDrawing::ConfigureScrollbarSize(CSSIntCoord aSize) {
96 ConfigureScrollbarSize(StyleScrollbarWidth::Auto, Overlay::No, aSize);
97 ConfigureScrollbarSize(StyleScrollbarWidth::Auto, Overlay::Yes, aSize);
98 ConfigureScrollbarSize(StyleScrollbarWidth::Thin, Overlay::No, aSize / 2);
99 ConfigureScrollbarSize(StyleScrollbarWidth::Thin, Overlay::Yes, aSize / 2);
102 LayoutDeviceIntCoord ScrollbarDrawing::GetScrollbarSize(
103 const nsPresContext* aPresContext, StyleScrollbarWidth aWidth,
104 Overlay aOverlay) {
105 return (CSSCoord(GetCSSScrollbarSize(aWidth, aOverlay)) *
106 GetDPIRatioForScrollbarPart(aPresContext))
107 .Rounded();
110 LayoutDeviceIntCoord ScrollbarDrawing::GetScrollbarSize(
111 const nsPresContext* aPresContext, nsIFrame* aFrame) {
112 auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
113 auto width = style->StyleUIReset()->ScrollbarWidth();
114 auto overlay =
115 aPresContext->UseOverlayScrollbars() ? Overlay::Yes : Overlay::No;
116 return GetScrollbarSize(aPresContext, width, overlay);
119 bool ScrollbarDrawing::IsScrollbarTrackOpaque(nsIFrame* aFrame) {
120 auto trackColor = ComputeScrollbarTrackColor(
121 aFrame, *nsLayoutUtils::StyleForScrollbar(aFrame),
122 aFrame->PresContext()->Document()->State(),
123 Colors(aFrame, StyleAppearance::ScrollbarVertical));
124 return trackColor.a == 1.0f;
127 sRGBColor ScrollbarDrawing::ComputeScrollbarTrackColor(
128 nsIFrame* aFrame, const ComputedStyle& aStyle,
129 const DocumentState& aDocumentState, const Colors& aColors) {
130 if (aColors.HighContrast()) {
131 return aColors.System(StyleSystemColor::Window);
133 const nsStyleUI* ui = aStyle.StyleUI();
134 if (ui->mScrollbarColor.IsColors()) {
135 return sRGBColor::FromABGR(
136 ui->mScrollbarColor.AsColors().track.CalcColor(aStyle));
138 static constexpr sRGBColor sDefaultDarkTrackColor =
139 sRGBColor::FromU8(20, 20, 25, 77);
140 static constexpr sRGBColor sDefaultTrackColor(
141 gfx::sRGBColor::UnusualFromARGB(0xfff0f0f0));
143 auto systemColor = aDocumentState.HasAllStates(DocumentState::WINDOW_INACTIVE)
144 ? StyleSystemColor::ThemedScrollbarInactive
145 : StyleSystemColor::ThemedScrollbar;
146 return aColors.SystemOrElse(systemColor, [&] {
147 return aColors.IsDark() ? sDefaultDarkTrackColor : sDefaultTrackColor;
151 // Don't use the theme color for dark scrollbars if it's not a color (if it's
152 // grey-ish), as that'd either lack enough contrast, or be close to what we'd do
153 // by default anyways.
154 sRGBColor ScrollbarDrawing::ComputeScrollbarThumbColor(
155 nsIFrame* aFrame, const ComputedStyle& aStyle,
156 const ElementState& aElementState, const DocumentState& aDocumentState,
157 const Colors& aColors) {
158 const nsStyleUI* ui = aStyle.StyleUI();
159 if (ui->mScrollbarColor.IsColors()) {
160 return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
161 ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle), aElementState));
164 auto systemColor = [&] {
165 if (aDocumentState.HasState(DocumentState::WINDOW_INACTIVE)) {
166 return StyleSystemColor::ThemedScrollbarThumbInactive;
168 if (aElementState.HasState(ElementState::ACTIVE)) {
169 if (aColors.HighContrast()) {
170 return StyleSystemColor::Selecteditem;
172 return StyleSystemColor::ThemedScrollbarThumbActive;
174 if (aElementState.HasState(ElementState::HOVER)) {
175 if (aColors.HighContrast()) {
176 return StyleSystemColor::Selecteditem;
178 return StyleSystemColor::ThemedScrollbarThumbHover;
180 if (aColors.HighContrast()) {
181 return StyleSystemColor::Windowtext;
183 return StyleSystemColor::ThemedScrollbarThumb;
184 }();
186 return aColors.SystemOrElse(systemColor, [&] {
187 const nscolor unthemedColor = aColors.IsDark() ? NS_RGBA(249, 249, 250, 102)
188 : NS_RGB(0xcd, 0xcd, 0xcd);
190 return sRGBColor::FromABGR(ThemeColors::AdjustUnthemedScrollbarThumbColor(
191 unthemedColor, aElementState));
195 template <typename PaintBackendData>
196 bool ScrollbarDrawing::DoPaintDefaultScrollbar(
197 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
198 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
199 const ElementState& aElementState, const DocumentState& aDocumentState,
200 const Colors& aColors, const DPIRatio& aDpiRatio) {
201 const bool overlay = aFrame->PresContext()->UseOverlayScrollbars();
202 if (overlay && !aElementState.HasAtLeastOneOfStates(ElementState::HOVER |
203 ElementState::ACTIVE)) {
204 return true;
206 const auto color =
207 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
208 if (overlay && mKind == Kind::Win11 &&
209 StaticPrefs::widget_non_native_theme_win11_scrollbar_round_track()) {
210 LayoutDeviceCoord radius =
211 (aScrollbarKind == ScrollbarKind::Horizontal ? aRect.height
212 : aRect.width) /
213 2.0f;
214 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, color,
215 sRGBColor(), 0, radius / aDpiRatio,
216 aDpiRatio);
217 } else {
218 ThemeDrawing::FillRect(aPaintData, aRect, color);
220 return true;
223 bool ScrollbarDrawing::PaintScrollbar(
224 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
225 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
226 const ElementState& aElementState, const DocumentState& aDocumentState,
227 const Colors& aColors, const DPIRatio& aDpiRatio) {
228 return DoPaintDefaultScrollbar(aDrawTarget, aRect, aScrollbarKind, aFrame,
229 aStyle, aElementState, aDocumentState, aColors,
230 aDpiRatio);
233 bool ScrollbarDrawing::PaintScrollbar(
234 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
235 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
236 const ElementState& aElementState, const DocumentState& aDocumentState,
237 const Colors& aColors, const DPIRatio& aDpiRatio) {
238 return DoPaintDefaultScrollbar(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
239 aElementState, aDocumentState, aColors,
240 aDpiRatio);
243 template <typename PaintBackendData>
244 bool ScrollbarDrawing::DoPaintDefaultScrollCorner(
245 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
246 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
247 const DocumentState& aDocumentState, const Colors& aColors,
248 const DPIRatio& aDpiRatio) {
249 auto scrollbarColor =
250 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
251 ThemeDrawing::FillRect(aPaintData, aRect, scrollbarColor);
252 return true;
255 bool ScrollbarDrawing::PaintScrollCorner(
256 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
257 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
258 const DocumentState& aDocumentState, const Colors& aColors,
259 const DPIRatio& aDpiRatio) {
260 return DoPaintDefaultScrollCorner(aDrawTarget, aRect, aScrollbarKind, aFrame,
261 aStyle, aDocumentState, aColors, aDpiRatio);
264 bool ScrollbarDrawing::PaintScrollCorner(
265 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
266 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
267 const DocumentState& aDocumentState, const Colors& aColors,
268 const DPIRatio& aDpiRatio) {
269 return DoPaintDefaultScrollCorner(aWrData, aRect, aScrollbarKind, aFrame,
270 aStyle, aDocumentState, aColors, aDpiRatio);
273 nscolor ScrollbarDrawing::GetScrollbarButtonColor(nscolor aTrackColor,
274 ElementState aStates) {
275 // See numbers in GetScrollbarArrowColor.
276 // This function is written based on ratios between values listed there.
278 bool isActive = aStates.HasState(ElementState::ACTIVE);
279 bool isHover = aStates.HasState(ElementState::HOVER);
280 if (!isActive && !isHover) {
281 return aTrackColor;
283 float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
284 if (isActive) {
285 if (luminance >= 0.18f) {
286 luminance *= 0.134f;
287 } else {
288 luminance /= 0.134f;
289 luminance = std::min(luminance, 1.0f);
291 } else {
292 if (luminance >= 0.18f) {
293 luminance *= 0.805f;
294 } else {
295 luminance /= 0.805f;
298 return RelativeLuminanceUtils::Adjust(aTrackColor, luminance);
301 Maybe<nscolor> ScrollbarDrawing::GetScrollbarArrowColor(nscolor aButtonColor) {
302 // In Windows 10 scrollbar, there are several gray colors used:
304 // State | Background (lum) | Arrow | Contrast
305 // -------+------------------+---------+---------
306 // Normal | Gray 240 (87.1%) | Gray 96 | 5.5
307 // Hover | Gray 218 (70.1%) | Black | 15.0
308 // Active | Gray 96 (11.7%) | White | 6.3
310 // Contrast value is computed based on the definition in
311 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef
313 // This function is written based on these values.
315 if (NS_GET_A(aButtonColor) == 0) {
316 // If the button color is transparent, because of e.g.
317 // scrollbar-color: <something> transparent, then use
318 // the thumb color, which is expected to have enough
319 // contrast.
320 return Nothing();
323 float luminance = RelativeLuminanceUtils::Compute(aButtonColor);
324 // Color with luminance larger than 0.72 has contrast ratio over 4.6
325 // to color with luminance of gray 96, so this value is chosen for
326 // this range. It is the luminance of gray 221.
327 if (luminance >= 0.72) {
328 // ComputeRelativeLuminanceFromComponents(96). That function cannot
329 // be constexpr because of std::pow.
330 const float GRAY96_LUMINANCE = 0.117f;
331 return Some(RelativeLuminanceUtils::Adjust(aButtonColor, GRAY96_LUMINANCE));
333 // The contrast ratio of a color to black equals that to white when its
334 // luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
335 // thus the value below. It's the lumanince of gray 118.
337 // TODO(emilio): Maybe the button alpha is not the best thing to use here and
338 // we should use the thumb alpha? It seems weird that the color of the arrow
339 // depends on the opacity of the scrollbar thumb...
340 if (luminance >= 0.18) {
341 return Some(NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor)));
343 return Some(NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor)));
346 std::pair<sRGBColor, sRGBColor> ScrollbarDrawing::ComputeScrollbarButtonColors(
347 nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
348 const ElementState& aElementState, const DocumentState& aDocumentState,
349 const Colors& aColors) {
350 if (aColors.HighContrast()) {
351 if (aElementState.HasAtLeastOneOfStates(ElementState::ACTIVE |
352 ElementState::HOVER)) {
353 return aColors.SystemPair(StyleSystemColor::Selecteditem,
354 StyleSystemColor::Buttonface);
356 return aColors.SystemPair(StyleSystemColor::Window,
357 StyleSystemColor::Windowtext);
360 auto trackColor =
361 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
362 nscolor buttonColor =
363 GetScrollbarButtonColor(trackColor.ToABGR(), aElementState);
364 auto arrowColor =
365 GetScrollbarArrowColor(buttonColor)
366 .map(sRGBColor::FromABGR)
367 .valueOrFrom([&] {
368 return ComputeScrollbarThumbColor(aFrame, aStyle, aElementState,
369 aDocumentState, aColors);
371 return {sRGBColor::FromABGR(buttonColor), arrowColor};
374 bool ScrollbarDrawing::PaintScrollbarButton(
375 DrawTarget& aDrawTarget, StyleAppearance aAppearance,
376 const LayoutDeviceRect& aRect, ScrollbarKind aScrollbarKind,
377 nsIFrame* aFrame, const ComputedStyle& aStyle,
378 const ElementState& aElementState, const DocumentState& aDocumentState,
379 const Colors& aColors, const DPIRatio&) {
380 auto [buttonColor, arrowColor] = ComputeScrollbarButtonColors(
381 aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors);
382 aDrawTarget.FillRect(aRect.ToUnknownRect(),
383 ColorPattern(ToDeviceColor(buttonColor)));
385 // Start with Up arrow.
386 float arrowPolygonX[] = {-4.0f, 0.0f, 4.0f, 4.0f, 0.0f, -4.0f};
387 float arrowPolygonY[] = {0.0f, -4.0f, 0.0f, 3.0f, -1.0f, 3.0f};
389 const float kPolygonSize = 17;
391 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
392 switch (aAppearance) {
393 case StyleAppearance::ScrollbarbuttonUp:
394 break;
395 case StyleAppearance::ScrollbarbuttonDown:
396 for (int32_t i = 0; i < arrowNumPoints; i++) {
397 arrowPolygonY[i] *= -1;
399 break;
400 case StyleAppearance::ScrollbarbuttonLeft:
401 for (int32_t i = 0; i < arrowNumPoints; i++) {
402 float temp = arrowPolygonX[i];
403 arrowPolygonX[i] = arrowPolygonY[i];
404 arrowPolygonY[i] = temp;
406 break;
407 case StyleAppearance::ScrollbarbuttonRight:
408 for (int32_t i = 0; i < arrowNumPoints; i++) {
409 float temp = arrowPolygonX[i];
410 arrowPolygonX[i] = arrowPolygonY[i] * -1;
411 arrowPolygonY[i] = temp;
413 break;
414 default:
415 return false;
417 ThemeDrawing::PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY,
418 kPolygonSize, arrowNumPoints, arrowColor);
419 return true;
422 } // namespace mozilla::widget