Bug 1697672 [wpt PR 28012] - Disable SVG composited animation if effective zoom is...
[gecko.git] / widget / ScrollbarDrawingMac.cpp
blob294b941644a89dc81cfa43ffe14024f21d92849e
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 = 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);
126 return params;
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) {
140 thickness -= 1.0f;
141 if (aParams.rolledOver) {
142 thickness += 4.0f;
145 thickness *= aScale;
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);
155 } else {
156 if (aParams.rtl) {
157 float leftEdge = thumbRect.X() + outerSpacing;
158 thumbRect.SetBoxX(leftEdge, leftEdge + thickness);
159 } else {
160 float rightEdge = thumbRect.XMost() - outerSpacing;
161 thumbRect.SetBoxX(rightEdge - thickness, rightEdge);
165 // Compute the thumb fill color.
166 nscolor faceColor;
167 if (aParams.custom) {
168 faceColor = aParams.faceColor;
169 } else {
170 if (aParams.overlay) {
171 faceColor = aParams.onDarkBackground ? NS_RGBA(255, 255, 255, 128)
172 : NS_RGBA(0, 0, 0, 128);
173 } else {
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) {
206 result.mInnerColor =
207 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.836f);
208 result.mShadowColor =
209 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.982f);
210 result.mOuterColor =
211 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 0.886f);
212 } else {
213 result.mInnerColor =
214 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.196f);
215 result.mShadowColor =
216 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.018f);
217 result.mOuterColor =
218 RelativeLuminanceUtils::Adjust(aTrackColor, luminance * 1.129f);
220 return result;
223 bool ScrollbarDrawingMac::GetScrollbarTrackRects(const Rect& aRect,
224 const ScrollbarParams& aParams,
225 float aScale,
226 ScrollbarTrackRects& aRects) {
227 if (aParams.overlay && !aParams.rolledOver) {
228 // Non-hovered overlay scrollbars don't have a track. Draw nothing.
229 return false;
232 // This matches the sizing checks in GetMinimumWidgetSize etc.
233 aScale = aScale >= 2.0f ? 2.0f : 1.0f;
235 nscolor trackColor;
236 if (aParams.custom) {
237 trackColor = aParams.trackColor;
238 } else {
239 if (aParams.overlay) {
240 trackColor = aParams.onDarkBackground ? NS_RGBA(201, 201, 201, 38)
241 : NS_RGBA(250, 250, 250, 191);
242 } else {
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);
253 struct {
254 nscolor color;
255 float thickness;
256 } segments[] = {
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
266 // aParams.rtl.
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);
275 } else {
276 if (aParams.rtl) {
277 segmentRect.SetBoxX(aRect.XMost() - endThickness,
278 aRect.XMost() - startThickness);
279 } else {
280 segmentRect.SetBoxX(aRect.X() + startThickness,
281 aRect.X() + endThickness);
284 accumulatedThickness = endThickness;
285 *current++ = {segmentRect, segment.color};
288 return true;
291 bool ScrollbarDrawingMac::GetScrollCornerRects(const Rect& aRect,
292 const ScrollbarParams& aParams,
293 float aScale,
294 ScrollCornerRects& aRects) {
295 if (aParams.overlay && !aParams.rolledOver) {
296 // Non-hovered overlay scrollbars don't have a corner. Draw nothing.
297 return false;
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;
320 nscolor trackColor =
321 aParams.custom ? aParams.trackColor : NS_RGBA(250, 250, 250, 255);
322 ScrollbarTrackDecorationColors colors =
323 ComputeScrollbarTrackDecorationColors(trackColor);
324 struct {
325 nscolor color;
326 Rect relativeRect;
327 } pieces[] = {
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}},
334 {trackColor,
335 {0.0f, 2.0f * aScale, width - 1.0f * aScale, height - 3.0f * aScale}},
336 {colors.mOuterColor,
337 {width - 1.0f * aScale, 0.0f, 1.0f * aScale, height - 1.0f * aScale}},
338 {colors.mOuterColor,
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();
345 if (aParams.rtl) {
346 pieceRect.x = aRect.XMost() - piece.relativeRect.XMost();
348 *current++ = {pieceRect, piece.color};
350 return true;
353 } // namespace widget
354 } // namespace mozilla