Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / widget / ScrollbarDrawingCocoa.cpp
blob7d8b548d258ea91254006468a4511781238f5ae2
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 "ScrollbarDrawingCocoa.h"
9 #include "mozilla/RelativeLuminanceUtils.h"
10 #include "nsIFrame.h"
11 #include "nsLayoutUtils.h"
12 #include "nsNativeTheme.h"
14 using namespace mozilla::gfx;
15 namespace mozilla::widget {
17 using ScrollbarKind = ScrollbarDrawing::ScrollbarKind;
19 struct ColoredRect {
20 LayoutDeviceRect mRect;
21 nscolor mColor = 0;
24 // The caller can draw this rectangle with rounded corners as appropriate.
25 struct ThumbRect {
26 LayoutDeviceRect mRect;
27 nscolor mFillColor = 0;
28 nscolor mStrokeColor = 0;
29 float mStrokeWidth = 0.0f;
30 float mStrokeOutset = 0.0f;
33 using ScrollbarTrackRects = Array<ColoredRect, 4>;
34 using ScrollCornerRects = Array<ColoredRect, 7>;
36 struct ScrollbarParams {
37 bool isOverlay = false;
38 bool isRolledOver = false;
39 bool isSmall = false;
40 bool isHorizontal = false;
41 bool isRtl = false;
42 bool isDark = false;
43 bool isCustom = false;
44 // Two colors only used when custom is true.
45 nscolor trackColor = NS_RGBA(0, 0, 0, 0);
46 nscolor faceColor = NS_RGBA(0, 0, 0, 0);
49 static ScrollbarParams ComputeScrollbarParams(nsIFrame* aFrame,
50 const ComputedStyle& aStyle,
51 const ThemeColors& aColors,
52 ScrollbarKind aScrollbarKind) {
53 ScrollbarParams params;
54 params.isOverlay = aFrame->PresContext()->UseOverlayScrollbars();
55 params.isRolledOver = ScrollbarDrawing::IsParentScrollbarRolledOver(aFrame);
56 params.isSmall =
57 aStyle.StyleUIReset()->ScrollbarWidth() == StyleScrollbarWidth::Thin;
58 params.isRtl = aScrollbarKind == ScrollbarKind::VerticalLeft;
59 params.isHorizontal = aScrollbarKind == ScrollbarKind::Horizontal;
60 params.isDark = aColors.IsDark();
62 const nsStyleUI* ui = aStyle.StyleUI();
63 if (ui->HasCustomScrollbars()) {
64 const auto& colors = ui->mScrollbarColor.AsColors();
65 params.isCustom = true;
66 params.trackColor = colors.track.CalcColor(aStyle);
67 params.faceColor = colors.thumb.CalcColor(aStyle);
70 return params;
73 LayoutDeviceIntSize ScrollbarDrawingCocoa::GetMinimumWidgetSize(
74 nsPresContext* aPresContext, StyleAppearance aAppearance,
75 nsIFrame* aFrame) {
76 MOZ_ASSERT(nsNativeTheme::IsWidgetScrollbarPart(aAppearance));
78 auto minSize = [&]() -> CSSIntSize {
79 switch (aAppearance) {
80 case StyleAppearance::ScrollbarthumbHorizontal:
81 return {26, 0};
82 case StyleAppearance::ScrollbarthumbVertical:
83 return {0, 26};
84 case StyleAppearance::ScrollbarVertical:
85 case StyleAppearance::ScrollbarHorizontal: {
86 ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
87 auto scrollbarWidth = style->StyleUIReset()->ScrollbarWidth();
88 auto size = GetCSSScrollbarSize(
89 scrollbarWidth, Overlay(aPresContext->UseOverlayScrollbars()));
90 return {size, size};
92 case StyleAppearance::ScrollbarbuttonUp:
93 case StyleAppearance::ScrollbarbuttonDown:
94 return {15, 16};
95 case StyleAppearance::ScrollbarbuttonLeft:
96 case StyleAppearance::ScrollbarbuttonRight:
97 return {16, 15};
98 default:
99 return {};
101 }();
103 auto dpi = GetDPIRatioForScrollbarPart(aPresContext);
104 return LayoutDeviceIntSize::Round(CSSSize(minSize) * dpi);
107 static ThumbRect GetThumbRect(const LayoutDeviceRect& aRect,
108 const ScrollbarParams& aParams, float aScale) {
109 // Compute the thumb thickness. This varies based on aParams.isSmall,
110 // aParams.isOverlay and aParams.isRolledOver.
111 // non-overlay: 6 / 8, overlay non-hovered: 5 / 7, overlay hovered: 7 / 11
112 // Note that this is drawn inside the rect of a size as specified by
113 // ConfigureScrollbarSize().
114 float thickness = aParams.isSmall ? 6.0f : 8.0f;
115 if (aParams.isOverlay) {
116 thickness -= 1.0f;
117 if (aParams.isRolledOver) {
118 thickness = aParams.isSmall ? 7.0f : 11.0f;
122 thickness *= aScale;
124 // Compute the thumb rect.
125 const float outerSpacing =
126 ((aParams.isOverlay || aParams.isSmall) ? 1.0f : 2.0f) * aScale;
127 LayoutDeviceRect thumbRect = aRect;
128 thumbRect.Deflate(1.0f * aScale);
129 if (aParams.isHorizontal) {
130 float bottomEdge = thumbRect.YMost() - outerSpacing;
131 thumbRect.SetBoxY(bottomEdge - thickness, bottomEdge);
132 } else {
133 if (aParams.isRtl) {
134 float leftEdge = thumbRect.X() + outerSpacing;
135 thumbRect.SetBoxX(leftEdge, leftEdge + thickness);
136 } else {
137 float rightEdge = thumbRect.XMost() - outerSpacing;
138 thumbRect.SetBoxX(rightEdge - thickness, rightEdge);
142 // Compute the thumb fill color.
143 nscolor faceColor;
144 if (aParams.isCustom) {
145 faceColor = aParams.faceColor;
146 } else {
147 if (aParams.isOverlay) {
148 faceColor =
149 aParams.isDark ? NS_RGBA(255, 255, 255, 128) : NS_RGBA(0, 0, 0, 128);
150 } else if (aParams.isDark) {
151 faceColor = aParams.isRolledOver ? NS_RGBA(158, 158, 158, 255)
152 : NS_RGBA(117, 117, 117, 255);
153 } else {
154 faceColor = aParams.isRolledOver ? NS_RGBA(125, 125, 125, 255)
155 : NS_RGBA(194, 194, 194, 255);
159 nscolor strokeColor = 0;
160 float strokeOutset = 0.0f;
161 float strokeWidth = 0.0f;
163 // Overlay scrollbars have an additional stroke around the fill.
164 if (aParams.isOverlay) {
165 // For the default alpha of 128 we want to end up with 48 in the outline.
166 constexpr float kAlphaScaling = 48.0f / 128.0f;
167 const uint8_t strokeAlpha =
168 uint8_t(std::clamp(NS_GET_A(faceColor) * kAlphaScaling, 0.0f, 48.0f));
169 if (strokeAlpha) {
170 strokeOutset = (aParams.isDark ? 0.3f : 0.5f) * aScale;
171 strokeWidth = (aParams.isDark ? 0.6f : 0.8f) * aScale;
173 strokeColor = aParams.isDark ? NS_RGBA(0, 0, 0, strokeAlpha)
174 : NS_RGBA(255, 255, 255, strokeAlpha);
178 return {thumbRect, faceColor, strokeColor, strokeWidth, strokeOutset};
181 struct ScrollbarTrackDecorationColors {
182 nscolor mInnerColor = 0;
183 nscolor mShadowColor = 0;
184 nscolor mOuterColor = 0;
187 static ScrollbarTrackDecorationColors ComputeScrollbarTrackDecorationColors(
188 nscolor aTrackColor) {
189 ScrollbarTrackDecorationColors result;
190 float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
191 if (luminance >= 0.5f) {
192 result.mInnerColor =
193 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836f);
194 result.mShadowColor =
195 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982f);
196 result.mOuterColor =
197 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886f);
198 } else {
199 result.mInnerColor =
200 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196f);
201 result.mShadowColor =
202 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018f);
203 result.mOuterColor =
204 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129f);
206 return result;
209 static bool GetScrollbarTrackRects(const LayoutDeviceRect& aRect,
210 const ScrollbarParams& aParams, float aScale,
211 ScrollbarTrackRects& aRects) {
212 nscolor trackColor;
213 if (aParams.isCustom) {
214 trackColor = aParams.trackColor;
215 } else {
216 if (aParams.isOverlay) {
217 trackColor = aParams.isDark ? NS_RGBA(201, 201, 201, 38)
218 : NS_RGBA(250, 250, 250, 191);
219 } else {
220 trackColor = aParams.isDark ? NS_RGBA(46, 46, 46, 255)
221 : NS_RGBA(250, 250, 250, 255);
225 float thickness = aParams.isHorizontal ? aRect.height : aRect.width;
227 // The scrollbar track is drawn as multiple non-overlapping segments, which
228 // make up lines of different widths and with slightly different shading.
229 ScrollbarTrackDecorationColors colors =
230 ComputeScrollbarTrackDecorationColors(trackColor);
231 struct {
232 nscolor color;
233 float thickness;
234 } segments[] = {
235 {colors.mInnerColor, 1.0f * aScale},
236 {colors.mShadowColor, 1.0f * aScale},
237 {trackColor, thickness - 3.0f * aScale},
238 {colors.mOuterColor, 1.0f * aScale},
241 // Iterate over the segments "from inside to outside" and fill each segment.
242 // For horizontal scrollbars, iterate top to bottom.
243 // For vertical scrollbars, iterate left to right or right to left based on
244 // aParams.isRtl.
245 auto current = aRects.begin();
246 float accumulatedThickness = 0.0f;
247 for (const auto& segment : segments) {
248 LayoutDeviceRect segmentRect = aRect;
249 float startThickness = accumulatedThickness;
250 float endThickness = startThickness + segment.thickness;
251 if (aParams.isHorizontal) {
252 segmentRect.SetBoxY(aRect.Y() + startThickness, aRect.Y() + endThickness);
253 } else {
254 if (aParams.isRtl) {
255 segmentRect.SetBoxX(aRect.XMost() - endThickness,
256 aRect.XMost() - startThickness);
257 } else {
258 segmentRect.SetBoxX(aRect.X() + startThickness,
259 aRect.X() + endThickness);
262 accumulatedThickness = endThickness;
263 *current++ = {segmentRect, segment.color};
266 return true;
269 static bool GetScrollCornerRects(const LayoutDeviceRect& aRect,
270 const ScrollbarParams& aParams, float aScale,
271 ScrollCornerRects& aRects) {
272 if (aParams.isOverlay && !aParams.isRolledOver) {
273 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
274 return false;
277 // Draw the following scroll corner.
279 // Output: Rectangles:
280 // +---+---+----------+---+ +---+---+----------+---+
281 // | I | S | T ... T | O | | I | S | T ... T | O |
282 // +---+ | | | +---+---+ | |
283 // | S S | T ... T | | | S S | T ... T | . |
284 // +-------+ | . | +-------+----------+ . |
285 // | T ... T | . | | T ... T | . |
286 // | . . | . | | . . | |
287 // | T ... T | | | T ... T | O |
288 // +------------------+ | +------------------+---+
289 // | O ... O | | O ... O |
290 // +----------------------+ +----------------------+
292 float width = aRect.width;
293 float height = aRect.height;
294 nscolor trackColor;
295 if (aParams.isCustom) {
296 trackColor = aParams.trackColor;
297 } else {
298 trackColor =
299 aParams.isDark ? NS_RGBA(46, 46, 46, 255) : NS_RGBA(250, 250, 250, 255);
301 ScrollbarTrackDecorationColors colors =
302 ComputeScrollbarTrackDecorationColors(trackColor);
303 struct {
304 nscolor color;
305 LayoutDeviceRect relativeRect;
306 } pieces[] = {
307 {colors.mInnerColor, {0.0f, 0.0f, 1.0f * aScale, 1.0f * aScale}},
308 {colors.mShadowColor,
309 {1.0f * aScale, 0.0f, 1.0f * aScale, 1.0f * aScale}},
310 {colors.mShadowColor,
311 {0.0f, 1.0f * aScale, 2.0f * aScale, 1.0f * aScale}},
312 {trackColor, {2.0f * aScale, 0.0f, width - 3.0f * aScale, 2.0f * aScale}},
313 {trackColor,
314 {0.0f, 2.0f * aScale, width - 1.0f * aScale, height - 3.0f * aScale}},
315 {colors.mOuterColor,
316 {width - 1.0f * aScale, 0.0f, 1.0f * aScale, height - 1.0f * aScale}},
317 {colors.mOuterColor,
318 {0.0f, height - 1.0f * aScale, width, 1.0f * aScale}},
321 auto current = aRects.begin();
322 for (const auto& piece : pieces) {
323 LayoutDeviceRect pieceRect = piece.relativeRect + aRect.TopLeft();
324 if (aParams.isRtl) {
325 pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
327 *current++ = {pieceRect, piece.color};
329 return true;
332 template <typename PaintBackendData>
333 void ScrollbarDrawingCocoa::DoPaintScrollbarThumb(
334 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
335 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
336 const ElementState& aElementState, const DocumentState& aDocumentState,
337 const Colors& aColors, const DPIRatio& aDpiRatio) {
338 ScrollbarParams params =
339 ComputeScrollbarParams(aFrame, aStyle, aColors, aScrollbarKind);
340 auto thumb = GetThumbRect(aRect, params, aDpiRatio.scale);
341 LayoutDeviceCoord radius =
342 (params.isHorizontal ? thumb.mRect.Height() : thumb.mRect.Width()) / 2.0f;
343 ThemeDrawing::PaintRoundedRectWithRadius(
344 aPaintData, thumb.mRect, thumb.mRect,
345 sRGBColor::FromABGR(thumb.mFillColor), sRGBColor::White(0.0f), 0.0f,
346 radius / aDpiRatio, aDpiRatio);
347 if (!thumb.mStrokeColor) {
348 return;
351 // Paint the stroke if needed.
352 auto strokeRect = thumb.mRect;
353 strokeRect.Inflate(thumb.mStrokeOutset + thumb.mStrokeWidth);
354 radius =
355 (params.isHorizontal ? strokeRect.Height() : strokeRect.Width()) / 2.0f;
356 ThemeDrawing::PaintRoundedRectWithRadius(
357 aPaintData, strokeRect, sRGBColor::White(0.0f),
358 sRGBColor::FromABGR(thumb.mStrokeColor), thumb.mStrokeWidth,
359 radius / aDpiRatio, aDpiRatio);
362 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
363 DrawTarget& aDt, const LayoutDeviceRect& aRect,
364 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
365 const ElementState& aElementState, const DocumentState& aDocumentState,
366 const Colors& aColors, const DPIRatio& aDpiRatio) {
367 DoPaintScrollbarThumb(aDt, aRect, aScrollbarKind, aFrame, aStyle,
368 aElementState, aDocumentState, aColors, aDpiRatio);
369 return true;
372 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
373 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
374 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
375 const ElementState& aElementState, const DocumentState& aDocumentState,
376 const Colors& aColors, const DPIRatio& aDpiRatio) {
377 DoPaintScrollbarThumb(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
378 aElementState, aDocumentState, aColors, aDpiRatio);
379 return true;
382 template <typename PaintBackendData>
383 void ScrollbarDrawingCocoa::DoPaintScrollbar(
384 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
385 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
386 const ElementState& aElementState, const DocumentState& aDocumentState,
387 const Colors& aColors, const DPIRatio& aDpiRatio) {
388 ScrollbarParams params =
389 ComputeScrollbarParams(aFrame, aStyle, aColors, aScrollbarKind);
390 if (params.isOverlay && !params.isRolledOver) {
391 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
392 return;
395 // Paint our track.
396 const auto color =
397 ComputeScrollbarTrackColor(aFrame, aStyle, aDocumentState, aColors);
398 ThemeDrawing::FillRect(aPaintData, aRect, color);
400 // Paint our decorations.
401 ScrollbarTrackRects rects;
402 GetScrollbarTrackRects(aRect, params, aDpiRatio.scale, rects);
403 for (const auto& rect : rects) {
404 ThemeDrawing::FillRect(aPaintData, rect.mRect,
405 sRGBColor::FromABGR(rect.mColor));
409 bool ScrollbarDrawingCocoa::PaintScrollbar(
410 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
411 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
412 const ElementState& aElementState, const DocumentState& aDocumentState,
413 const Colors& aColors, const DPIRatio& aDpiRatio) {
414 DoPaintScrollbar(aDrawTarget, aRect, aScrollbarKind, aFrame, aStyle,
415 aElementState, aDocumentState, aColors, aDpiRatio);
416 return true;
419 bool ScrollbarDrawingCocoa::PaintScrollbar(
420 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
421 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
422 const ElementState& aElementState, const DocumentState& aDocumentState,
423 const Colors& aColors, const DPIRatio& aDpiRatio) {
424 DoPaintScrollbar(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
425 aElementState, aDocumentState, aColors, aDpiRatio);
426 return true;
429 template <typename PaintBackendData>
430 void ScrollbarDrawingCocoa::DoPaintScrollCorner(
431 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
432 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
433 const DocumentState& aDocumentState, const Colors& aColors,
434 const DPIRatio& aDpiRatio) {
435 ScrollbarParams params =
436 ComputeScrollbarParams(aFrame, aStyle, aColors, aScrollbarKind);
437 ScrollCornerRects rects;
438 if (GetScrollCornerRects(aRect, params, aDpiRatio.scale, rects)) {
439 for (const auto& rect : rects) {
440 ThemeDrawing::FillRect(aPaintData, rect.mRect,
441 sRGBColor::FromABGR(rect.mColor));
446 bool ScrollbarDrawingCocoa::PaintScrollCorner(
447 DrawTarget& aDt, const LayoutDeviceRect& aRect,
448 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
449 const DocumentState& aDocumentState, const Colors& aColors,
450 const DPIRatio& aDpiRatio) {
451 DoPaintScrollCorner(aDt, aRect, aScrollbarKind, aFrame, aStyle,
452 aDocumentState, aColors, aDpiRatio);
453 return true;
456 bool ScrollbarDrawingCocoa::PaintScrollCorner(
457 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
458 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
459 const DocumentState& aDocumentState, const Colors& aColors,
460 const DPIRatio& aDpiRatio) {
461 DoPaintScrollCorner(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
462 aDocumentState, aColors, aDpiRatio);
463 return true;
466 void ScrollbarDrawingCocoa::RecomputeScrollbarParams() {
467 // FIXME(emilio): This doesn't respect the
468 // StaticPrefs::widget_non_native_theme_scrollbar_size_override() pref;
469 ConfigureScrollbarSize(15); // Just in case, for future-proofing
470 ConfigureScrollbarSize(StyleScrollbarWidth::Auto, Overlay::No, 15);
471 ConfigureScrollbarSize(StyleScrollbarWidth::Thin, Overlay::No, 11);
472 ConfigureScrollbarSize(StyleScrollbarWidth::Auto, Overlay::Yes, 16);
473 ConfigureScrollbarSize(StyleScrollbarWidth::Thin, Overlay::Yes, 12);
476 } // namespace mozilla::widget