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
));
84 auto minSize
= [&]() -> CSSIntSize
{
85 switch (aAppearance
) {
86 case StyleAppearance::ScrollbarthumbHorizontal
:
88 case StyleAppearance::ScrollbarthumbVertical
:
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
= GetCSSScrollbarSize(
97 scrollbarWidth
, Overlay(aPresContext
->UseOverlayScrollbars()));
100 case StyleAppearance::ScrollbarbuttonUp
:
101 case StyleAppearance::ScrollbarbuttonDown
:
103 case StyleAppearance::ScrollbarbuttonLeft
:
104 case StyleAppearance::ScrollbarbuttonRight
:
111 auto dpi
= GetDPIRatioForScrollbarPart(aPresContext
);
112 return LayoutDeviceIntSize::Round(CSSSize(minSize
) * dpi
);
115 static ThumbRect
GetThumbRect(const LayoutDeviceRect
& aRect
,
116 const ScrollbarParams
& aParams
, float aScale
) {
117 // Compute the thumb thickness. This varies based on aParams.small,
118 // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
119 // non-hovered: 5 / 7, overlay hovered: 9 / 11
120 float thickness
= aParams
.isSmall
? 6.0f
: 8.0f
;
121 if (aParams
.isOverlay
) {
123 if (aParams
.isRolledOver
) {
129 // Compute the thumb rect.
130 const float outerSpacing
=
131 ((aParams
.isOverlay
|| aParams
.isSmall
) ? 1.0f
: 2.0f
) * aScale
;
132 LayoutDeviceRect thumbRect
= aRect
;
133 thumbRect
.Deflate(1.0f
* aScale
);
134 if (aParams
.isHorizontal
) {
135 float bottomEdge
= thumbRect
.YMost() - outerSpacing
;
136 thumbRect
.SetBoxY(bottomEdge
- thickness
, bottomEdge
);
139 float leftEdge
= thumbRect
.X() + outerSpacing
;
140 thumbRect
.SetBoxX(leftEdge
, leftEdge
+ thickness
);
142 float rightEdge
= thumbRect
.XMost() - outerSpacing
;
143 thumbRect
.SetBoxX(rightEdge
- thickness
, rightEdge
);
147 // Compute the thumb fill color.
149 if (aParams
.isCustom
) {
150 faceColor
= aParams
.faceColor
;
152 if (aParams
.isOverlay
) {
154 aParams
.isDark
? NS_RGBA(255, 255, 255, 128) : NS_RGBA(0, 0, 0, 128);
155 } else if (aParams
.isDark
) {
156 faceColor
= aParams
.isRolledOver
? NS_RGBA(158, 158, 158, 255)
157 : NS_RGBA(117, 117, 117, 255);
159 faceColor
= aParams
.isRolledOver
? NS_RGBA(125, 125, 125, 255)
160 : NS_RGBA(194, 194, 194, 255);
164 nscolor strokeColor
= 0;
165 float strokeOutset
= 0.0f
;
166 float strokeWidth
= 0.0f
;
168 // Overlay scrollbars have an additional stroke around the fill.
169 if (aParams
.isOverlay
) {
170 // For the default alpha of 128 we want to end up with 48 in the outline.
171 constexpr float kAlphaScaling
= 48.0f
/ 128.0f
;
172 const uint8_t strokeAlpha
=
173 uint8_t(clamped(NS_GET_A(faceColor
) * kAlphaScaling
, 0.0f
, 48.0f
));
175 strokeOutset
= (aParams
.isDark
? 0.3f
: 0.5f
) * aScale
;
176 strokeWidth
= (aParams
.isDark
? 0.6f
: 0.8f
) * aScale
;
178 strokeColor
= aParams
.isDark
? NS_RGBA(0, 0, 0, strokeAlpha
)
179 : NS_RGBA(255, 255, 255, strokeAlpha
);
183 return {thumbRect
, faceColor
, strokeColor
, strokeWidth
, strokeOutset
};
186 struct ScrollbarTrackDecorationColors
{
187 nscolor mInnerColor
= 0;
188 nscolor mShadowColor
= 0;
189 nscolor mOuterColor
= 0;
192 static ScrollbarTrackDecorationColors
ComputeScrollbarTrackDecorationColors(
193 nscolor aTrackColor
) {
194 ScrollbarTrackDecorationColors result
;
195 float luminance
= RelativeLuminanceUtils::Compute(aTrackColor
);
196 if (luminance
>= 0.5f
) {
198 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.836f
);
199 result
.mShadowColor
=
200 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.982f
);
202 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.886f
);
205 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.196f
);
206 result
.mShadowColor
=
207 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.018f
);
209 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.129f
);
214 static bool GetScrollbarTrackRects(const LayoutDeviceRect
& aRect
,
215 const ScrollbarParams
& aParams
, float aScale
,
216 ScrollbarTrackRects
& aRects
) {
217 if (aParams
.isOverlay
&& !aParams
.isRolledOver
) {
218 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
223 if (aParams
.isCustom
) {
224 trackColor
= aParams
.trackColor
;
226 if (aParams
.isOverlay
) {
227 trackColor
= aParams
.isDark
? NS_RGBA(201, 201, 201, 38)
228 : NS_RGBA(250, 250, 250, 191);
230 trackColor
= aParams
.isDark
? NS_RGBA(46, 46, 46, 255)
231 : NS_RGBA(250, 250, 250, 255);
235 float thickness
= aParams
.isHorizontal
? aRect
.height
: aRect
.width
;
237 // The scrollbar track is drawn as multiple non-overlapping segments, which
238 // make up lines of different widths and with slightly different shading.
239 ScrollbarTrackDecorationColors colors
=
240 ComputeScrollbarTrackDecorationColors(trackColor
);
245 {colors
.mInnerColor
, 1.0f
* aScale
},
246 {colors
.mShadowColor
, 1.0f
* aScale
},
247 {trackColor
, thickness
- 3.0f
* aScale
},
248 {colors
.mOuterColor
, 1.0f
* aScale
},
251 // Iterate over the segments "from inside to outside" and fill each segment.
252 // For horizontal scrollbars, iterate top to bottom.
253 // For vertical scrollbars, iterate left to right or right to left based on
255 auto current
= aRects
.begin();
256 float accumulatedThickness
= 0.0f
;
257 for (const auto& segment
: segments
) {
258 LayoutDeviceRect segmentRect
= aRect
;
259 float startThickness
= accumulatedThickness
;
260 float endThickness
= startThickness
+ segment
.thickness
;
261 if (aParams
.isHorizontal
) {
262 segmentRect
.SetBoxY(aRect
.Y() + startThickness
, aRect
.Y() + endThickness
);
265 segmentRect
.SetBoxX(aRect
.XMost() - endThickness
,
266 aRect
.XMost() - startThickness
);
268 segmentRect
.SetBoxX(aRect
.X() + startThickness
,
269 aRect
.X() + endThickness
);
272 accumulatedThickness
= endThickness
;
273 *current
++ = {segmentRect
, segment
.color
};
279 static bool GetScrollCornerRects(const LayoutDeviceRect
& aRect
,
280 const ScrollbarParams
& aParams
, float aScale
,
281 ScrollCornerRects
& aRects
) {
282 if (aParams
.isOverlay
&& !aParams
.isRolledOver
) {
283 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
287 // Draw the following scroll corner.
289 // Output: Rectangles:
290 // +---+---+----------+---+ +---+---+----------+---+
291 // | I | S | T ... T | O | | I | S | T ... T | O |
292 // +---+ | | | +---+---+ | |
293 // | S S | T ... T | | | S S | T ... T | . |
294 // +-------+ | . | +-------+----------+ . |
295 // | T ... T | . | | T ... T | . |
296 // | . . | . | | . . | |
297 // | T ... T | | | T ... T | O |
298 // +------------------+ | +------------------+---+
299 // | O ... O | | O ... O |
300 // +----------------------+ +----------------------+
302 float width
= aRect
.width
;
303 float height
= aRect
.height
;
305 if (aParams
.isCustom
) {
306 trackColor
= aParams
.trackColor
;
309 aParams
.isDark
? NS_RGBA(46, 46, 46, 255) : NS_RGBA(250, 250, 250, 255);
311 ScrollbarTrackDecorationColors colors
=
312 ComputeScrollbarTrackDecorationColors(trackColor
);
315 LayoutDeviceRect relativeRect
;
317 {colors
.mInnerColor
, {0.0f
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
318 {colors
.mShadowColor
,
319 {1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
320 {colors
.mShadowColor
,
321 {0.0f
, 1.0f
* aScale
, 2.0f
* aScale
, 1.0f
* aScale
}},
322 {trackColor
, {2.0f
* aScale
, 0.0f
, width
- 3.0f
* aScale
, 2.0f
* aScale
}},
324 {0.0f
, 2.0f
* aScale
, width
- 1.0f
* aScale
, height
- 3.0f
* aScale
}},
326 {width
- 1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, height
- 1.0f
* aScale
}},
328 {0.0f
, height
- 1.0f
* aScale
, width
, 1.0f
* aScale
}},
331 auto current
= aRects
.begin();
332 for (const auto& piece
: pieces
) {
333 LayoutDeviceRect pieceRect
= piece
.relativeRect
+ aRect
.TopLeft();
335 pieceRect
.x
= aRect
.XMost() - piece
.relativeRect
.XMost();
337 *current
++ = {pieceRect
, piece
.color
};
342 template <typename PaintBackendData
>
343 void ScrollbarDrawingCocoa::DoPaintScrollbarThumb(
344 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
345 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
346 const ElementState
& aElementState
, const DocumentState
& aDocumentState
,
347 const Colors
& aColors
, const DPIRatio
& aDpiRatio
) {
348 ScrollbarParams params
=
349 ComputeScrollbarParams(aFrame
, aStyle
, aColors
, aScrollbarKind
);
350 auto thumb
= GetThumbRect(aRect
, params
, aDpiRatio
.scale
);
351 LayoutDeviceCoord radius
=
352 (params
.isHorizontal
? thumb
.mRect
.Height() : thumb
.mRect
.Width()) / 2.0f
;
353 ThemeDrawing::PaintRoundedRectWithRadius(
354 aPaintData
, thumb
.mRect
, thumb
.mRect
,
355 sRGBColor::FromABGR(thumb
.mFillColor
), sRGBColor::White(0.0f
), 0.0f
,
356 radius
/ aDpiRatio
, aDpiRatio
);
357 if (!thumb
.mStrokeColor
) {
361 // Paint the stroke if needed.
362 auto strokeRect
= thumb
.mRect
;
363 strokeRect
.Inflate(thumb
.mStrokeOutset
+ thumb
.mStrokeWidth
);
365 (params
.isHorizontal
? strokeRect
.Height() : strokeRect
.Width()) / 2.0f
;
366 ThemeDrawing::PaintRoundedRectWithRadius(
367 aPaintData
, strokeRect
, sRGBColor::White(0.0f
),
368 sRGBColor::FromABGR(thumb
.mStrokeColor
), thumb
.mStrokeWidth
,
369 radius
/ aDpiRatio
, aDpiRatio
);
372 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
373 DrawTarget
& aDt
, 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(aDt
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
378 aElementState
, aDocumentState
, aColors
, aDpiRatio
);
382 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
383 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
384 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
385 const ElementState
& aElementState
, const DocumentState
& aDocumentState
,
386 const Colors
& aColors
, const DPIRatio
& aDpiRatio
) {
387 DoPaintScrollbarThumb(aWrData
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
388 aElementState
, aDocumentState
, aColors
, aDpiRatio
);
392 template <typename PaintBackendData
>
393 void ScrollbarDrawingCocoa::DoPaintScrollbarTrack(
394 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
395 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
396 const DocumentState
& aDocumentState
, const Colors
& aColors
,
397 const DPIRatio
& aDpiRatio
) {
398 ScrollbarParams params
=
399 ComputeScrollbarParams(aFrame
, aStyle
, aColors
, aScrollbarKind
);
400 ScrollbarTrackRects rects
;
401 if (GetScrollbarTrackRects(aRect
, params
, aDpiRatio
.scale
, rects
)) {
402 for (const auto& rect
: rects
) {
403 ThemeDrawing::FillRect(aPaintData
, rect
.mRect
,
404 sRGBColor::FromABGR(rect
.mColor
));
409 bool ScrollbarDrawingCocoa::PaintScrollbarTrack(
410 DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
,
411 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
412 const DocumentState
& aDocumentState
, const Colors
& aColors
,
413 const DPIRatio
& aDpiRatio
) {
414 DoPaintScrollbarTrack(aDt
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
415 aDocumentState
, aColors
, aDpiRatio
);
419 bool ScrollbarDrawingCocoa::PaintScrollbarTrack(
420 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
421 ScrollbarKind aScrollbarKind
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
422 const DocumentState
& aDocumentState
, const Colors
& aColors
,
423 const DPIRatio
& aDpiRatio
) {
424 DoPaintScrollbarTrack(aWrData
, aRect
, aScrollbarKind
, aFrame
, aStyle
,
425 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
, 14);
476 } // namespace mozilla::widget