Bug 1734943 [wpt PR 31170] - Correct scrolling contents cull rect, a=testonly
[gecko.git] / widget / ScrollbarDrawingMac.cpp
blob37144f708560be228d533a4193d9193209a6b5ed
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"
11 #include "nsIFrame.h"
12 #include "nsLookAndFeel.h"
13 #include "nsContainerFrame.h"
14 #include "nsNativeTheme.h"
16 namespace mozilla {
18 using namespace gfx;
20 namespace widget {
22 static nsIFrame* GetParentScrollbarFrame(nsIFrame* aFrame) {
23 // Walk our parents to find a scrollbar frame
24 nsIFrame* scrollbarFrame = aFrame;
25 do {
26 if (scrollbarFrame->IsScrollbarFrame()) {
27 break;
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,
45 bool aOverlay) {
46 bool isSmall = aWidth == StyleScrollbarWidth::Thin;
47 if (aOverlay) {
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;
59 return int32_t(size);
62 LayoutDeviceIntSize ScrollbarDrawingMac::GetMinimumWidgetSize(
63 StyleAppearance aAppearance, nsIFrame* aFrame, float aDpiRatio) {
64 auto minSize = [&] {
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(
77 scrollbarWidth,
78 LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars));
79 return IntSize{size, size};
81 case StyleAppearance::MozMenulistArrowButton: {
82 auto size =
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};
92 default:
93 return IntSize{};
95 }();
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;
106 params.overlay =
107 nsLookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0;
108 params.rolledOver = IsParentScrollbarRolledOver(aFrame);
109 params.small =
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);
127 return params;
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) {
141 thickness -= 1.0f;
142 if (aParams.rolledOver) {
143 thickness += 4.0f;
146 thickness *= aScale;
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);
156 } else {
157 if (aParams.rtl) {
158 float leftEdge = thumbRect.X() + outerSpacing;
159 thumbRect.SetBoxX(leftEdge, leftEdge + thickness);
160 } else {
161 float rightEdge = thumbRect.XMost() - outerSpacing;
162 thumbRect.SetBoxX(rightEdge - thickness, rightEdge);
166 // Compute the thumb fill color.
167 nscolor faceColor;
168 if (aParams.custom) {
169 faceColor = aParams.faceColor;
170 } else {
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);
177 } else {
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) {
210 result.mInnerColor =
211 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836f);
212 result.mShadowColor =
213 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982f);
214 result.mOuterColor =
215 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886f);
216 } else {
217 result.mInnerColor =
218 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196f);
219 result.mShadowColor =
220 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018f);
221 result.mOuterColor =
222 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129f);
224 return result;
227 bool ScrollbarDrawingMac::GetScrollbarTrackRects(const Rect& aRect,
228 const ScrollbarParams& aParams,
229 float aScale,
230 ScrollbarTrackRects& aRects) {
231 if (aParams.overlay && !aParams.rolledOver) {
232 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
233 return false;
236 // This matches the sizing checks in GetMinimumWidgetSize etc.
237 aScale = aScale >= 2.0f ? 2.0f : 1.0f;
239 nscolor trackColor;
240 if (aParams.custom) {
241 trackColor = aParams.trackColor;
242 } else {
243 if (aParams.overlay) {
244 trackColor = aParams.onDarkBackground ? NS_RGBA(201, 201, 201, 38)
245 : NS_RGBA(250, 250, 250, 191);
246 } else {
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);
258 struct {
259 nscolor color;
260 float thickness;
261 } segments[] = {
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
271 // aParams.rtl.
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);
280 } else {
281 if (aParams.rtl) {
282 segmentRect.SetBoxX(aRect.XMost() - endThickness,
283 aRect.XMost() - startThickness);
284 } else {
285 segmentRect.SetBoxX(aRect.X() + startThickness,
286 aRect.X() + endThickness);
289 accumulatedThickness = endThickness;
290 *current++ = {segmentRect, segment.color};
293 return true;
296 bool ScrollbarDrawingMac::GetScrollCornerRects(const Rect& aRect,
297 const ScrollbarParams& aParams,
298 float aScale,
299 ScrollCornerRects& aRects) {
300 if (aParams.overlay && !aParams.rolledOver) {
301 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
302 return false;
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;
325 nscolor trackColor;
326 if (aParams.custom) {
327 trackColor = aParams.trackColor;
328 } else {
329 trackColor = aParams.onDarkBackground ? NS_RGBA(46, 46, 46, 255)
330 : NS_RGBA(250, 250, 250, 255);
332 ScrollbarTrackDecorationColors colors =
333 ComputeScrollbarTrackDecorationColors(trackColor);
334 struct {
335 nscolor color;
336 Rect relativeRect;
337 } pieces[] = {
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}},
344 {trackColor,
345 {0.0f, 2.0f * aScale, width - 1.0f * aScale, height - 3.0f * aScale}},
346 {colors.mOuterColor,
347 {width - 1.0f * aScale, 0.0f, 1.0f * aScale, height - 1.0f * aScale}},
348 {colors.mOuterColor,
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();
355 if (aParams.rtl) {
356 pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
358 *current++ = {pieceRect, piece.color};
360 return true;
363 } // namespace widget
364 } // namespace mozilla