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 "ScrollbarDrawingMac.h"
8 #include "mozilla/gfx/Helpers.h"
9 #include "mozilla/RelativeLuminanceUtils.h"
10 #include "nsLayoutUtils.h"
12 #include "nsLookAndFeel.h"
13 #include "nsContainerFrame.h"
14 #include "nsNativeTheme.h"
22 static nsIFrame
* GetParentScrollbarFrame(nsIFrame
* aFrame
) {
23 // Walk our parents to find a scrollbar frame
24 nsIFrame
* scrollbarFrame
= aFrame
;
26 if (scrollbarFrame
->IsScrollbarFrame()) {
29 } while ((scrollbarFrame
= scrollbarFrame
->GetParent()));
31 // We return null if we can't find a parent scrollbar frame
32 return scrollbarFrame
;
35 static bool IsParentScrollbarRolledOver(nsIFrame
* aFrame
) {
36 nsIFrame
* scrollbarFrame
= GetParentScrollbarFrame(aFrame
);
37 return nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
) != 0
38 ? nsNativeTheme::CheckBooleanAttr(scrollbarFrame
, nsGkAtoms::hover
)
39 : nsNativeTheme::GetContentState(scrollbarFrame
,
40 StyleAppearance::None
)
41 .HasState(NS_EVENT_STATE_HOVER
);
44 CSSIntCoord
ScrollbarDrawingMac::GetScrollbarSize(StyleScrollbarWidth aWidth
,
46 bool isSmall
= aWidth
== StyleScrollbarWidth::Thin
;
48 return isSmall
? 14 : 16;
50 return isSmall
? 11 : 15;
53 LayoutDeviceIntCoord
ScrollbarDrawingMac::GetScrollbarSize(
54 StyleScrollbarWidth aWidth
, bool aOverlay
, float aDpiRatio
) {
55 CSSIntCoord size
= GetScrollbarSize(aWidth
, aOverlay
);
56 if (aDpiRatio
>= 2.0f
) {
57 return int32_t(size
) * 2;
62 LayoutDeviceIntSize
ScrollbarDrawingMac::GetMinimumWidgetSize(
63 StyleAppearance aAppearance
, nsIFrame
* aFrame
, float aDpiRatio
) {
65 switch (aAppearance
) {
66 case StyleAppearance::ScrollbarthumbHorizontal
:
67 return IntSize
{26, 0};
68 case StyleAppearance::ScrollbarthumbVertical
:
69 return IntSize
{0, 26};
70 case StyleAppearance::ScrollbarVertical
:
71 case StyleAppearance::ScrollbarHorizontal
:
72 case StyleAppearance::ScrollbartrackVertical
:
73 case StyleAppearance::ScrollbartrackHorizontal
: {
74 ComputedStyle
* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
75 auto scrollbarWidth
= style
->StyleUIReset()->mScrollbarWidth
;
76 auto size
= GetScrollbarSize(
78 LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
));
79 return IntSize
{size
, size
};
81 case StyleAppearance::MozMenulistArrowButton
: {
83 GetScrollbarSize(StyleScrollbarWidth::Auto
, /* aOverlay = */ false);
84 return IntSize
{size
, size
};
86 case StyleAppearance::ScrollbarbuttonUp
:
87 case StyleAppearance::ScrollbarbuttonDown
:
88 return IntSize
{15, 16};
89 case StyleAppearance::ScrollbarbuttonLeft
:
90 case StyleAppearance::ScrollbarbuttonRight
:
91 return IntSize
{16, 15};
97 if (aDpiRatio
>= 2.0f
) {
98 return LayoutDeviceIntSize
{minSize
.width
* 2, minSize
.height
* 2};
100 return LayoutDeviceIntSize
{minSize
.width
, minSize
.height
};
103 ScrollbarParams
ScrollbarDrawingMac::ComputeScrollbarParams(
104 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
, bool aIsHorizontal
) {
105 ScrollbarParams params
;
107 nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
) != 0;
108 params
.rolledOver
= IsParentScrollbarRolledOver(aFrame
);
110 aStyle
.StyleUIReset()->mScrollbarWidth
== StyleScrollbarWidth::Thin
;
111 params
.rtl
= nsNativeTheme::IsFrameRTL(aFrame
);
112 params
.horizontal
= aIsHorizontal
;
113 params
.onDarkBackground
= !StaticPrefs::widget_disable_dark_scrollbar() &&
114 nsNativeTheme::IsDarkBackground(aFrame
);
115 // Don't use custom scrollbars for overlay scrollbars since they are
116 // generally good enough for use cases of custom scrollbars.
117 if (!params
.overlay
) {
118 const nsStyleUI
* ui
= aStyle
.StyleUI();
119 if (ui
->HasCustomScrollbars()) {
120 const auto& colors
= ui
->mScrollbarColor
.AsColors();
121 params
.custom
= true;
122 params
.trackColor
= colors
.track
.CalcColor(aStyle
);
123 params
.faceColor
= colors
.thumb
.CalcColor(aStyle
);
130 auto ScrollbarDrawingMac::GetThumbRect(const Rect
& aRect
,
131 const ScrollbarParams
& aParams
,
132 float aScale
) -> ThumbRect
{
133 // This matches the sizing checks in GetMinimumWidgetSize etc.
134 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
136 // Compute the thumb thickness. This varies based on aParams.small,
137 // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
138 // non-hovered: 5 / 7, overlay hovered: 9 / 11
139 float thickness
= aParams
.small
? 6.0f
: 8.0f
;
140 if (aParams
.overlay
) {
142 if (aParams
.rolledOver
) {
148 // Compute the thumb rect.
149 const float outerSpacing
=
150 ((aParams
.overlay
|| aParams
.small
) ? 1.0f
: 2.0f
) * aScale
;
151 Rect thumbRect
= aRect
;
152 thumbRect
.Deflate(1.0f
* aScale
);
153 if (aParams
.horizontal
) {
154 float bottomEdge
= thumbRect
.YMost() - outerSpacing
;
155 thumbRect
.SetBoxY(bottomEdge
- thickness
, bottomEdge
);
158 float leftEdge
= thumbRect
.X() + outerSpacing
;
159 thumbRect
.SetBoxX(leftEdge
, leftEdge
+ thickness
);
161 float rightEdge
= thumbRect
.XMost() - outerSpacing
;
162 thumbRect
.SetBoxX(rightEdge
- thickness
, rightEdge
);
166 // Compute the thumb fill color.
168 if (aParams
.custom
) {
169 faceColor
= aParams
.faceColor
;
171 if (aParams
.overlay
) {
172 faceColor
= aParams
.onDarkBackground
? NS_RGBA(255, 255, 255, 128)
173 : NS_RGBA(0, 0, 0, 128);
174 } else if (aParams
.onDarkBackground
) {
175 faceColor
= aParams
.rolledOver
? NS_RGBA(158, 158, 158, 255)
176 : NS_RGBA(117, 117, 117, 255);
178 faceColor
= aParams
.rolledOver
? NS_RGBA(125, 125, 125, 255)
179 : NS_RGBA(194, 194, 194, 255);
183 nscolor strokeColor
= 0;
184 float strokeOutset
= 0.0f
;
185 float strokeWidth
= 0.0f
;
187 // Overlay scrollbars have an additional stroke around the fill.
188 if (aParams
.overlay
) {
189 strokeOutset
= (aParams
.onDarkBackground
? 0.3f
: 0.5f
) * aScale
;
190 strokeWidth
= (aParams
.onDarkBackground
? 0.6f
: 0.8f
) * aScale
;
192 strokeColor
= aParams
.onDarkBackground
? NS_RGBA(0, 0, 0, 48)
193 : NS_RGBA(255, 255, 255, 48);
196 return {thumbRect
, faceColor
, strokeColor
, strokeWidth
, strokeOutset
};
199 struct ScrollbarTrackDecorationColors
{
200 nscolor mInnerColor
= 0;
201 nscolor mShadowColor
= 0;
202 nscolor mOuterColor
= 0;
205 static ScrollbarTrackDecorationColors
ComputeScrollbarTrackDecorationColors(
206 nscolor aTrackColor
) {
207 ScrollbarTrackDecorationColors result
;
208 float luminance
= RelativeLuminanceUtils::Compute(aTrackColor
);
209 if (luminance
>= 0.5f
) {
211 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.836f
);
212 result
.mShadowColor
=
213 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.982f
);
215 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.886f
);
218 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.196f
);
219 result
.mShadowColor
=
220 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.018f
);
222 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.129f
);
227 bool ScrollbarDrawingMac::GetScrollbarTrackRects(const Rect
& aRect
,
228 const ScrollbarParams
& aParams
,
230 ScrollbarTrackRects
& aRects
) {
231 if (aParams
.overlay
&& !aParams
.rolledOver
) {
232 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
236 // This matches the sizing checks in GetMinimumWidgetSize etc.
237 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
240 if (aParams
.custom
) {
241 trackColor
= aParams
.trackColor
;
243 if (aParams
.overlay
) {
244 trackColor
= aParams
.onDarkBackground
? NS_RGBA(201, 201, 201, 38)
245 : NS_RGBA(250, 250, 250, 191);
247 trackColor
= aParams
.onDarkBackground
? NS_RGBA(46, 46, 46, 255)
248 : NS_RGBA(250, 250, 250, 255);
252 float thickness
= aParams
.horizontal
? aRect
.height
: aRect
.width
;
254 // The scrollbar track is drawn as multiple non-overlapping segments, which
255 // make up lines of different widths and with slightly different shading.
256 ScrollbarTrackDecorationColors colors
=
257 ComputeScrollbarTrackDecorationColors(trackColor
);
262 {colors
.mInnerColor
, 1.0f
* aScale
},
263 {colors
.mShadowColor
, 1.0f
* aScale
},
264 {trackColor
, thickness
- 3.0f
* aScale
},
265 {colors
.mOuterColor
, 1.0f
* aScale
},
268 // Iterate over the segments "from inside to outside" and fill each segment.
269 // For horizontal scrollbars, iterate top to bottom.
270 // For vertical scrollbars, iterate left to right or right to left based on
272 auto current
= aRects
.begin();
273 float accumulatedThickness
= 0.0f
;
274 for (const auto& segment
: segments
) {
275 Rect segmentRect
= aRect
;
276 float startThickness
= accumulatedThickness
;
277 float endThickness
= startThickness
+ segment
.thickness
;
278 if (aParams
.horizontal
) {
279 segmentRect
.SetBoxY(aRect
.Y() + startThickness
, aRect
.Y() + endThickness
);
282 segmentRect
.SetBoxX(aRect
.XMost() - endThickness
,
283 aRect
.XMost() - startThickness
);
285 segmentRect
.SetBoxX(aRect
.X() + startThickness
,
286 aRect
.X() + endThickness
);
289 accumulatedThickness
= endThickness
;
290 *current
++ = {segmentRect
, segment
.color
};
296 bool ScrollbarDrawingMac::GetScrollCornerRects(const Rect
& aRect
,
297 const ScrollbarParams
& aParams
,
299 ScrollCornerRects
& aRects
) {
300 if (aParams
.overlay
&& !aParams
.rolledOver
) {
301 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
305 // This matches the sizing checks in GetMinimumWidgetSize etc.
306 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
308 // Draw the following scroll corner.
310 // Output: Rectangles:
311 // +---+---+----------+---+ +---+---+----------+---+
312 // | I | S | T ... T | O | | I | S | T ... T | O |
313 // +---+ | | | +---+---+ | |
314 // | S S | T ... T | | | S S | T ... T | . |
315 // +-------+ | . | +-------+----------+ . |
316 // | T ... T | . | | T ... T | . |
317 // | . . | . | | . . | |
318 // | T ... T | | | T ... T | O |
319 // +------------------+ | +------------------+---+
320 // | O ... O | | O ... O |
321 // +----------------------+ +----------------------+
323 float width
= aRect
.width
;
324 float height
= aRect
.height
;
326 if (aParams
.custom
) {
327 trackColor
= aParams
.trackColor
;
329 trackColor
= aParams
.onDarkBackground
? NS_RGBA(46, 46, 46, 255)
330 : NS_RGBA(250, 250, 250, 255);
332 ScrollbarTrackDecorationColors colors
=
333 ComputeScrollbarTrackDecorationColors(trackColor
);
338 {colors
.mInnerColor
, {0.0f
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
339 {colors
.mShadowColor
,
340 {1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
341 {colors
.mShadowColor
,
342 {0.0f
, 1.0f
* aScale
, 2.0f
* aScale
, 1.0f
* aScale
}},
343 {trackColor
, {2.0f
* aScale
, 0.0f
, width
- 3.0f
* aScale
, 2.0f
* aScale
}},
345 {0.0f
, 2.0f
* aScale
, width
- 1.0f
* aScale
, height
- 3.0f
* aScale
}},
347 {width
- 1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, height
- 1.0f
* aScale
}},
349 {0.0f
, height
- 1.0f
* aScale
, width
, 1.0f
* aScale
}},
352 auto current
= aRects
.begin();
353 for (const auto& piece
: pieces
) {
354 Rect pieceRect
= piece
.relativeRect
+ aRect
.TopLeft();
356 pieceRect
.x
= aRect
.XMost() - piece
.relativeRect
.XMost();
358 *current
++ = {pieceRect
, piece
.color
};
363 } // namespace widget
364 } // namespace mozilla