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/gfx/PathHelpers.h"
10 #include "mozilla/RelativeLuminanceUtils.h"
11 #include "nsLayoutUtils.h"
13 #include "nsLookAndFeel.h"
14 #include "nsContainerFrame.h"
15 #include "nsNativeTheme.h"
23 static nsIFrame
* GetParentScrollbarFrame(nsIFrame
* aFrame
) {
24 // Walk our parents to find a scrollbar frame
25 nsIFrame
* scrollbarFrame
= aFrame
;
27 if (scrollbarFrame
->IsScrollbarFrame()) {
30 } while ((scrollbarFrame
= scrollbarFrame
->GetParent()));
32 // We return null if we can't find a parent scrollbar frame
33 return scrollbarFrame
;
36 static bool IsParentScrollbarRolledOver(nsIFrame
* aFrame
) {
37 nsIFrame
* scrollbarFrame
= GetParentScrollbarFrame(aFrame
);
38 return nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
) != 0
39 ? nsNativeTheme::CheckBooleanAttr(scrollbarFrame
, nsGkAtoms::hover
)
40 : nsNativeTheme::GetContentState(scrollbarFrame
,
41 StyleAppearance::None
)
42 .HasState(NS_EVENT_STATE_HOVER
);
45 LayoutDeviceIntSize
ScrollbarDrawingMac::GetMinimumWidgetSize(
46 StyleAppearance aAppearance
, nsIFrame
* aFrame
, float aDpiRatio
) {
47 auto fn
= [](StyleAppearance aAppearance
, nsIFrame
* aFrame
) -> IntSize
{
48 switch (aAppearance
) {
49 case StyleAppearance::ScrollbarthumbHorizontal
:
50 return IntSize
{26, 0};
51 case StyleAppearance::ScrollbarthumbVertical
:
52 return IntSize
{0, 26};
53 case StyleAppearance::ScrollbarVertical
:
54 case StyleAppearance::ScrollbarHorizontal
:
55 case StyleAppearance::ScrollbartrackVertical
:
56 case StyleAppearance::ScrollbartrackHorizontal
: {
57 ComputedStyle
* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
59 style
->StyleUIReset()->mScrollbarWidth
== StyleScrollbarWidth::Thin
;
60 if (nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
) !=
63 return IntSize
{14, 14};
65 return IntSize
{16, 16};
68 return IntSize
{11, 11};
70 return IntSize
{15, 15};
72 case StyleAppearance::MozMenulistArrowButton
:
73 case StyleAppearance::ScrollbarNonDisappearing
:
74 return IntSize
{15, 15};
75 case StyleAppearance::ScrollbarbuttonUp
:
76 case StyleAppearance::ScrollbarbuttonDown
:
77 return IntSize
{15, 16};
78 case StyleAppearance::ScrollbarbuttonLeft
:
79 case StyleAppearance::ScrollbarbuttonRight
:
80 return IntSize
{16, 15};
86 IntSize minSize
= fn(aAppearance
, aFrame
);
87 if (aDpiRatio
>= 2.0f
) {
88 return LayoutDeviceIntSize
{minSize
.width
* 2, minSize
.height
* 2};
90 return LayoutDeviceIntSize
{minSize
.width
, minSize
.height
};
93 ScrollbarParams
ScrollbarDrawingMac::ComputeScrollbarParams(
94 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
, bool aIsHorizontal
) {
95 ScrollbarParams params
;
97 nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars
) != 0;
98 params
.rolledOver
= IsParentScrollbarRolledOver(aFrame
);
100 aStyle
.StyleUIReset()->mScrollbarWidth
== StyleScrollbarWidth::Thin
;
101 params
.rtl
= nsNativeTheme::IsFrameRTL(aFrame
);
102 params
.horizontal
= aIsHorizontal
;
103 params
.onDarkBackground
= nsNativeTheme::IsDarkBackground(aFrame
);
104 // Don't use custom scrollbars for overlay scrollbars since they are
105 // generally good enough for use cases of custom scrollbars.
106 if (!params
.overlay
) {
107 const nsStyleUI
* ui
= aStyle
.StyleUI();
108 if (ui
->HasCustomScrollbars()) {
109 const auto& colors
= ui
->mScrollbarColor
.AsColors();
110 params
.custom
= true;
111 params
.trackColor
= colors
.track
.CalcColor(aStyle
);
112 params
.faceColor
= colors
.thumb
.CalcColor(aStyle
);
119 void ScrollbarDrawingMac::DrawScrollbarThumb(DrawTarget
& aDT
, const Rect
& aRect
,
120 const ScrollbarParams
& aParams
) {
121 // Compute the thumb thickness. This varies based on aParams.small,
122 // aParams.overlay and aParams.rolledOver. non-overlay: 6 / 8, overlay
123 // non-hovered: 5 / 7, overlay hovered: 9 / 11
124 float thickness
= aParams
.small
? 6.0f
: 8.0f
;
125 if (aParams
.overlay
) {
127 if (aParams
.rolledOver
) {
132 // Compute the thumb rect.
133 float outerSpacing
= (aParams
.overlay
|| aParams
.small
) ? 1.0f
: 2.0f
;
134 Rect thumbRect
= aRect
;
135 thumbRect
.Deflate(1.0f
);
136 if (aParams
.horizontal
) {
137 float bottomEdge
= thumbRect
.YMost() - outerSpacing
;
138 thumbRect
.SetBoxY(bottomEdge
- thickness
, bottomEdge
);
141 float leftEdge
= thumbRect
.X() + outerSpacing
;
142 thumbRect
.SetBoxX(leftEdge
, leftEdge
+ thickness
);
144 float rightEdge
= thumbRect
.XMost() - outerSpacing
;
145 thumbRect
.SetBoxX(rightEdge
- thickness
, rightEdge
);
149 // Compute the thumb fill color.
151 if (aParams
.custom
) {
152 faceColor
= aParams
.faceColor
;
154 if (aParams
.overlay
) {
155 faceColor
= aParams
.onDarkBackground
? NS_RGBA(255, 255, 255, 128)
156 : NS_RGBA(0, 0, 0, 128);
158 faceColor
= aParams
.rolledOver
? NS_RGBA(125, 125, 125, 255)
159 : NS_RGBA(194, 194, 194, 255);
163 // Fill the thumb shape with the color.
165 (aParams
.horizontal
? thumbRect
.Height() : thumbRect
.Width()) / 2.0f
;
166 aDT
.FillRoundedRect(RoundedRect(thumbRect
, RectCornerRadii(cornerRadius
)),
167 ColorPattern(ToDeviceColor(faceColor
)));
169 // Overlay scrollbars have an additional stroke around the fill.
170 if (aParams
.overlay
) {
171 float strokeOutset
= aParams
.onDarkBackground
? 0.3f
: 0.5f
;
172 float strokeWidth
= aParams
.onDarkBackground
? 0.6f
: 0.8f
;
173 nscolor strokeColor
= aParams
.onDarkBackground
? NS_RGBA(0, 0, 0, 48)
174 : NS_RGBA(255, 255, 255, 48);
175 Rect thumbStrokeRect
= thumbRect
;
176 thumbStrokeRect
.Inflate(strokeOutset
);
177 float strokeRadius
= (aParams
.horizontal
? thumbStrokeRect
.Height()
178 : thumbStrokeRect
.Width()) /
181 RefPtr
<Path
> path
= MakePathForRoundedRect(aDT
, thumbStrokeRect
,
182 RectCornerRadii(strokeRadius
));
183 aDT
.Stroke(path
, ColorPattern(ToDeviceColor(strokeColor
)),
184 StrokeOptions(strokeWidth
));
188 struct ScrollbarTrackDecorationColors
{
189 nscolor mInnerColor
= 0;
190 nscolor mShadowColor
= 0;
191 nscolor mOuterColor
= 0;
194 static ScrollbarTrackDecorationColors
ComputeScrollbarTrackDecorationColors(
195 nscolor aTrackColor
) {
196 ScrollbarTrackDecorationColors result
;
197 float luminance
= RelativeLuminanceUtils::Compute(aTrackColor
);
198 if (luminance
>= 0.5f
) {
200 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.836f
);
201 result
.mShadowColor
=
202 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.982f
);
204 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 0.886f
);
207 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.196f
);
208 result
.mShadowColor
=
209 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.018f
);
211 RelativeLuminanceUtils::Adjust(aTrackColor
, luminance
* 1.129f
);
216 void ScrollbarDrawingMac::DrawScrollbarTrack(DrawTarget
& aDT
, const Rect
& aRect
,
217 const ScrollbarParams
& aParams
) {
218 if (aParams
.overlay
&& !aParams
.rolledOver
) {
219 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
224 if (aParams
.custom
) {
225 trackColor
= aParams
.trackColor
;
227 if (aParams
.overlay
) {
228 trackColor
= aParams
.onDarkBackground
? NS_RGBA(201, 201, 201, 38)
229 : NS_RGBA(250, 250, 250, 191);
231 trackColor
= NS_RGBA(250, 250, 250, 255);
235 float thickness
= aParams
.horizontal
? 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
},
246 {colors
.mShadowColor
, 1.0f
},
247 {trackColor
, thickness
- 3.0f
},
248 {colors
.mOuterColor
, 1.0f
},
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 float accumulatedThickness
= 0.0f
;
256 for (const auto& segment
: segments
) {
257 Rect segmentRect
= aRect
;
258 float startThickness
= accumulatedThickness
;
259 float endThickness
= startThickness
+ segment
.thickness
;
260 if (aParams
.horizontal
) {
261 segmentRect
.SetBoxY(aRect
.Y() + startThickness
, aRect
.Y() + endThickness
);
264 segmentRect
.SetBoxX(aRect
.XMost() - endThickness
,
265 aRect
.XMost() - startThickness
);
267 segmentRect
.SetBoxX(aRect
.X() + startThickness
,
268 aRect
.X() + endThickness
);
271 aDT
.FillRect(segmentRect
, ColorPattern(ToDeviceColor(segment
.color
)));
272 accumulatedThickness
= endThickness
;
276 void ScrollbarDrawingMac::DrawScrollCorner(DrawTarget
& aDT
, const Rect
& aRect
,
277 const ScrollbarParams
& aParams
) {
278 if (aParams
.overlay
&& !aParams
.rolledOver
) {
279 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
283 // Draw the following scroll corner.
285 // Output: Rectangles:
286 // +---+---+----------+---+ +---+---+----------+---+
287 // | I | S | T ... T | O | | I | S | T ... T | O |
288 // +---+ | | | +---+---+ | |
289 // | S S | T ... T | | | S S | T ... T | . |
290 // +-------+ | . | +-------+----------+ . |
291 // | T ... T | . | | T ... T | . |
292 // | . . | . | | . . | |
293 // | T ... T | | | T ... T | O |
294 // +------------------+ | +------------------+---+
295 // | O ... O | | O ... O |
296 // +----------------------+ +----------------------+
298 float width
= aRect
.width
;
299 float height
= aRect
.height
;
301 aParams
.custom
? aParams
.trackColor
: NS_RGBA(250, 250, 250, 255);
302 ScrollbarTrackDecorationColors colors
=
303 ComputeScrollbarTrackDecorationColors(trackColor
);
308 {colors
.mInnerColor
, {0.0f
, 0.0f
, 1.0f
, 1.0f
}},
309 {colors
.mShadowColor
, {1.0f
, 0.0f
, 1.0f
, 1.0f
}},
310 {colors
.mShadowColor
, {0.0f
, 1.0f
, 2.0f
, 1.0f
}},
311 {trackColor
, {2.0f
, 0.0f
, width
- 3.0f
, 2.0f
}},
312 {trackColor
, {0.0f
, 2.0f
, width
- 1.0f
, height
- 3.0f
}},
313 {colors
.mOuterColor
, {width
- 1.0f
, 0.0f
, 1.0f
, height
- 1.0f
}},
314 {colors
.mOuterColor
, {0.0f
, height
- 1.0f
, width
, 1.0f
}},
317 for (const auto& piece
: pieces
) {
318 Rect pieceRect
= piece
.relativeRect
+ aRect
.TopLeft();
320 pieceRect
.x
= aRect
.XMost() - piece
.relativeRect
.XMost();
322 aDT
.FillRect(pieceRect
, ColorPattern(ToDeviceColor(piece
.color
)));
326 } // namespace widget
327 } // namespace mozilla