Bug 1755924 [wpt PR 32876] - Handle resumed blocks that get sliced by floats correctl...
[gecko.git] / widget / ScrollbarDrawingWin11.cpp
blobc93af566dcdcd147b5e170aad7ded16578018b9a
1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ScrollbarDrawingWin11.h"
8 #include "mozilla/gfx/Helpers.h"
9 #include "mozilla/Maybe.h"
10 #include "mozilla/StaticPrefs_widget.h"
11 #include "nsLayoutUtils.h"
12 #include "Theme.h"
13 #include "nsNativeTheme.h"
15 using mozilla::gfx::sRGBColor;
17 namespace mozilla::widget {
19 // There are effectively three kinds of scrollbars in Windows 11:
21 // * Overlay scrollbars (the ones where the scrollbar disappears automatically
22 // and doesn't take space)
23 // * Non-overlay scrollbar with thin (overlay-like) thumb.
24 // * Non-overlay scrollbar with thick thumb.
26 // See bug 1755193 for some discussion on non-overlay scrollbar styles.
27 enum class Style {
28 Overlay,
29 ThinThumb,
30 ThickThumb,
33 static Style ScrollbarStyle(nsPresContext* aPresContext) {
34 if (aPresContext->UseOverlayScrollbars()) {
35 return Style::Overlay;
37 if (StaticPrefs::
38 widget_non_native_theme_win11_scrollbar_force_overlay_style()) {
39 return Style::ThinThumb;
41 return Style::ThickThumb;
44 static constexpr CSSIntCoord kDefaultWinOverlayScrollbarSize = CSSIntCoord(12);
45 static constexpr CSSIntCoord kDefaultWinOverlayThinScrollbarSize =
46 CSSIntCoord(10);
48 auto ScrollbarDrawingWin11::GetScrollbarSizes(nsPresContext* aPresContext,
49 StyleScrollbarWidth aWidth,
50 Overlay aOverlay)
51 -> ScrollbarSizes {
52 if (aOverlay == Overlay::Yes) {
53 // TODO(emilio): Maybe make this configurable? Though this doesn't respect
54 // classic Windows registry settings, and cocoa overlay scrollbars also
55 // don't respect the override it seems, so this should be fine.
56 CSSCoord cssSize(aWidth == StyleScrollbarWidth::Thin
57 ? kDefaultWinOverlayThinScrollbarSize
58 : kDefaultWinOverlayScrollbarSize);
59 auto size = (cssSize * GetDPIRatioForScrollbarPart(aPresContext)).Rounded();
60 return {size, size};
62 return ScrollbarDrawingWin::GetScrollbarSizes(aPresContext, aWidth, aOverlay);
65 LayoutDeviceIntSize ScrollbarDrawingWin11::GetMinimumWidgetSize(
66 nsPresContext* aPresContext, StyleAppearance aAppearance,
67 nsIFrame* aFrame) {
68 MOZ_ASSERT(nsNativeTheme::IsWidgetScrollbarPart(aAppearance));
69 if (ScrollbarStyle(aPresContext) != Style::ThinThumb) {
70 return ScrollbarDrawingWin::GetMinimumWidgetSize(aPresContext, aAppearance,
71 aFrame);
73 constexpr float kArrowRatio = 14.0f / kDefaultWinScrollbarSize;
74 switch (aAppearance) {
75 case StyleAppearance::ScrollbarbuttonUp:
76 case StyleAppearance::ScrollbarbuttonDown: {
77 if (IsScrollbarWidthThin(aFrame)) {
78 return {};
80 const LayoutDeviceIntCoord size =
81 ScrollbarDrawing::GetScrollbarSizes(aPresContext, aFrame).mVertical;
82 return LayoutDeviceIntSize{
83 size, (kArrowRatio * LayoutDeviceCoord(size)).Rounded()};
85 case StyleAppearance::ScrollbarbuttonLeft:
86 case StyleAppearance::ScrollbarbuttonRight: {
87 if (IsScrollbarWidthThin(aFrame)) {
88 return {};
90 const LayoutDeviceIntCoord size =
91 ScrollbarDrawing::GetScrollbarSizes(aPresContext, aFrame).mHorizontal;
92 return LayoutDeviceIntSize{
93 (kArrowRatio * LayoutDeviceCoord(size)).Rounded(), size};
95 default:
96 return ScrollbarDrawingWin::GetMinimumWidgetSize(aPresContext,
97 aAppearance, aFrame);
101 sRGBColor ScrollbarDrawingWin11::ComputeScrollbarTrackColor(
102 nsIFrame* aFrame, const ComputedStyle& aStyle,
103 const EventStates& aDocumentState, const Colors& aColors) {
104 if (aColors.HighContrast()) {
105 return ScrollbarDrawingWin::ComputeScrollbarTrackColor(
106 aFrame, aStyle, aDocumentState, aColors);
108 const nsStyleUI* ui = aStyle.StyleUI();
109 if (ui->mScrollbarColor.IsColors()) {
110 return sRGBColor::FromABGR(
111 ui->mScrollbarColor.AsColors().track.CalcColor(aStyle));
113 return aColors.IsDark() ? sRGBColor::FromU8(23, 23, 23, 255)
114 : sRGBColor::FromU8(240, 240, 240, 255);
117 sRGBColor ScrollbarDrawingWin11::ComputeScrollbarThumbColor(
118 nsIFrame* aFrame, const ComputedStyle& aStyle,
119 const EventStates& aElementState, const EventStates& aDocumentState,
120 const Colors& aColors) {
121 if (aColors.HighContrast()) {
122 return ScrollbarDrawingWin::ComputeScrollbarThumbColor(
123 aFrame, aStyle, aElementState, aDocumentState, aColors);
125 const nscolor baseColor = [&] {
126 const nsStyleUI* ui = aStyle.StyleUI();
127 if (ui->mScrollbarColor.IsColors()) {
128 return ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle);
130 return aColors.IsDark() ? NS_RGBA(149, 149, 149, 255)
131 : NS_RGBA(133, 133, 133, 255);
132 }();
133 EventStates state = aElementState;
134 if (!IsScrollbarWidthThin(aStyle)) {
135 // non-thin scrollbars get hover feedback by changing thumb shape, so we
136 // only provide active feedback (and we use the hover state for that as it's
137 // more subtle).
138 state &= ~NS_EVENT_STATE_HOVER;
139 if (state.HasState(NS_EVENT_STATE_ACTIVE)) {
140 state &= ~NS_EVENT_STATE_ACTIVE;
141 state |= NS_EVENT_STATE_HOVER;
144 return sRGBColor::FromABGR(
145 ThemeColors::AdjustUnthemedScrollbarThumbColor(baseColor, state));
148 std::pair<sRGBColor, sRGBColor>
149 ScrollbarDrawingWin11::ComputeScrollbarButtonColors(
150 nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
151 const EventStates& aElementState, const EventStates& aDocumentState,
152 const Colors& aColors) {
153 if (aColors.HighContrast()) {
154 return ScrollbarDrawingWin::ComputeScrollbarButtonColors(
155 aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors);
157 // The button always looks transparent (the track behind it is visible), so we
158 // can hardcode it.
159 sRGBColor arrowColor = ComputeScrollbarThumbColor(
160 aFrame, aStyle, aElementState, aDocumentState, aColors);
161 return {sRGBColor::White(0.0f), arrowColor};
164 bool ScrollbarDrawingWin11::PaintScrollbarButton(
165 DrawTarget& aDrawTarget, StyleAppearance aAppearance,
166 const LayoutDeviceRect& aRect, nsIFrame* aFrame,
167 const ComputedStyle& aStyle, const EventStates& aElementState,
168 const EventStates& aDocumentState, const Colors& aColors,
169 const DPIRatio& aDpiRatio) {
170 if (!ScrollbarDrawing::IsParentScrollbarHoveredOrActive(aFrame)) {
171 return true;
174 const auto style = ScrollbarStyle(aFrame->PresContext());
175 auto [buttonColor, arrowColor] = ComputeScrollbarButtonColors(
176 aFrame, aAppearance, aStyle, aElementState, aDocumentState, aColors);
177 if (style != Style::Overlay) {
178 aDrawTarget.FillRect(aRect.ToUnknownRect(),
179 gfx::ColorPattern(ToDeviceColor(buttonColor)));
182 // Start with Up arrow.
183 float arrowPolygonX[] = {-4.5f, 4.5f, 4.5f, 0.5f, -0.5f, -4.5f, -4.5f};
184 float arrowPolygonXActive[] = {-4.0f, 4.0f, 4.0f, -0.25f,
185 -0.25f, -4.0f, -4.0f};
186 float arrowPolygonXHover[] = {-5.0f, 5.0f, 5.0f, 0.75f, -0.75f, -5.0f, -5.0f};
187 float arrowPolygonY[] = {2.5f, 2.5f, 1.0f, -4.0f, -4.0f, 1.0f, 2.5f};
188 float arrowPolygonYActive[] = {2.0f, 2.0f, 0.5f, -3.5f, -3.5f, 0.5f, 2.0f};
189 float arrowPolygonYHover[] = {3.0f, 3.0f, 1.5f, -4.5f, -4.5f, 1.5f, 3.0f};
190 float* arrowX = arrowPolygonX;
191 float* arrowY = arrowPolygonY;
192 const bool horizontal =
193 aAppearance == StyleAppearance::ScrollbarbuttonRight ||
194 aAppearance == StyleAppearance::ScrollbarbuttonLeft;
196 const float verticalOffset = [&] {
197 if (style != Style::Overlay) {
198 return 0.0f;
200 // To compensate for the scrollbar track radius we shift stuff vertically a
201 // bit. This 1px is arbitrary, but enough for the triangle not to overflow.
202 return 1.0f;
203 }();
204 const float horizontalOffset = [&] {
205 if (style != Style::ThinThumb) {
206 return 0.0f; // Always center it in the rect.
208 // Compensate for the displacement we do of the thumb position by displacing
209 // the arrow as well, see comment in DoPaintScrollbarThumb.
210 if (horizontal) {
211 return -0.5f;
213 return aFrame->GetWritingMode().IsPhysicalLTR() ? 0.5f : -0.5f;
214 }();
215 const float polygonSize = style == Style::Overlay
216 ? float(kDefaultWinOverlayScrollbarSize)
217 : float(kDefaultWinScrollbarSize);
218 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
220 if (aElementState.HasState(NS_EVENT_STATE_ACTIVE)) {
221 arrowX = arrowPolygonXActive;
222 arrowY = arrowPolygonYActive;
223 } else if (aElementState.HasState(NS_EVENT_STATE_HOVER)) {
224 arrowX = arrowPolygonXHover;
225 arrowY = arrowPolygonYHover;
228 switch (aAppearance) {
229 case StyleAppearance::ScrollbarbuttonDown:
230 case StyleAppearance::ScrollbarbuttonRight:
231 for (int32_t i = 0; i < arrowNumPoints; i++) {
232 arrowY[i] += verticalOffset;
233 arrowY[i] *= -1;
235 [[fallthrough]];
236 case StyleAppearance::ScrollbarbuttonUp:
237 case StyleAppearance::ScrollbarbuttonLeft:
238 if (horizontalOffset != 0.0f) {
239 for (int32_t i = 0; i < arrowNumPoints; i++) {
240 arrowX[i] += horizontalOffset;
243 break;
244 default:
245 return false;
248 if (horizontal) {
249 std::swap(arrowX, arrowY);
252 LayoutDeviceRect arrowRect(aRect);
253 if (style != Style::ThinThumb) {
254 auto margin = CSSCoord(style == Style::Overlay ? 1 : 2) * aDpiRatio;
255 arrowRect.Deflate(margin, margin);
258 ThemeDrawing::PaintArrow(aDrawTarget, arrowRect, arrowX, arrowY, polygonSize,
259 arrowNumPoints, arrowColor);
260 return true;
263 template <typename PaintBackendData>
264 bool ScrollbarDrawingWin11::DoPaintScrollbarThumb(
265 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
266 bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
267 const EventStates& aElementState, const EventStates& aDocumentState,
268 const Colors& aColors, const DPIRatio& aDpiRatio) {
269 sRGBColor thumbColor = ComputeScrollbarThumbColor(
270 aFrame, aStyle, aElementState, aDocumentState, aColors);
272 LayoutDeviceRect thumbRect(aRect);
274 const auto style = ScrollbarStyle(aFrame->PresContext());
275 const bool hovered =
276 ScrollbarDrawing::IsParentScrollbarHoveredOrActive(aFrame) ||
277 (style != Style::Overlay && IsScrollbarWidthThin(aStyle));
278 if (style == Style::ThickThumb) {
279 constexpr float kHoveredThumbRatio =
280 (1.0f - (11.0f / kDefaultWinScrollbarSize)) / 2.0f;
281 constexpr float kUnhoveredThumbRatio =
282 (1.0f - (9.0f / kDefaultWinScrollbarSize)) / 2.0f;
283 const float ratio = hovered ? kHoveredThumbRatio : kUnhoveredThumbRatio;
284 if (aHorizontal) {
285 thumbRect.Deflate(0, thumbRect.height * ratio);
286 } else {
287 thumbRect.Deflate(thumbRect.width * ratio, 0);
290 auto radius = CSSCoord(hovered ? 2 : 0);
291 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, thumbRect, thumbColor,
292 sRGBColor(), 0, radius, aDpiRatio);
293 return true;
296 const float defaultTrackSize = style == Style::Overlay
297 ? float(kDefaultWinOverlayScrollbarSize)
298 : float(kDefaultWinScrollbarSize);
299 const float trackSize = aHorizontal ? thumbRect.height : thumbRect.width;
300 const float thumbSizeInPixels = hovered ? 6.0f : 2.0f;
302 // The thumb might be a bit off-center, depending on our scrollbar styles.
304 // Hovered shifts, if any, need to be accounted for in PaintScrollbarButton.
305 // For example, for the hovered horizontal thin scrollbar shift:
307 // Scrollbar is 17px high by default. We make the thumb 6px tall and move
308 // it 5px towards the bottom, so the center (8.5 initially) is displaced
309 // by:
310 // (5px + 6px / 2) - 8.5px = -0.5px
312 // Same calculations apply to other shifts.
313 const float shiftInPixels = [&] {
314 if (style == Style::Overlay) {
315 if (hovered) {
316 // Keep the center intact.
317 return (defaultTrackSize - thumbSizeInPixels) / 2.0f;
319 // We want logical pixels from the thumb to the edge. For RTL and
320 // horizontal scrollbars that means shifting down the scrollbar size minus
321 // the thumb.
322 constexpr float kSpaceToEdge = 3.0f;
323 if (aHorizontal || aFrame->GetWritingMode().IsPhysicalLTR()) {
324 return defaultTrackSize - thumbSizeInPixels - kSpaceToEdge;
326 // For rtl is simpler.
327 return kSpaceToEdge;
329 if (aHorizontal) {
330 return hovered ? 5.0f : 7.0f;
332 const bool ltr = aFrame->GetWritingMode().IsPhysicalLTR();
333 return ltr ? (hovered ? 6.0f : 8.0f) : (hovered ? 5.0f : 7.0f);
334 }();
336 if (aHorizontal) {
337 thumbRect.y += shiftInPixels * trackSize / defaultTrackSize;
338 thumbRect.height *= thumbSizeInPixels / defaultTrackSize;
339 } else {
340 thumbRect.x += shiftInPixels * trackSize / defaultTrackSize;
341 thumbRect.width *= thumbSizeInPixels / defaultTrackSize;
344 if (style == Style::Overlay || hovered) {
345 LayoutDeviceCoord radius =
346 (aHorizontal ? thumbRect.height : thumbRect.width) / 2.0f;
348 MOZ_ASSERT(aRect.Contains(thumbRect));
349 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, thumbRect, thumbColor,
350 sRGBColor(), 0, radius / aDpiRatio,
351 aDpiRatio);
352 return true;
355 ThemeDrawing::FillRect(aPaintData, thumbRect, thumbColor);
356 return true;
359 bool ScrollbarDrawingWin11::PaintScrollbarThumb(
360 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
361 nsIFrame* aFrame, const ComputedStyle& aStyle,
362 const EventStates& aElementState, const EventStates& aDocumentState,
363 const Colors& aColors, const DPIRatio& aDpiRatio) {
364 return DoPaintScrollbarThumb(aDrawTarget, aRect, aHorizontal, aFrame, aStyle,
365 aElementState, aDocumentState, aColors,
366 aDpiRatio);
369 bool ScrollbarDrawingWin11::PaintScrollbarThumb(
370 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
371 bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
372 const EventStates& aElementState, const EventStates& aDocumentState,
373 const Colors& aColors, const DPIRatio& aDpiRatio) {
374 return DoPaintScrollbarThumb(aWrData, aRect, aHorizontal, aFrame, aStyle,
375 aElementState, aDocumentState, aColors,
376 aDpiRatio);
379 } // namespace mozilla::widget