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"
14 #include "nsLayoutUtils.h"
15 #include "nsLookAndFeel.h"
16 #include "nsNativeTheme.h"
18 using namespace mozilla
;
19 using namespace mozilla::gfx
;
20 using namespace mozilla::widget
;
22 LayoutDeviceIntSize
ScrollbarDrawingCocoa::GetMinimumWidgetSize(
23 nsPresContext
* aPresContext
, StyleAppearance aAppearance
,
25 MOZ_ASSERT(nsNativeTheme::IsWidgetScrollbarPart(aAppearance
));
28 switch (aAppearance
) {
29 case StyleAppearance::ScrollbarthumbHorizontal
:
30 return IntSize
{26, 0};
31 case StyleAppearance::ScrollbarthumbVertical
:
32 return IntSize
{0, 26};
33 case StyleAppearance::ScrollbarVertical
:
34 case StyleAppearance::ScrollbarHorizontal
:
35 case StyleAppearance::ScrollbartrackVertical
:
36 case StyleAppearance::ScrollbartrackHorizontal
: {
37 ComputedStyle
* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
38 auto scrollbarWidth
= style
->StyleUIReset()->mScrollbarWidth
;
39 auto size
= GetScrollbarSize(
41 LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
));
42 return IntSize
{size
, size
};
44 case StyleAppearance::ScrollbarbuttonUp
:
45 case StyleAppearance::ScrollbarbuttonDown
:
46 return IntSize
{15, 16};
47 case StyleAppearance::ScrollbarbuttonLeft
:
48 case StyleAppearance::ScrollbarbuttonRight
:
49 return IntSize
{16, 15};
55 auto dpi
= GetDPIRatioForScrollbarPart(aPresContext
).scale
;
57 return LayoutDeviceIntSize
{minSize
.width
* 2, minSize
.height
* 2};
59 return LayoutDeviceIntSize
{minSize
.width
, minSize
.height
};
63 CSSIntCoord
ScrollbarDrawingCocoa::GetScrollbarSize(StyleScrollbarWidth aWidth
,
65 bool isSmall
= aWidth
== StyleScrollbarWidth::Thin
;
67 return isSmall
? 14 : 16;
69 return isSmall
? 11 : 15;
73 LayoutDeviceIntCoord
ScrollbarDrawingCocoa::GetScrollbarSize(
74 StyleScrollbarWidth aWidth
, bool aOverlay
, DPIRatio aDpiRatio
) {
75 CSSIntCoord size
= GetScrollbarSize(aWidth
, aOverlay
);
76 if (aDpiRatio
.scale
>= 2.0f
) {
77 return int32_t(size
) * 2;
82 auto ScrollbarDrawingCocoa::GetScrollbarSizes(nsPresContext
* aPresContext
,
83 StyleScrollbarWidth aWidth
,
86 auto size
= GetScrollbarSize(aWidth
, aOverlay
== Overlay::Yes
,
87 GetDPIRatioForScrollbarPart(aPresContext
));
92 auto ScrollbarDrawingCocoa::GetThumbRect(const Rect
& aRect
,
93 const ScrollbarParams
& aParams
,
94 float aScale
) -> ThumbRect
{
95 // This matches the sizing checks in GetMinimumWidgetSize etc.
96 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
98 // Compute the thumb thickness. This varies based on aParams.small,
99 // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
100 // non-hovered: 5 / 7, overlay hovered: 9 / 11
101 float thickness
= aParams
.isSmall
? 6.0f
: 8.0f
;
102 if (aParams
.isOverlay
) {
104 if (aParams
.isRolledOver
) {
110 // Compute the thumb rect.
111 const float outerSpacing
=
112 ((aParams
.isOverlay
|| aParams
.isSmall
) ? 1.0f
: 2.0f
) * aScale
;
113 Rect thumbRect
= aRect
;
114 thumbRect
.Deflate(1.0f
* aScale
);
115 if (aParams
.isHorizontal
) {
116 float bottomEdge
= thumbRect
.YMost() - outerSpacing
;
117 thumbRect
.SetBoxY(bottomEdge
- thickness
, bottomEdge
);
120 float leftEdge
= thumbRect
.X() + outerSpacing
;
121 thumbRect
.SetBoxX(leftEdge
, leftEdge
+ thickness
);
123 float rightEdge
= thumbRect
.XMost() - outerSpacing
;
124 thumbRect
.SetBoxX(rightEdge
- thickness
, rightEdge
);
128 // Compute the thumb fill color.
130 if (aParams
.isCustom
) {
131 faceColor
= aParams
.faceColor
;
133 if (aParams
.isOverlay
) {
134 faceColor
= aParams
.isOnDarkBackground
? NS_RGBA(255, 255, 255, 128)
135 : NS_RGBA(0, 0, 0, 128);
136 } else if (aParams
.isOnDarkBackground
) {
137 faceColor
= aParams
.isRolledOver
? NS_RGBA(158, 158, 158, 255)
138 : NS_RGBA(117, 117, 117, 255);
140 faceColor
= aParams
.isRolledOver
? NS_RGBA(125, 125, 125, 255)
141 : NS_RGBA(194, 194, 194, 255);
145 nscolor strokeColor
= 0;
146 float strokeOutset
= 0.0f
;
147 float strokeWidth
= 0.0f
;
149 // Overlay scrollbars have an additional stroke around the fill.
150 if (aParams
.isOverlay
) {
151 strokeOutset
= (aParams
.isOnDarkBackground
? 0.3f
: 0.5f
) * aScale
;
152 strokeWidth
= (aParams
.isOnDarkBackground
? 0.6f
: 0.8f
) * aScale
;
154 strokeColor
= aParams
.isOnDarkBackground
? NS_RGBA(0, 0, 0, 48)
155 : NS_RGBA(255, 255, 255, 48);
158 return {thumbRect
, faceColor
, strokeColor
, strokeWidth
, strokeOutset
};
161 struct ScrollbarTrackDecorationColors
{
162 nscolor mInnerColor
= 0;
163 nscolor mShadowColor
= 0;
164 nscolor mOuterColor
= 0;
167 static ScrollbarTrackDecorationColors
ComputeScrollbarTrackDecorationColors(
168 nscolor aTrackColor
) {
169 ScrollbarTrackDecorationColors result
;
170 float luminance
= RelativeLuminanceUtils::Compute(aTrackColor
);
171 if (luminance
>= 0.5f
) {
173 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.836f
);
174 result
.mShadowColor
=
175 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.982f
);
177 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.886f
);
180 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.196f
);
181 result
.mShadowColor
=
182 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.018f
);
184 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.129f
);
190 bool ScrollbarDrawingCocoa::GetScrollbarTrackRects(
191 const Rect
& aRect
, const ScrollbarParams
& aParams
, float aScale
,
192 ScrollbarTrackRects
& aRects
) {
193 if (aParams
.isOverlay
&& !aParams
.isRolledOver
) {
194 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
198 // This matches the sizing checks in GetMinimumWidgetSize etc.
199 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
202 if (aParams
.isCustom
) {
203 trackColor
= aParams
.trackColor
;
205 if (aParams
.isOverlay
) {
206 trackColor
= aParams
.isOnDarkBackground
? NS_RGBA(201, 201, 201, 38)
207 : NS_RGBA(250, 250, 250, 191);
209 trackColor
= aParams
.isOnDarkBackground
? NS_RGBA(46, 46, 46, 255)
210 : NS_RGBA(250, 250, 250, 255);
214 float thickness
= aParams
.isHorizontal
? aRect
.height
: aRect
.width
;
216 // The scrollbar track is drawn as multiple non-overlapping segments, which
217 // make up lines of different widths and with slightly different shading.
218 ScrollbarTrackDecorationColors colors
=
219 ComputeScrollbarTrackDecorationColors(trackColor
);
224 {colors
.mInnerColor
, 1.0f
* aScale
},
225 {colors
.mShadowColor
, 1.0f
* aScale
},
226 {trackColor
, thickness
- 3.0f
* aScale
},
227 {colors
.mOuterColor
, 1.0f
* aScale
},
230 // Iterate over the segments "from inside to outside" and fill each segment.
231 // For horizontal scrollbars, iterate top to bottom.
232 // For vertical scrollbars, iterate left to right or right to left based on
234 auto current
= aRects
.begin();
235 float accumulatedThickness
= 0.0f
;
236 for (const auto& segment
: segments
) {
237 Rect segmentRect
= aRect
;
238 float startThickness
= accumulatedThickness
;
239 float endThickness
= startThickness
+ segment
.thickness
;
240 if (aParams
.isHorizontal
) {
241 segmentRect
.SetBoxY(aRect
.Y() + startThickness
, aRect
.Y() + endThickness
);
244 segmentRect
.SetBoxX(aRect
.XMost() - endThickness
,
245 aRect
.XMost() - startThickness
);
247 segmentRect
.SetBoxX(aRect
.X() + startThickness
,
248 aRect
.X() + endThickness
);
251 accumulatedThickness
= endThickness
;
252 *current
++ = {segmentRect
, segment
.color
};
259 bool ScrollbarDrawingCocoa::GetScrollCornerRects(const Rect
& aRect
,
260 const ScrollbarParams
& aParams
,
262 ScrollCornerRects
& aRects
) {
263 if (aParams
.isOverlay
&& !aParams
.isRolledOver
) {
264 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
268 // This matches the sizing checks in GetMinimumWidgetSize etc.
269 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
271 // Draw the following scroll corner.
273 // Output: Rectangles:
274 // +---+---+----------+---+ +---+---+----------+---+
275 // | I | S | T ... T | O | | I | S | T ... T | O |
276 // +---+ | | | +---+---+ | |
277 // | S S | T ... T | | | S S | T ... T | . |
278 // +-------+ | . | +-------+----------+ . |
279 // | T ... T | . | | T ... T | . |
280 // | . . | . | | . . | |
281 // | T ... T | | | T ... T | O |
282 // +------------------+ | +------------------+---+
283 // | O ... O | | O ... O |
284 // +----------------------+ +----------------------+
286 float width
= aRect
.width
;
287 float height
= aRect
.height
;
289 if (aParams
.isCustom
) {
290 trackColor
= aParams
.trackColor
;
292 trackColor
= aParams
.isOnDarkBackground
? NS_RGBA(46, 46, 46, 255)
293 : NS_RGBA(250, 250, 250, 255);
295 ScrollbarTrackDecorationColors colors
=
296 ComputeScrollbarTrackDecorationColors(trackColor
);
301 {colors
.mInnerColor
, {0.0f
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
302 {colors
.mShadowColor
,
303 {1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
304 {colors
.mShadowColor
,
305 {0.0f
, 1.0f
* aScale
, 2.0f
* aScale
, 1.0f
* aScale
}},
306 {trackColor
, {2.0f
* aScale
, 0.0f
, width
- 3.0f
* aScale
, 2.0f
* aScale
}},
308 {0.0f
, 2.0f
* aScale
, width
- 1.0f
* aScale
, height
- 3.0f
* aScale
}},
310 {width
- 1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, height
- 1.0f
* aScale
}},
312 {0.0f
, height
- 1.0f
* aScale
, width
, 1.0f
* aScale
}},
315 auto current
= aRects
.begin();
316 for (const auto& piece
: pieces
) {
317 Rect pieceRect
= piece
.relativeRect
+ aRect
.TopLeft();
319 pieceRect
.x
= aRect
.XMost() - piece
.relativeRect
.XMost();
321 *current
++ = {pieceRect
, piece
.color
};
326 template <typename PaintBackendData
>
327 void ScrollbarDrawingCocoa::DoPaintScrollbarThumb(
328 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
329 bool aHorizontal
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
330 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
331 const DPIRatio
& aDpiRatio
) {
332 ScrollbarParams params
= ComputeScrollbarParams(aFrame
, aStyle
, aHorizontal
);
333 auto thumb
= GetThumbRect(aRect
.ToUnknownRect(), params
, aDpiRatio
.scale
);
334 auto thumbRect
= LayoutDeviceRect::FromUnknownRect(thumb
.mRect
);
335 LayoutDeviceCoord radius
=
336 (params
.isHorizontal
? thumbRect
.Height() : thumbRect
.Width()) / 2.0f
;
337 ThemeDrawing::PaintRoundedRectWithRadius(
338 aPaintData
, thumbRect
, thumbRect
, sRGBColor::FromABGR(thumb
.mFillColor
),
339 sRGBColor::White(0.0f
), 0.0f
, radius
/ aDpiRatio
, aDpiRatio
);
340 if (!thumb
.mStrokeColor
) {
344 // Paint the stroke if needed.
345 thumbRect
.Inflate(thumb
.mStrokeOutset
+ thumb
.mStrokeWidth
);
347 (params
.isHorizontal
? thumbRect
.Height() : thumbRect
.Width()) / 2.0f
;
348 ThemeDrawing::PaintRoundedRectWithRadius(
349 aPaintData
, thumbRect
, sRGBColor::White(0.0f
),
350 sRGBColor::FromABGR(thumb
.mStrokeColor
), thumb
.mStrokeWidth
,
351 radius
/ aDpiRatio
, aDpiRatio
);
354 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
355 DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
, bool aHorizontal
,
356 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
357 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
358 const Colors
&, const DPIRatio
& aDpiRatio
) {
359 // TODO: Maybe respect the UseSystemColors setting?
360 DoPaintScrollbarThumb(aDt
, aRect
, aHorizontal
, aFrame
, aStyle
, aElementState
,
361 aDocumentState
, aDpiRatio
);
365 bool ScrollbarDrawingCocoa::PaintScrollbarThumb(
366 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
367 bool aHorizontal
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
368 const EventStates
& aElementState
, const EventStates
& aDocumentState
,
369 const Colors
&, const DPIRatio
& aDpiRatio
) {
370 // TODO: Maybe respect the UseSystemColors setting?
371 DoPaintScrollbarThumb(aWrData
, aRect
, aHorizontal
, aFrame
, aStyle
,
372 aElementState
, aDocumentState
, aDpiRatio
);
376 template <typename PaintBackendData
>
377 void ScrollbarDrawingCocoa::DoPaintScrollbarTrack(
378 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
379 bool aHorizontal
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
380 const EventStates
& aDocumentState
, const DPIRatio
& aDpiRatio
) {
381 ScrollbarParams params
= ComputeScrollbarParams(aFrame
, aStyle
, aHorizontal
);
382 ScrollbarTrackRects rects
;
383 if (GetScrollbarTrackRects(aRect
.ToUnknownRect(), params
, aDpiRatio
.scale
,
385 for (const auto& rect
: rects
) {
386 ThemeDrawing::FillRect(aPaintData
,
387 LayoutDeviceRect::FromUnknownRect(rect
.mRect
),
388 sRGBColor::FromABGR(rect
.mColor
));
393 bool ScrollbarDrawingCocoa::PaintScrollbarTrack(
394 DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
, bool aHorizontal
,
395 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
396 const EventStates
& aDocumentState
, const Colors
&,
397 const DPIRatio
& aDpiRatio
) {
398 // TODO: Maybe respect the UseSystemColors setting?
399 DoPaintScrollbarTrack(aDt
, aRect
, aHorizontal
, aFrame
, aStyle
, aDocumentState
,
404 bool ScrollbarDrawingCocoa::PaintScrollbarTrack(
405 WebRenderBackendData
& aWrData
, const LayoutDeviceRect
& aRect
,
406 bool aHorizontal
, nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
407 const EventStates
& aDocumentState
, const Colors
&,
408 const DPIRatio
& aDpiRatio
) {
409 // TODO: Maybe respect the UseSystemColors setting?
410 DoPaintScrollbarTrack(aWrData
, aRect
, aHorizontal
, aFrame
, aStyle
,
411 aDocumentState
, aDpiRatio
);
415 template <typename PaintBackendData
>
416 void ScrollbarDrawingCocoa::DoPaintScrollCorner(
417 PaintBackendData
& aPaintData
, const LayoutDeviceRect
& aRect
,
418 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
419 const EventStates
& aDocumentState
, const DPIRatio
& aDpiRatio
) {
420 ScrollbarParams params
= ComputeScrollbarParams(aFrame
, aStyle
, false);
421 ScrollCornerRects rects
;
422 if (GetScrollCornerRects(aRect
.ToUnknownRect(), params
, aDpiRatio
.scale
,
424 for (const auto& rect
: rects
) {
425 ThemeDrawing::FillRect(aPaintData
,
426 LayoutDeviceRect::FromUnknownRect(rect
.mRect
),
427 sRGBColor::FromABGR(rect
.mColor
));
432 bool ScrollbarDrawingCocoa::PaintScrollCorner(
433 DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
, nsIFrame
* aFrame
,
434 const ComputedStyle
& aStyle
, const EventStates
& aDocumentState
,
435 const Colors
&, const DPIRatio
& aDpiRatio
) {
436 // TODO: Maybe respect the UseSystemColors setting?
437 DoPaintScrollCorner(aDt
, aRect
, aFrame
, aStyle
, aDocumentState
, aDpiRatio
);
441 bool ScrollbarDrawingCocoa::PaintScrollCorner(WebRenderBackendData
& aWrData
,
442 const LayoutDeviceRect
& aRect
,
444 const ComputedStyle
& aStyle
,
445 const EventStates
& aDocumentState
,
447 const DPIRatio
& aDpiRatio
) {
448 // TODO: Maybe respect the UseSystemColors setting?
449 DoPaintScrollCorner(aWrData
, aRect
, aFrame
, aStyle
, aDocumentState
,
454 void ScrollbarDrawingCocoa::RecomputeScrollbarParams() {
455 uint32_t defaultSize
= 17;
456 uint32_t overrideSize
=
457 StaticPrefs::widget_non_native_theme_scrollbar_size_override();
458 if (overrideSize
> 0) {
459 defaultSize
= overrideSize
;
461 mHorizontalScrollbarHeight
= mVerticalScrollbarWidth
= defaultSize
;