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;
47 bool isOnDarkBackground
= 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 ScrollbarKind aScrollbarKind
) {
57 ScrollbarParams params
;
59 nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
) != 0;
60 params
.isRolledOver
= ScrollbarDrawing::IsParentScrollbarRolledOver(aFrame
);
62 aStyle
.StyleUIReset()->ScrollbarWidth() == StyleScrollbarWidth::Thin
;
63 params
.isRtl
= aScrollbarKind
== ScrollbarKind::VerticalLeft
;
64 params
.isHorizontal
= aScrollbarKind
== ScrollbarKind::Horizontal
;
65 params
.isOnDarkBackground
= !StaticPrefs::widget_disable_dark_scrollbar() &&
66 nsNativeTheme::IsDarkBackground(aFrame
);
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
) {
189 faceColor
= aParams
.isOnDarkBackground
? NS_RGBA(255, 255, 255, 128)
190 : NS_RGBA(0, 0, 0, 128);
191 } else if (aParams
.isOnDarkBackground
) {
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
.isOnDarkBackground
? 0.3f
: 0.5f
) * aScale
;
212 strokeWidth
= (aParams
.isOnDarkBackground
? 0.6f
: 0.8f
) * aScale
;
214 strokeColor
= aParams
.isOnDarkBackground
215 ? NS_RGBA(0, 0, 0, strokeAlpha
)
216 : NS_RGBA(255, 255, 255, strokeAlpha
);
220 return {thumbRect
, faceColor
, strokeColor
, strokeWidth
, strokeOutset
};
223 struct ScrollbarTrackDecorationColors
{
224 nscolor mInnerColor
= 0;
225 nscolor mShadowColor
= 0;
226 nscolor mOuterColor
= 0;
229 static ScrollbarTrackDecorationColors
ComputeScrollbarTrackDecorationColors(
230 nscolor aTrackColor
) {
231 ScrollbarTrackDecorationColors result
;
232 float luminance
= RelativeLuminanceUtils::Compute(aTrackColor
);
233 if (luminance
>= 0.5f
) {
235 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.836f
);
236 result
.mShadowColor
=
237 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.982f
);
239 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.886f
);
242 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.196f
);
243 result
.mShadowColor
=
244 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.018f
);
246 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.129f
);
251 static bool GetScrollbarTrackRects(const LayoutDeviceRect
& aRect
,
252 const ScrollbarParams
& aParams
, float aScale
,
253 ScrollbarTrackRects
& aRects
) {
254 if (aParams
.isOverlay
&& !aParams
.isRolledOver
) {
255 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
259 // This matches the sizing checks in GetMinimumWidgetSize etc.
260 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
263 if (aParams
.isCustom
) {
264 trackColor
= aParams
.trackColor
;
266 if (aParams
.isOverlay
) {
267 trackColor
= aParams
.isOnDarkBackground
? NS_RGBA(201, 201, 201, 38)
268 : NS_RGBA(250, 250, 250, 191);
270 trackColor
= aParams
.isOnDarkBackground
? NS_RGBA(46, 46, 46, 255)
271 : NS_RGBA(250, 250, 250, 255);
275 float thickness
= aParams
.isHorizontal
? aRect
.height
: aRect
.width
;
277 // The scrollbar track is drawn as multiple non-overlapping segments, which
278 // make up lines of different widths and with slightly different shading.
279 ScrollbarTrackDecorationColors colors
=
280 ComputeScrollbarTrackDecorationColors(trackColor
);
285 {colors
.mInnerColor
, 1.0f
* aScale
},
286 {colors
.mShadowColor
, 1.0f
* aScale
},
287 {trackColor
, thickness
- 3.0f
* aScale
},
288 {colors
.mOuterColor
, 1.0f
* aScale
},
291 // Iterate over the segments "from inside to outside" and fill each segment.
292 // For horizontal scrollbars, iterate top to bottom.
293 // For vertical scrollbars, iterate left to right or right to left based on
295 auto current
= aRects
.begin();
296 float accumulatedThickness
= 0.0f
;
297 for (const auto& segment
: segments
) {
298 LayoutDeviceRect segmentRect
= aRect
;
299 float startThickness
= accumulatedThickness
;
300 float endThickness
= startThickness
+ segment
.thickness
;
301 if (aParams
.isHorizontal
) {
302 segmentRect
.SetBoxY(aRect
.Y() + startThickness
, aRect
.Y() + endThickness
);
305 segmentRect
.SetBoxX(aRect
.XMost() - endThickness
,
306 aRect
.XMost() - startThickness
);
308 segmentRect
.SetBoxX(aRect
.X() + startThickness
,
309 aRect
.X() + endThickness
);
312 accumulatedThickness
= endThickness
;
313 *current
++ = {segmentRect
, segment
.color
};
319 static bool GetScrollCornerRects(const LayoutDeviceRect
& aRect
,
320 const ScrollbarParams
& aParams
, float aScale
,
321 ScrollCornerRects
& aRects
) {
322 if (aParams
.isOverlay
&& !aParams
.isRolledOver
) {
323 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
327 // This matches the sizing checks in GetMinimumWidgetSize etc.
328 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
330 // Draw the following scroll corner.
332 // Output: Rectangles:
333 // +---+---+----------+---+ +---+---+----------+---+
334 // | I | S | T ... T | O | | I | S | T ... T | O |
335 // +---+ | | | +---+---+ | |
336 // | S S | T ... T | | | S S | T ... T | . |
337 // +-------+ | . | +-------+----------+ . |
338 // | T ... T | . | | T ... T | . |
339 // | . . | . | | . . | |
340 // | T ... T | | | T ... T | O |
341 // +------------------+ | +------------------+---+
342 // | O ... O | | O ... O |
343 // +----------------------+ +----------------------+
345 float width
= aRect
.width
;
346 float height
= aRect
.height
;
348 if (aParams
.isCustom
) {
349 trackColor
= aParams
.trackColor
;
351 trackColor
= aParams
.isOnDarkBackground
? NS_RGBA(46, 46, 46, 255)
352 : NS_RGBA(250, 250, 250, 255);
354 ScrollbarTrackDecorationColors colors
=
355 ComputeScrollbarTrackDecorationColors(trackColor
);
358 LayoutDeviceRect relativeRect
;
360 {colors
.mInnerColor
, {0.0f
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
361 {colors
.mShadowColor
,
362 {1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
363 {colors
.mShadowColor
,
364 {0.0f
, 1.0f
* aScale
, 2.0f
* aScale
, 1.0f
* aScale
}},
365 {trackColor
, {2.0f
* aScale
, 0.0f
, width
- 3.0f
* aScale
, 2.0f
* aScale
}},
367 {0.0f
, 2.0f
* aScale
, width
- 1.0f
* aScale
, height
- 3.0f
* aScale
}},
369 {width
- 1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, height
- 1.0f
* aScale
}},
371 {0.0f
, height
- 1.0f
* aScale
, width
, 1.0f
* aScale
}},
374 auto current
= aRects
.begin();
375 for (const auto& piece
: pieces
) {
376 LayoutDeviceRect pieceRect
= piece
.relativeRect
+ aRect
.TopLeft();
378 pieceRect
.x
= aRect
.XMost() - piece
.relativeRect
.XMost();
380 *current
++ = {pieceRect
, piece
.color
};
385 template <typename PaintBackendData
>
386 void ScrollbarDrawingCocoa::DoPaintScrollbarThumb(
387 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
388 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
389 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
390 const DPIRatio
& aDpiRatio
) {
391 ScrollbarParams params
=
392 ComputeScrollbarParams(aFrame
, aStyle
, aScrollbarKind
);
393 auto thumb
= GetThumbRect(aRect
, params
, aDpiRatio
.scale
);
394 LayoutDeviceCoord radius
=
395 (params
.isHorizontal
? thumb
.mRect
.Height() : thumb
.mRect
.Width()) / 2.0f
;
396 ThemeDrawing::PaintRoundedRectWithRadius(
397 aPaintData
, thumb
.mRect
, thumb
.mRect
,
398 sRGBColor::FromABGR(thumb
.mFillColor
), sRGBColor::White(0.0f
), 0.0f
,
399 radius
/ aDpiRatio
, aDpiRatio
);
400 if (!thumb
.mStrokeColor
) {
404 // Paint the stroke if needed.
405 auto strokeRect
= thumb
.mRect
;
406 strokeRect
.Inflate(thumb
.mStrokeOutset
+ thumb
.mStrokeWidth
);
408 (params
.isHorizontal
? strokeRect
.Height() : strokeRect
.Width()) / 2.0f
;
409 ThemeDrawing::PaintRoundedRectWithRadius(
410 aPaintData
, strokeRect
, sRGBColor::White(0.0f
),
411 sRGBColor::FromABGR(thumb
.mStrokeColor
), thumb
.mStrokeWidth
,
412 radius
/ aDpiRatio
, aDpiRatio
);
415 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
416 DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
,
417 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
418 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
419 const Colors
&, const DPIRatio
& aDpiRatio
) {
420 // TODO: Maybe respect the UseSystemColors setting?
421 DoPaintScrollbarThumb(aDt
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
422 aElementState
, aDocumentState
, aDpiRatio
);
426 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
427 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
428 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
429 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
430 const Colors
&, const DPIRatio
& aDpiRatio
) {
431 // TODO: Maybe respect the UseSystemColors setting?
432 DoPaintScrollbarThumb(aWrData
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
433 aElementState
, aDocumentState
, aDpiRatio
);
437 template <typename PaintBackendData
>
438 void ScrollbarDrawingCocoa::DoPaintScrollbarTrack(
439 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
440 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
441 const EventStates
& aDocumentState
, const DPIRatio
& aDpiRatio
) {
442 ScrollbarParams params
=
443 ComputeScrollbarParams(aFrame
, aStyle
, aScrollbarKind
);
444 ScrollbarTrackRects rects
;
445 if (GetScrollbarTrackRects(aRect
, params
, aDpiRatio
.scale
, rects
)) {
446 for (const auto& rect
: rects
) {
447 ThemeDrawing::FillRect(aPaintData
, rect
.mRect
,
448 sRGBColor::FromABGR(rect
.mColor
));
453 bool ScrollbarDrawingCocoa::PaintScrollbarTrack(
454 DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
,
455 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
456 const EventStates
& aDocumentState
, const Colors
&,
457 const DPIRatio
& aDpiRatio
) {
458 // TODO: Maybe respect the UseSystemColors setting?
459 DoPaintScrollbarTrack(aDt
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
460 aDocumentState
, aDpiRatio
);
464 bool ScrollbarDrawingCocoa::PaintScrollbarTrack(
465 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
466 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
467 const EventStates
& aDocumentState
, const Colors
&,
468 const DPIRatio
& aDpiRatio
) {
469 // TODO: Maybe respect the UseSystemColors setting?
470 DoPaintScrollbarTrack(aWrData
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
471 aDocumentState
, aDpiRatio
);
475 template <typename PaintBackendData
>
476 void ScrollbarDrawingCocoa::DoPaintScrollCorner(
477 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
478 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
479 const EventStates
& aDocumentState
, const DPIRatio
& aDpiRatio
) {
480 ScrollbarParams params
=
481 ComputeScrollbarParams(aFrame
, aStyle
, aScrollbarKind
);
482 ScrollCornerRects rects
;
483 if (GetScrollCornerRects(aRect
, params
, aDpiRatio
.scale
, rects
)) {
484 for (const auto& rect
: rects
) {
485 ThemeDrawing::FillRect(aPaintData
, rect
.mRect
,
486 sRGBColor::FromABGR(rect
.mColor
));
491 bool ScrollbarDrawingCocoa::PaintScrollCorner(
492 DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
,
493 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
494 const EventStates
& aDocumentState
, const Colors
&,
495 const DPIRatio
& aDpiRatio
) {
496 // TODO: Maybe respect the UseSystemColors setting?
497 DoPaintScrollCorner(aDt
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
498 aDocumentState
, aDpiRatio
);
502 bool ScrollbarDrawingCocoa::PaintScrollCorner(
503 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
504 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
505 const EventStates
& aDocumentState
, const Colors
&,
506 const DPIRatio
& aDpiRatio
) {
507 // TODO: Maybe respect the UseSystemColors setting?
508 DoPaintScrollCorner(aWrData
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
509 aDocumentState
, aDpiRatio
);
513 void ScrollbarDrawingCocoa::RecomputeScrollbarParams() {
514 uint32_t defaultSize
= 17;
515 uint32_t overrideSize
=
516 StaticPrefs::widget_non_native_theme_scrollbar_size_override();
517 if (overrideSize
> 0) {
518 defaultSize
= overrideSize
;
520 mHorizontalScrollbarHeight
= mVerticalScrollbarWidth
= defaultSize
;
523 } // namespace mozilla::widget