Backed out 2 changesets (bug 1908320) for causing wr failures on align-items-baseline...
[gecko.git] / widget / ScrollbarDrawing.cpp
blob34ab841920d65e632d63257cf36eb4380eab1234
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 "nsLookAndFeel.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 nsIFrame* ScrollbarDrawing::GetParentScrollbarFrame(nsIFrame* aFrame) {
41 // Walk our parents to find a scrollbar frame
42 nsIFrame* scrollbarFrame = aFrame;
43 do {
44 if (scrollbarFrame->IsScrollbarFrame()) {
45 break;
47 } while ((scrollbarFrame = scrollbarFrame->GetParent()));
49 // We return null if we can't find a parent scrollbar frame
50 return scrollbarFrame;
53 /*static*/
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);
63 /*static*/
64 bool ScrollbarDrawing::IsParentScrollbarHoveredOrActive(nsIFrame* aFrame) {
65 nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
66 return scrollbarFrame &&
67 scrollbarFrame->GetContent()
68 ->AsElement()
69 ->State()
70 .HasAtLeastOneOfStates(ElementState::HOVER | ElementState::ACTIVE);
73 /*static*/
74 bool ScrollbarDrawing::IsScrollbarWidthThin(const ComputedStyle& aStyle) {
75 auto scrollbarWidth = aStyle.StyleUIReset()->ScrollbarWidth();
76 return scrollbarWidth == StyleScrollbarWidth::Thin;
79 /*static*/
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,
92 Overlay aOverlay,
93 CSSIntCoord aSize) {
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,
107 Overlay aOverlay) {
108 return (CSSCoord(GetCSSScrollbarSize(aWidth, aOverlay)) *
109 GetDPIRatioForScrollbarPart(aPresContext))
110 .Rounded();
113 LayoutDeviceIntCoord ScrollbarDrawing::GetScrollbarSize(
114 const nsPresContext* aPresContext, nsIFrame* aFrame) {
115 auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
116 auto width = style->StyleUIReset()->ScrollbarWidth();
117 auto overlay =
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()->State(),
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;
187 }();
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)) {
207 return true;
209 const auto color =
210 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
211 if (overlay && mKind == Kind::Win11 &&
212 StaticPrefs::widget_non_native_theme_win11_scrollbar_round_track()) {
213 LayoutDeviceCoord radius =
214 (aScrollbarKind == ScrollbarKind::Horizontal ? aRect.height
215 : aRect.width) /
216 2.0f;
217 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, color,
218 sRGBColor(), 0, radius / aDpiRatio,
219 aDpiRatio);
220 } else {
221 ThemeDrawing::FillRect(aPaintData, aRect, color);
223 return true;
226 bool ScrollbarDrawing::PaintScrollbar(
227 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
228 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
229 const ElementState& aElementState, const DocumentState& aDocumentState,
230 const Colors& aColors, const DPIRatio& aDpiRatio) {
231 return DoPaintDefaultScrollbar(aDrawTarget, aRect, aScrollbarKind, aFrame,
232 aStyle, aElementState, aDocumentState, aColors,
233 aDpiRatio);
236 bool ScrollbarDrawing::PaintScrollbar(
237 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
238 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
239 const ElementState& aElementState, const DocumentState& aDocumentState,
240 const Colors& aColors, const DPIRatio& aDpiRatio) {
241 return DoPaintDefaultScrollbar(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
242 aElementState, aDocumentState, aColors,
243 aDpiRatio);
246 template <typename PaintBackendData>
247 bool ScrollbarDrawing::DoPaintDefaultScrollCorner(
248 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
249 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
250 const DocumentState& aDocumentState, const Colors& aColors,
251 const DPIRatio& aDpiRatio) {
252 auto scrollbarColor =
253 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
254 ThemeDrawing::FillRect(aPaintData, aRect, scrollbarColor);
255 return true;
258 bool ScrollbarDrawing::PaintScrollCorner(
259 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
260 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
261 const DocumentState& aDocumentState, const Colors& aColors,
262 const DPIRatio& aDpiRatio) {
263 return DoPaintDefaultScrollCorner(aDrawTarget, aRect, aScrollbarKind, aFrame,
264 aStyle, aDocumentState, aColors, aDpiRatio);
267 bool ScrollbarDrawing::PaintScrollCorner(
268 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
269 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
270 const DocumentState& aDocumentState, const Colors& aColors,
271 const DPIRatio& aDpiRatio) {
272 return DoPaintDefaultScrollCorner(aWrData, aRect, aScrollbarKind, aFrame,
273 aStyle, aDocumentState, aColors, aDpiRatio);
276 nscolor ScrollbarDrawing::GetScrollbarButtonColor(nscolor aTrackColor,
277 ElementState aStates) {
278 // See numbers in GetScrollbarArrowColor.
279 // This function is written based on ratios between values listed there.
281 bool isActive = aStates.HasState(ElementState::ACTIVE);
282 bool isHover = aStates.HasState(ElementState::HOVER);
283 if (!isActive && !isHover) {
284 return aTrackColor;
286 float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
287 if (isActive) {
288 if (luminance >= 0.18f) {
289 luminance *= 0.134f;
290 } else {
291 luminance /= 0.134f;
292 luminance = std::min(luminance, 1.0f);
294 } else {
295 if (luminance >= 0.18f) {
296 luminance *= 0.805f;
297 } else {
298 luminance /= 0.805f;
301 return RelativeLuminanceUtils::Adjust(aTrackColor, luminance);
304 Maybe<nscolor> ScrollbarDrawing::GetScrollbarArrowColor(nscolor aButtonColor) {
305 // In Windows 10 scrollbar, there are several gray colors used:
307 // State | Background (lum) | Arrow | Contrast
308 // -------+------------------+---------+---------
309 // Normal | Gray 240 (87.1%) | Gray 96 | 5.5
310 // Hover | Gray 218 (70.1%) | Black | 15.0
311 // Active | Gray 96 (11.7%) | White | 6.3
313 // Contrast value is computed based on the definition in
314 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef
316 // This function is written based on these values.
318 if (NS_GET_A(aButtonColor) == 0) {
319 // If the button color is transparent, because of e.g.
320 // scrollbar-color: <something> transparent, then use
321 // the thumb color, which is expected to have enough
322 // contrast.
323 return Nothing();
326 float luminance = RelativeLuminanceUtils::Compute(aButtonColor);
327 // Color with luminance larger than 0.72 has contrast ratio over 4.6
328 // to color with luminance of gray 96, so this value is chosen for
329 // this range. It is the luminance of gray 221.
330 if (luminance >= 0.72) {
331 // ComputeRelativeLuminanceFromComponents(96). That function cannot
332 // be constexpr because of std::pow.
333 const float GRAY96_LUMINANCE = 0.117f;
334 return Some(RelativeLuminanceUtils::Adjust(aButtonColor, GRAY96_LUMINANCE));
336 // The contrast ratio of a color to black equals that to white when its
337 // luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
338 // thus the value below. It's the lumanince of gray 118.
340 // TODO(emilio): Maybe the button alpha is not the best thing to use here and
341 // we should use the thumb alpha? It seems weird that the color of the arrow
342 // depends on the opacity of the scrollbar thumb...
343 if (luminance >= 0.18) {
344 return Some(NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor)));
346 return Some(NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor)));
349 std::pair<sRGBColor, sRGBColor> ScrollbarDrawing::ComputeScrollbarButtonColors(
350 nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
351 const ElementState& aElementState, const DocumentState& aDocumentState,
352 const Colors& aColors) {
353 if (aColors.HighContrast()) {
354 if (aElementState.HasAtLeastOneOfStates(ElementState::ACTIVE |
355 ElementState::HOVER)) {
356 return aColors.SystemPair(StyleSystemColor::Selecteditem,
357 StyleSystemColor::Buttonface);
359 return aColors.SystemPair(StyleSystemColor::Window,
360 StyleSystemColor::Windowtext);
363 auto trackColor =
364 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
365 nscolor buttonColor =
366 GetScrollbarButtonColor(trackColor.ToABGR(), aElementState);
367 auto arrowColor =
368 GetScrollbarArrowColor(buttonColor)
369 .map(sRGBColor::FromABGR)
370 .valueOrFrom([&] {
371 return ComputeScrollbarThumbColor(aFrame, aStyle, aElementState,
372 aDocumentState, aColors);
374 return {sRGBColor::FromABGR(buttonColor), arrowColor};
377 bool ScrollbarDrawing::PaintScrollbarButton(
378 DrawTarget& aDrawTarget, StyleAppearance aAppearance,
379 const LayoutDeviceRect& aRect, ScrollbarKind aScrollbarKind,
380 nsIFrame* aFrame, const ComputedStyle& aStyle,
381 const ElementState& aElementState, const DocumentState& aDocumentState,
382 const Colors& aColors, const DPIRatio&) {
383 auto [buttonColor, arrowColor] = ComputeScrollbarButtonColors(
384 aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors);
385 aDrawTarget.FillRect(aRect.ToUnknownRect(),
386 ColorPattern(ToDeviceColor(buttonColor)));
388 // Start with Up arrow.
389 float arrowPolygonX[] = {-4.0f, 0.0f, 4.0f, 4.0f, 0.0f, -4.0f};
390 float arrowPolygonY[] = {0.0f, -4.0f, 0.0f, 3.0f, -1.0f, 3.0f};
392 const float kPolygonSize = 17;
394 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
395 switch (aAppearance) {
396 case StyleAppearance::ScrollbarbuttonUp:
397 break;
398 case StyleAppearance::ScrollbarbuttonDown:
399 for (int32_t i = 0; i < arrowNumPoints; i++) {
400 arrowPolygonY[i] *= -1;
402 break;
403 case StyleAppearance::ScrollbarbuttonLeft:
404 for (int32_t i = 0; i < arrowNumPoints; i++) {
405 float temp = arrowPolygonX[i];
406 arrowPolygonX[i] = arrowPolygonY[i];
407 arrowPolygonY[i] = temp;
409 break;
410 case StyleAppearance::ScrollbarbuttonRight:
411 for (int32_t i = 0; i < arrowNumPoints; i++) {
412 float temp = arrowPolygonX[i];
413 arrowPolygonX[i] = arrowPolygonY[i] * -1;
414 arrowPolygonY[i] = temp;
416 break;
417 default:
418 return false;
420 ThemeDrawing::PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY,
421 kPolygonSize, arrowNumPoints, arrowColor);
422 return true;
425 } // namespace mozilla::widget