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"
11 #include "nsLayoutUtils.h"
12 #include "nsNativeTheme.h"
14 using namespace mozilla::gfx
;
15 namespace mozilla::widget
{
17 using ScrollbarKind
= ScrollbarDrawing::ScrollbarKind
;
20 LayoutDeviceRect mRect
;
24 // The caller can draw this rectangle with rounded corners as appropriate.
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;
40 bool isHorizontal
= 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
);
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
);
73 LayoutDeviceIntSize
ScrollbarDrawingCocoa::GetMinimumWidgetSize(
74 nsPresContext
* aPresContext
, StyleAppearance aAppearance
,
76 MOZ_ASSERT(nsNativeTheme::IsWidgetScrollbarPart(aAppearance
));
78 auto minSize
= [&]() -> CSSIntSize
{
79 switch (aAppearance
) {
80 case StyleAppearance::ScrollbarthumbHorizontal
:
82 case StyleAppearance::ScrollbarthumbVertical
:
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()));
92 case StyleAppearance::ScrollbarbuttonUp
:
93 case StyleAppearance::ScrollbarbuttonDown
:
95 case StyleAppearance::ScrollbarbuttonLeft
:
96 case StyleAppearance::ScrollbarbuttonRight
:
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
) {
117 if (aParams
.isRolledOver
) {
118 thickness
= aParams
.isSmall
? 7.0f
: 11.0f
;
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
);
134 float leftEdge
= thumbRect
.X() + outerSpacing
;
135 thumbRect
.SetBoxX(leftEdge
, leftEdge
+ thickness
);
137 float rightEdge
= thumbRect
.XMost() - outerSpacing
;
138 thumbRect
.SetBoxX(rightEdge
- thickness
, rightEdge
);
142 // Compute the thumb fill color.
144 if (aParams
.isCustom
) {
145 faceColor
= aParams
.faceColor
;
147 if (aParams
.isOverlay
) {
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);
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
));
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
) {
193 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.836f
);
194 result
.mShadowColor
=
195 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.982f
);
197 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.886f
);
200 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.196f
);
201 result
.mShadowColor
=
202 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.018f
);
204 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.129f
);
209 static bool GetScrollbarTrackRects(const LayoutDeviceRect
& aRect
,
210 const ScrollbarParams
& aParams
, float aScale
,
211 ScrollbarTrackRects
& aRects
) {
213 if (aParams
.isCustom
) {
214 trackColor
= aParams
.trackColor
;
216 if (aParams
.isOverlay
) {
217 trackColor
= aParams
.isDark
? NS_RGBA(201, 201, 201, 38)
218 : NS_RGBA(250, 250, 250, 191);
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
);
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
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
);
255 segmentRect
.SetBoxX(aRect
.XMost() - endThickness
,
256 aRect
.XMost() - startThickness
);
258 segmentRect
.SetBoxX(aRect
.X() + startThickness
,
259 aRect
.X() + endThickness
);
262 accumulatedThickness
= endThickness
;
263 *current
++ = {segmentRect
, segment
.color
};
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.
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
;
295 if (aParams
.isCustom
) {
296 trackColor
= aParams
.trackColor
;
299 aParams
.isDark
? NS_RGBA(46, 46, 46, 255) : NS_RGBA(250, 250, 250, 255);
301 ScrollbarTrackDecorationColors colors
=
302 ComputeScrollbarTrackDecorationColors(trackColor
);
305 LayoutDeviceRect relativeRect
;
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
}},
314 {0.0f
, 2.0f
* aScale
, width
- 1.0f
* aScale
, height
- 3.0f
* aScale
}},
316 {width
- 1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, height
- 1.0f
* aScale
}},
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();
325 pieceRect
.x
= aRect
.XMost() - piece
.relativeRect
.XMost();
327 *current
++ = {pieceRect
, piece
.color
};
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
) {
351 // Paint the stroke if needed.
352 auto strokeRect
= thumb
.mRect
;
353 strokeRect
.Inflate(thumb
.mStrokeOutset
+ thumb
.mStrokeWidth
);
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
);
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
);
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.
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
);
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
);
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
);
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
);
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