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"
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
;
25 LayoutDeviceRect mRect
;
29 // The caller can draw this rectangle with rounded corners as appropriate.
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;
45 bool isHorizontal
= 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
;
60 nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
) != 0;
61 params
.isRolledOver
= ScrollbarDrawing::IsParentScrollbarRolledOver(aFrame
);
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
);
79 LayoutDeviceIntSize
ScrollbarDrawingCocoa::GetMinimumWidgetSize(
80 nsPresContext
* aPresContext
, StyleAppearance aAppearance
,
82 MOZ_ASSERT(nsNativeTheme::IsWidgetScrollbarPart(aAppearance
));
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(
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};
112 auto dpi
= GetDPIRatioForScrollbarPart(aPresContext
).scale
;
114 return LayoutDeviceIntSize
{minSize
.width
* 2, minSize
.height
* 2};
116 return LayoutDeviceIntSize
{minSize
.width
, minSize
.height
};
120 CSSIntCoord
ScrollbarDrawingCocoa::GetScrollbarSize(StyleScrollbarWidth aWidth
,
122 bool isSmall
= aWidth
== StyleScrollbarWidth::Thin
;
124 return isSmall
? 14 : 16;
126 return isSmall
? 11 : 15;
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
,
143 auto size
= GetScrollbarSize(aWidth
, aOverlay
== Overlay::Yes
,
144 GetDPIRatioForScrollbarPart(aPresContext
));
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
) {
159 if (aParams
.isRolledOver
) {
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
);
175 float leftEdge
= thumbRect
.X() + outerSpacing
;
176 thumbRect
.SetBoxX(leftEdge
, leftEdge
+ thickness
);
178 float rightEdge
= thumbRect
.XMost() - outerSpacing
;
179 thumbRect
.SetBoxX(rightEdge
- thickness
, rightEdge
);
183 // Compute the thumb fill color.
185 if (aParams
.isCustom
) {
186 faceColor
= aParams
.faceColor
;
188 if (aParams
.isOverlay
) {
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);
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
));
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
) {
234 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.836f
);
235 result
.mShadowColor
=
236 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.982f
);
238 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.886f
);
241 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.196f
);
242 result
.mShadowColor
=
243 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.018f
);
245 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.129f
);
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.
258 // This matches the sizing checks in GetMinimumWidgetSize etc.
259 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
262 if (aParams
.isCustom
) {
263 trackColor
= aParams
.trackColor
;
265 if (aParams
.isOverlay
) {
266 trackColor
= aParams
.isDark
? NS_RGBA(201, 201, 201, 38)
267 : NS_RGBA(250, 250, 250, 191);
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
);
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
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
);
304 segmentRect
.SetBoxX(aRect
.XMost() - endThickness
,
305 aRect
.XMost() - startThickness
);
307 segmentRect
.SetBoxX(aRect
.X() + startThickness
,
308 aRect
.X() + endThickness
);
311 accumulatedThickness
= endThickness
;
312 *current
++ = {segmentRect
, segment
.color
};
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.
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
;
347 if (aParams
.isCustom
) {
348 trackColor
= aParams
.trackColor
;
351 aParams
.isDark
? NS_RGBA(46, 46, 46, 255) : NS_RGBA(250, 250, 250, 255);
353 ScrollbarTrackDecorationColors colors
=
354 ComputeScrollbarTrackDecorationColors(trackColor
);
357 LayoutDeviceRect relativeRect
;
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
}},
366 {0.0f
, 2.0f
* aScale
, width
- 1.0f
* aScale
, height
- 3.0f
* aScale
}},
368 {width
- 1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, height
- 1.0f
* aScale
}},
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();
377 pieceRect
.x
= aRect
.XMost() - piece
.relativeRect
.XMost();
379 *current
++ = {pieceRect
, piece
.color
};
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
) {
403 // Paint the stroke if needed.
404 auto strokeRect
= thumb
.mRect
;
405 strokeRect
.Inflate(thumb
.mStrokeOutset
+ thumb
.mStrokeWidth
);
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
);
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
);
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
);
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
);
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
);
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
);
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