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
= nsNativeTheme::IsDarkBackground(aFrame
);
114 // Don't use custom scrollbars for overlay scrollbars since they are
115 // generally good enough for use cases of custom scrollbars.
116 if (!params
.overlay
) {
117 const nsStyleUI
* ui
= aStyle
.StyleUI();
118 if (ui
->HasCustomScrollbars()) {
119 const auto& colors
= ui
->mScrollbarColor
.AsColors();
120 params
.custom
= true;
121 params
.trackColor
= colors
.track
.CalcColor(aStyle
);
122 params
.faceColor
= colors
.thumb
.CalcColor(aStyle
);
129 auto ScrollbarDrawingMac::GetThumbRect(const Rect
& aRect
,
130 const ScrollbarParams
& aParams
,
131 float aScale
) -> ThumbRect
{
132 // This matches the sizing checks in GetMinimumWidgetSize etc.
133 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
135 // Compute the thumb thickness. This varies based on aParams.small,
136 // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
137 // non-hovered: 5 / 7, overlay hovered: 9 / 11
138 float thickness
= aParams
.small
? 6.0f
: 8.0f
;
139 if (aParams
.overlay
) {
141 if (aParams
.rolledOver
) {
147 // Compute the thumb rect.
148 const float outerSpacing
=
149 ((aParams
.overlay
|| aParams
.small
) ? 1.0f
: 2.0f
) * aScale
;
150 Rect thumbRect
= aRect
;
151 thumbRect
.Deflate(1.0f
* aScale
);
152 if (aParams
.horizontal
) {
153 float bottomEdge
= thumbRect
.YMost() - outerSpacing
;
154 thumbRect
.SetBoxY(bottomEdge
- thickness
, bottomEdge
);
157 float leftEdge
= thumbRect
.X() + outerSpacing
;
158 thumbRect
.SetBoxX(leftEdge
, leftEdge
+ thickness
);
160 float rightEdge
= thumbRect
.XMost() - outerSpacing
;
161 thumbRect
.SetBoxX(rightEdge
- thickness
, rightEdge
);
165 // Compute the thumb fill color.
167 if (aParams
.custom
) {
168 faceColor
= aParams
.faceColor
;
170 if (aParams
.overlay
) {
171 faceColor
= aParams
.onDarkBackground
? NS_RGBA(255, 255, 255, 128)
172 : NS_RGBA(0, 0, 0, 128);
174 faceColor
= aParams
.rolledOver
? NS_RGBA(125, 125, 125, 255)
175 : NS_RGBA(194, 194, 194, 255);
179 nscolor strokeColor
= 0;
180 float strokeOutset
= 0.0f
;
181 float strokeWidth
= 0.0f
;
183 // Overlay scrollbars have an additional stroke around the fill.
184 if (aParams
.overlay
) {
185 strokeOutset
= (aParams
.onDarkBackground
? 0.3f
: 0.5f
) * aScale
;
186 strokeWidth
= (aParams
.onDarkBackground
? 0.6f
: 0.8f
) * aScale
;
188 strokeColor
= aParams
.onDarkBackground
? NS_RGBA(0, 0, 0, 48)
189 : NS_RGBA(255, 255, 255, 48);
192 return {thumbRect
, faceColor
, strokeColor
, strokeWidth
, strokeOutset
};
195 struct ScrollbarTrackDecorationColors
{
196 nscolor mInnerColor
= 0;
197 nscolor mShadowColor
= 0;
198 nscolor mOuterColor
= 0;
201 static ScrollbarTrackDecorationColors
ComputeScrollbarTrackDecorationColors(
202 nscolor aTrackColor
) {
203 ScrollbarTrackDecorationColors result
;
204 float luminance
= RelativeLuminanceUtils::Compute(aTrackColor
);
205 if (luminance
>= 0.5f
) {
207 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.836f
);
208 result
.mShadowColor
=
209 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.982f
);
211 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.886f
);
214 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.196f
);
215 result
.mShadowColor
=
216 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.018f
);
218 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.129f
);
223 bool ScrollbarDrawingMac::GetScrollbarTrackRects(const Rect
& aRect
,
224 const ScrollbarParams
& aParams
,
226 ScrollbarTrackRects
& aRects
) {
227 if (aParams
.overlay
&& !aParams
.rolledOver
) {
228 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
232 // This matches the sizing checks in GetMinimumWidgetSize etc.
233 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
236 if (aParams
.custom
) {
237 trackColor
= aParams
.trackColor
;
239 if (aParams
.overlay
) {
240 trackColor
= aParams
.onDarkBackground
? NS_RGBA(201, 201, 201, 38)
241 : NS_RGBA(250, 250, 250, 191);
243 trackColor
= NS_RGBA(250, 250, 250, 255);
247 float thickness
= aParams
.horizontal
? aRect
.height
: aRect
.width
;
249 // The scrollbar track is drawn as multiple non-overlapping segments, which
250 // make up lines of different widths and with slightly different shading.
251 ScrollbarTrackDecorationColors colors
=
252 ComputeScrollbarTrackDecorationColors(trackColor
);
257 {colors
.mInnerColor
, 1.0f
* aScale
},
258 {colors
.mShadowColor
, 1.0f
* aScale
},
259 {trackColor
, thickness
- 3.0f
* aScale
},
260 {colors
.mOuterColor
, 1.0f
* aScale
},
263 // Iterate over the segments "from inside to outside" and fill each segment.
264 // For horizontal scrollbars, iterate top to bottom.
265 // For vertical scrollbars, iterate left to right or right to left based on
267 auto current
= aRects
.begin();
268 float accumulatedThickness
= 0.0f
;
269 for (const auto& segment
: segments
) {
270 Rect segmentRect
= aRect
;
271 float startThickness
= accumulatedThickness
;
272 float endThickness
= startThickness
+ segment
.thickness
;
273 if (aParams
.horizontal
) {
274 segmentRect
.SetBoxY(aRect
.Y() + startThickness
, aRect
.Y() + endThickness
);
277 segmentRect
.SetBoxX(aRect
.XMost() - endThickness
,
278 aRect
.XMost() - startThickness
);
280 segmentRect
.SetBoxX(aRect
.X() + startThickness
,
281 aRect
.X() + endThickness
);
284 accumulatedThickness
= endThickness
;
285 *current
++ = {segmentRect
, segment
.color
};
291 bool ScrollbarDrawingMac::GetScrollCornerRects(const Rect
& aRect
,
292 const ScrollbarParams
& aParams
,
294 ScrollCornerRects
& aRects
) {
295 if (aParams
.overlay
&& !aParams
.rolledOver
) {
296 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
300 // This matches the sizing checks in GetMinimumWidgetSize etc.
301 aScale
= aScale
>= 2.0f
? 2.0f
: 1.0f
;
303 // Draw the following scroll corner.
305 // Output: Rectangles:
306 // +---+---+----------+---+ +---+---+----------+---+
307 // | I | S | T ... T | O | | I | S | T ... T | O |
308 // +---+ | | | +---+---+ | |
309 // | S S | T ... T | | | S S | T ... T | . |
310 // +-------+ | . | +-------+----------+ . |
311 // | T ... T | . | | T ... T | . |
312 // | . . | . | | . . | |
313 // | T ... T | | | T ... T | O |
314 // +------------------+ | +------------------+---+
315 // | O ... O | | O ... O |
316 // +----------------------+ +----------------------+
318 float width
= aRect
.width
;
319 float height
= aRect
.height
;
321 aParams
.custom
? aParams
.trackColor
: NS_RGBA(250, 250, 250, 255);
322 ScrollbarTrackDecorationColors colors
=
323 ComputeScrollbarTrackDecorationColors(trackColor
);
328 {colors
.mInnerColor
, {0.0f
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
329 {colors
.mShadowColor
,
330 {1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, 1.0f
* aScale
}},
331 {colors
.mShadowColor
,
332 {0.0f
, 1.0f
* aScale
, 2.0f
* aScale
, 1.0f
* aScale
}},
333 {trackColor
, {2.0f
* aScale
, 0.0f
, width
- 3.0f
* aScale
, 2.0f
* aScale
}},
335 {0.0f
, 2.0f
* aScale
, width
- 1.0f
* aScale
, height
- 3.0f
* aScale
}},
337 {width
- 1.0f
* aScale
, 0.0f
, 1.0f
* aScale
, height
- 1.0f
* aScale
}},
339 {0.0f
, height
- 1.0f
* aScale
, width
, 1.0f
* aScale
}},
342 auto current
= aRects
.begin();
343 for (const auto& piece
: pieces
) {
344 Rect pieceRect
= piece
.relativeRect
+ aRect
.TopLeft();
346 pieceRect
.x
= aRect
.XMost() - piece
.relativeRect
.XMost();
348 *current
++ = {pieceRect
, piece
.color
};
353 } // namespace widget
354 } // namespace mozilla