Bug 1793579 [wpt PR 36253] - Set empty string for reflection of IDREF attributes...
[gecko.git] / widget / ScrollbarDrawingCocoa.cpp
blobc58b7509e54b300ef099cc4575a74224589d4346
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/gfx/Helpers.h"
10 #include "mozilla/RelativeLuminanceUtils.h"
11 #include "mozilla/StaticPrefs_widget.h"
12 #include "nsContainerFrame.h"
13 #include "nsAlgorithm.h"
14 #include "nsIFrame.h"
15 #include "nsLayoutUtils.h"
16 #include "nsLookAndFeel.h"
17 #include "nsNativeTheme.h"
19 using namespace mozilla::gfx;
20 namespace mozilla::widget {
22 using ScrollbarKind = ScrollbarDrawing::ScrollbarKind;
24 struct ColoredRect {
25 LayoutDeviceRect mRect;
26 nscolor mColor = 0;
29 // The caller can draw this rectangle with rounded corners as appropriate.
30 struct ThumbRect {
31 LayoutDeviceRect mRect;
32 nscolor mFillColor = 0;
33 nscolor mStrokeColor = 0;
34 float mStrokeWidth = 0.0f;
35 float mStrokeOutset = 0.0f;
38 using ScrollbarTrackRects = Array<ColoredRect, 4>;
39 using ScrollCornerRects = Array<ColoredRect, 7>;
41 struct ScrollbarParams {
42 bool isOverlay = false;
43 bool isRolledOver = false;
44 bool isSmall = false;
45 bool isHorizontal = false;
46 bool isRtl = false;
47 bool isDark = false;
48 bool isCustom = false;
49 // Two colors only used when custom is true.
50 nscolor trackColor = NS_RGBA(0, 0, 0, 0);
51 nscolor faceColor = NS_RGBA(0, 0, 0, 0);
54 static ScrollbarParams ComputeScrollbarParams(nsIFrame* aFrame,
55 const ComputedStyle& aStyle,
56 const ThemeColors& aColors,
57 ScrollbarKind aScrollbarKind) {
58 ScrollbarParams params;
59 params.isOverlay =
60 nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0;
61 params.isRolledOver = ScrollbarDrawing::IsParentScrollbarRolledOver(aFrame);
62 params.isSmall =
63 aStyle.StyleUIReset()->ScrollbarWidth() == StyleScrollbarWidth::Thin;
64 params.isRtl = aScrollbarKind == ScrollbarKind::VerticalLeft;
65 params.isHorizontal = aScrollbarKind == ScrollbarKind::Horizontal;
66 params.isDark = aColors.IsDark();
68 const nsStyleUI* ui = aStyle.StyleUI();
69 if (ui->HasCustomScrollbars()) {
70 const auto& colors = ui->mScrollbarColor.AsColors();
71 params.isCustom = true;
72 params.trackColor = colors.track.CalcColor(aStyle);
73 params.faceColor = colors.thumb.CalcColor(aStyle);
76 return params;
79 LayoutDeviceIntSize ScrollbarDrawingCocoa::GetMinimumWidgetSize(
80 nsPresContext* aPresContext, StyleAppearance aAppearance,
81 nsIFrame* aFrame) {
82 MOZ_ASSERT(nsNativeTheme::IsWidgetScrollbarPart(aAppearance));
84 auto minSize = [&] {
85 switch (aAppearance) {
86 case StyleAppearance::ScrollbarthumbHorizontal:
87 return IntSize{26, 0};
88 case StyleAppearance::ScrollbarthumbVertical:
89 return IntSize{0, 26};
90 case StyleAppearance::ScrollbarVertical:
91 case StyleAppearance::ScrollbarHorizontal:
92 case StyleAppearance::ScrollbartrackVertical:
93 case StyleAppearance::ScrollbartrackHorizontal: {
94 ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
95 auto scrollbarWidth = style->StyleUIReset()->ScrollbarWidth();
96 auto size = GetScrollbarSize(
97 scrollbarWidth,
98 LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars));
99 return IntSize{size, size};
101 case StyleAppearance::ScrollbarbuttonUp:
102 case StyleAppearance::ScrollbarbuttonDown:
103 return IntSize{15, 16};
104 case StyleAppearance::ScrollbarbuttonLeft:
105 case StyleAppearance::ScrollbarbuttonRight:
106 return IntSize{16, 15};
107 default:
108 return IntSize{};
110 }();
112 auto dpi = GetDPIRatioForScrollbarPart(aPresContext).scale;
113 if (dpi >= 2.0f) {
114 return LayoutDeviceIntSize{minSize.width * 2, minSize.height * 2};
116 return LayoutDeviceIntSize{minSize.width, minSize.height};
119 /*static*/
120 CSSIntCoord ScrollbarDrawingCocoa::GetScrollbarSize(StyleScrollbarWidth aWidth,
121 bool aOverlay) {
122 bool isSmall = aWidth == StyleScrollbarWidth::Thin;
123 if (aOverlay) {
124 return isSmall ? 14 : 16;
126 return isSmall ? 11 : 15;
129 /*static*/
130 LayoutDeviceIntCoord ScrollbarDrawingCocoa::GetScrollbarSize(
131 StyleScrollbarWidth aWidth, bool aOverlay, DPIRatio aDpiRatio) {
132 CSSIntCoord size = GetScrollbarSize(aWidth, aOverlay);
133 if (aDpiRatio.scale >= 2.0f) {
134 return int32_t(size) * 2;
136 return int32_t(size);
139 auto ScrollbarDrawingCocoa::GetScrollbarSizes(nsPresContext* aPresContext,
140 StyleScrollbarWidth aWidth,
141 Overlay aOverlay)
142 -> ScrollbarSizes {
143 auto size = GetScrollbarSize(aWidth, aOverlay == Overlay::Yes,
144 GetDPIRatioForScrollbarPart(aPresContext));
145 return {size, size};
148 static ThumbRect GetThumbRect(const LayoutDeviceRect& aRect,
149 const ScrollbarParams& aParams, float aScale) {
150 // This matches the sizing checks in GetMinimumWidgetSize etc.
151 aScale = aScale >= 2.0f ? 2.0f : 1.0f;
153 // Compute the thumb thickness. This varies based on aParams.small,
154 // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
155 // non-hovered: 5 / 7, overlay hovered: 9 / 11
156 float thickness = aParams.isSmall ? 6.0f : 8.0f;
157 if (aParams.isOverlay) {
158 thickness -= 1.0f;
159 if (aParams.isRolledOver) {
160 thickness += 4.0f;
163 thickness *= aScale;
165 // Compute the thumb rect.
166 const float outerSpacing =
167 ((aParams.isOverlay || aParams.isSmall) ? 1.0f : 2.0f) * aScale;
168 LayoutDeviceRect thumbRect = aRect;
169 thumbRect.Deflate(1.0f * aScale);
170 if (aParams.isHorizontal) {
171 float bottomEdge = thumbRect.YMost() - outerSpacing;
172 thumbRect.SetBoxY(bottomEdge - thickness, bottomEdge);
173 } else {
174 if (aParams.isRtl) {
175 float leftEdge = thumbRect.X() + outerSpacing;
176 thumbRect.SetBoxX(leftEdge, leftEdge + thickness);
177 } else {
178 float rightEdge = thumbRect.XMost() - outerSpacing;
179 thumbRect.SetBoxX(rightEdge - thickness, rightEdge);
183 // Compute the thumb fill color.
184 nscolor faceColor;
185 if (aParams.isCustom) {
186 faceColor = aParams.faceColor;
187 } else {
188 if (aParams.isOverlay) {
189 faceColor =
190 aParams.isDark ? NS_RGBA(255, 255, 255, 128) : NS_RGBA(0, 0, 0, 128);
191 } else if (aParams.isDark) {
192 faceColor = aParams.isRolledOver ? NS_RGBA(158, 158, 158, 255)
193 : NS_RGBA(117, 117, 117, 255);
194 } else {
195 faceColor = aParams.isRolledOver ? NS_RGBA(125, 125, 125, 255)
196 : NS_RGBA(194, 194, 194, 255);
200 nscolor strokeColor = 0;
201 float strokeOutset = 0.0f;
202 float strokeWidth = 0.0f;
204 // Overlay scrollbars have an additional stroke around the fill.
205 if (aParams.isOverlay) {
206 // For the default alpha of 128 we want to end up with 48 in the outline.
207 constexpr float kAlphaScaling = 48.0f / 128.0f;
208 const uint8_t strokeAlpha =
209 uint8_t(clamped(NS_GET_A(faceColor) * kAlphaScaling, 0.0f, 48.0f));
210 if (strokeAlpha) {
211 strokeOutset = (aParams.isDark ? 0.3f : 0.5f) * aScale;
212 strokeWidth = (aParams.isDark ? 0.6f : 0.8f) * aScale;
214 strokeColor = aParams.isDark ? NS_RGBA(0, 0, 0, strokeAlpha)
215 : NS_RGBA(255, 255, 255, strokeAlpha);
219 return {thumbRect, faceColor, strokeColor, strokeWidth, strokeOutset};
222 struct ScrollbarTrackDecorationColors {
223 nscolor mInnerColor = 0;
224 nscolor mShadowColor = 0;
225 nscolor mOuterColor = 0;
228 static ScrollbarTrackDecorationColors ComputeScrollbarTrackDecorationColors(
229 nscolor aTrackColor) {
230 ScrollbarTrackDecorationColors result;
231 float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
232 if (luminance >= 0.5f) {
233 result.mInnerColor =
234 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836f);
235 result.mShadowColor =
236 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982f);
237 result.mOuterColor =
238 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886f);
239 } else {
240 result.mInnerColor =
241 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196f);
242 result.mShadowColor =
243 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018f);
244 result.mOuterColor =
245 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129f);
247 return result;
250 static bool GetScrollbarTrackRects(const LayoutDeviceRect& aRect,
251 const ScrollbarParams& aParams, float aScale,
252 ScrollbarTrackRects& aRects) {
253 if (aParams.isOverlay && !aParams.isRolledOver) {
254 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
255 return false;
258 // This matches the sizing checks in GetMinimumWidgetSize etc.
259 aScale = aScale >= 2.0f ? 2.0f : 1.0f;
261 nscolor trackColor;
262 if (aParams.isCustom) {
263 trackColor = aParams.trackColor;
264 } else {
265 if (aParams.isOverlay) {
266 trackColor = aParams.isDark ? NS_RGBA(201, 201, 201, 38)
267 : NS_RGBA(250, 250, 250, 191);
268 } else {
269 trackColor = aParams.isDark ? NS_RGBA(46, 46, 46, 255)
270 : NS_RGBA(250, 250, 250, 255);
274 float thickness = aParams.isHorizontal ? aRect.height : aRect.width;
276 // The scrollbar track is drawn as multiple non-overlapping segments, which
277 // make up lines of different widths and with slightly different shading.
278 ScrollbarTrackDecorationColors colors =
279 ComputeScrollbarTrackDecorationColors(trackColor);
280 struct {
281 nscolor color;
282 float thickness;
283 } segments[] = {
284 {colors.mInnerColor, 1.0f * aScale},
285 {colors.mShadowColor, 1.0f * aScale},
286 {trackColor, thickness - 3.0f * aScale},
287 {colors.mOuterColor, 1.0f * aScale},
290 // Iterate over the segments "from inside to outside" and fill each segment.
291 // For horizontal scrollbars, iterate top to bottom.
292 // For vertical scrollbars, iterate left to right or right to left based on
293 // aParams.isRtl.
294 auto current = aRects.begin();
295 float accumulatedThickness = 0.0f;
296 for (const auto& segment : segments) {
297 LayoutDeviceRect segmentRect = aRect;
298 float startThickness = accumulatedThickness;
299 float endThickness = startThickness + segment.thickness;
300 if (aParams.isHorizontal) {
301 segmentRect.SetBoxY(aRect.Y() + startThickness, aRect.Y() + endThickness);
302 } else {
303 if (aParams.isRtl) {
304 segmentRect.SetBoxX(aRect.XMost() - endThickness,
305 aRect.XMost() - startThickness);
306 } else {
307 segmentRect.SetBoxX(aRect.X() + startThickness,
308 aRect.X() + endThickness);
311 accumulatedThickness = endThickness;
312 *current++ = {segmentRect, segment.color};
315 return true;
318 static bool GetScrollCornerRects(const LayoutDeviceRect& aRect,
319 const ScrollbarParams& aParams, float aScale,
320 ScrollCornerRects& aRects) {
321 if (aParams.isOverlay && !aParams.isRolledOver) {
322 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
323 return false;
326 // This matches the sizing checks in GetMinimumWidgetSize etc.
327 aScale = aScale >= 2.0f ? 2.0f : 1.0f;
329 // Draw the following scroll corner.
331 // Output: Rectangles:
332 // +---+---+----------+---+ +---+---+----------+---+
333 // | I | S | T ... T | O | | I | S | T ... T | O |
334 // +---+ | | | +---+---+ | |
335 // | S S | T ... T | | | S S | T ... T | . |
336 // +-------+ | . | +-------+----------+ . |
337 // | T ... T | . | | T ... T | . |
338 // | . . | . | | . . | |
339 // | T ... T | | | T ... T | O |
340 // +------------------+ | +------------------+---+
341 // | O ... O | | O ... O |
342 // +----------------------+ +----------------------+
344 float width = aRect.width;
345 float height = aRect.height;
346 nscolor trackColor;
347 if (aParams.isCustom) {
348 trackColor = aParams.trackColor;
349 } else {
350 trackColor =
351 aParams.isDark ? NS_RGBA(46, 46, 46, 255) : NS_RGBA(250, 250, 250, 255);
353 ScrollbarTrackDecorationColors colors =
354 ComputeScrollbarTrackDecorationColors(trackColor);
355 struct {
356 nscolor color;
357 LayoutDeviceRect relativeRect;
358 } pieces[] = {
359 {colors.mInnerColor, {0.0f, 0.0f, 1.0f * aScale, 1.0f * aScale}},
360 {colors.mShadowColor,
361 {1.0f * aScale, 0.0f, 1.0f * aScale, 1.0f * aScale}},
362 {colors.mShadowColor,
363 {0.0f, 1.0f * aScale, 2.0f * aScale, 1.0f * aScale}},
364 {trackColor, {2.0f * aScale, 0.0f, width - 3.0f * aScale, 2.0f * aScale}},
365 {trackColor,
366 {0.0f, 2.0f * aScale, width - 1.0f * aScale, height - 3.0f * aScale}},
367 {colors.mOuterColor,
368 {width - 1.0f * aScale, 0.0f, 1.0f * aScale, height - 1.0f * aScale}},
369 {colors.mOuterColor,
370 {0.0f, height - 1.0f * aScale, width, 1.0f * aScale}},
373 auto current = aRects.begin();
374 for (const auto& piece : pieces) {
375 LayoutDeviceRect pieceRect = piece.relativeRect + aRect.TopLeft();
376 if (aParams.isRtl) {
377 pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
379 *current++ = {pieceRect, piece.color};
381 return true;
384 template <typename PaintBackendData>
385 void ScrollbarDrawingCocoa::DoPaintScrollbarThumb(
386 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
387 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
388 const ElementState& aElementState, const DocumentState& aDocumentState,
389 const Colors& aColors, const DPIRatio& aDpiRatio) {
390 ScrollbarParams params =
391 ComputeScrollbarParams(aFrame, aStyle, aColors, aScrollbarKind);
392 auto thumb = GetThumbRect(aRect, params, aDpiRatio.scale);
393 LayoutDeviceCoord radius =
394 (params.isHorizontal ? thumb.mRect.Height() : thumb.mRect.Width()) / 2.0f;
395 ThemeDrawing::PaintRoundedRectWithRadius(
396 aPaintData, thumb.mRect, thumb.mRect,
397 sRGBColor::FromABGR(thumb.mFillColor), sRGBColor::White(0.0f), 0.0f,
398 radius / aDpiRatio, aDpiRatio);
399 if (!thumb.mStrokeColor) {
400 return;
403 // Paint the stroke if needed.
404 auto strokeRect = thumb.mRect;
405 strokeRect.Inflate(thumb.mStrokeOutset + thumb.mStrokeWidth);
406 radius =
407 (params.isHorizontal ? strokeRect.Height() : strokeRect.Width()) / 2.0f;
408 ThemeDrawing::PaintRoundedRectWithRadius(
409 aPaintData, strokeRect, sRGBColor::White(0.0f),
410 sRGBColor::FromABGR(thumb.mStrokeColor), thumb.mStrokeWidth,
411 radius / aDpiRatio, aDpiRatio);
414 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
415 DrawTarget& aDt, const LayoutDeviceRect& aRect,
416 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
417 const ElementState& aElementState, const DocumentState& aDocumentState,
418 const Colors& aColors, const DPIRatio& aDpiRatio) {
419 DoPaintScrollbarThumb(aDt, aRect, aScrollbarKind, aFrame, aStyle,
420 aElementState, aDocumentState, aColors, aDpiRatio);
421 return true;
424 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
425 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
426 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
427 const ElementState& aElementState, const DocumentState& aDocumentState,
428 const Colors& aColors, const DPIRatio& aDpiRatio) {
429 DoPaintScrollbarThumb(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
430 aElementState, aDocumentState, aColors, aDpiRatio);
431 return true;
434 template <typename PaintBackendData>
435 void ScrollbarDrawingCocoa::DoPaintScrollbarTrack(
436 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
437 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
438 const DocumentState& aDocumentState, const Colors& aColors,
439 const DPIRatio& aDpiRatio) {
440 ScrollbarParams params =
441 ComputeScrollbarParams(aFrame, aStyle, aColors, aScrollbarKind);
442 ScrollbarTrackRects rects;
443 if (GetScrollbarTrackRects(aRect, params, aDpiRatio.scale, rects)) {
444 for (const auto& rect : rects) {
445 ThemeDrawing::FillRect(aPaintData, rect.mRect,
446 sRGBColor::FromABGR(rect.mColor));
451 bool ScrollbarDrawingCocoa::PaintScrollbarTrack(
452 DrawTarget& aDt, const LayoutDeviceRect& aRect,
453 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
454 const DocumentState& aDocumentState, const Colors& aColors,
455 const DPIRatio& aDpiRatio) {
456 DoPaintScrollbarTrack(aDt, aRect, aScrollbarKind, aFrame, aStyle,
457 aDocumentState, aColors, aDpiRatio);
458 return true;
461 bool ScrollbarDrawingCocoa::PaintScrollbarTrack(
462 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
463 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
464 const DocumentState& aDocumentState, const Colors& aColors,
465 const DPIRatio& aDpiRatio) {
466 DoPaintScrollbarTrack(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
467 aDocumentState, aColors, aDpiRatio);
468 return true;
471 template <typename PaintBackendData>
472 void ScrollbarDrawingCocoa::DoPaintScrollCorner(
473 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
474 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
475 const DocumentState& aDocumentState, const Colors& aColors,
476 const DPIRatio& aDpiRatio) {
477 ScrollbarParams params =
478 ComputeScrollbarParams(aFrame, aStyle, aColors, aScrollbarKind);
479 ScrollCornerRects rects;
480 if (GetScrollCornerRects(aRect, params, aDpiRatio.scale, rects)) {
481 for (const auto& rect : rects) {
482 ThemeDrawing::FillRect(aPaintData, rect.mRect,
483 sRGBColor::FromABGR(rect.mColor));
488 bool ScrollbarDrawingCocoa::PaintScrollCorner(
489 DrawTarget& aDt, const LayoutDeviceRect& aRect,
490 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
491 const DocumentState& aDocumentState, const Colors& aColors,
492 const DPIRatio& aDpiRatio) {
493 DoPaintScrollCorner(aDt, aRect, aScrollbarKind, aFrame, aStyle,
494 aDocumentState, aColors, aDpiRatio);
495 return true;
498 bool ScrollbarDrawingCocoa::PaintScrollCorner(
499 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
500 ScrollbarKind aScrollbarKind, nsIFrame* aFrame, const ComputedStyle& aStyle,
501 const DocumentState& aDocumentState, const Colors& aColors,
502 const DPIRatio& aDpiRatio) {
503 DoPaintScrollCorner(aWrData, aRect, aScrollbarKind, aFrame, aStyle,
504 aDocumentState, aColors, aDpiRatio);
505 return true;
508 void ScrollbarDrawingCocoa::RecomputeScrollbarParams() {
509 uint32_t defaultSize = 17;
510 uint32_t overrideSize =
511 StaticPrefs::widget_non_native_theme_scrollbar_size_override();
512 if (overrideSize > 0) {
513 defaultSize = overrideSize;
515 mHorizontalScrollbarHeight = mVerticalScrollbarWidth = defaultSize;
518 } // namespace mozilla::widget