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/. */
7 #include "ThemeCocoa.h"
9 #include "mozilla/MathAlgorithms.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/HTMLMeterElement.h"
13 #include "mozilla/dom/HTMLProgressElement.h"
14 #include "mozilla/gfx/Rect.h"
15 #include "mozilla/gfx/Types.h"
16 #include "mozilla/gfx/Filters.h"
17 #include "mozilla/RelativeLuminanceUtils.h"
18 #include "mozilla/StaticPrefs_widget.h"
19 #include "mozilla/webrender/WebRenderAPI.h"
20 #include "nsCSSColorUtils.h"
21 #include "nsCSSRendering.h"
22 #include "nsDeviceContext.h"
23 #include "nsLayoutUtils.h"
24 #include "nsRangeFrame.h"
25 #include "PathHelpers.h"
26 #include "ScrollbarDrawingAndroid.h"
27 #include "ScrollbarDrawingCocoa.h"
28 #include "ScrollbarDrawingGTK.h"
29 #include "ScrollbarDrawingWin.h"
30 #include "ScrollbarDrawingWin11.h"
33 # include "mozilla/WindowsVersion.h"
36 using namespace mozilla
;
37 using namespace mozilla::dom
;
38 using namespace mozilla::gfx
;
39 using namespace mozilla::widget
;
43 static constexpr gfx::sRGBColor
sColorGrey10(
44 gfx::sRGBColor::UnusualFromARGB(0xffe9e9ed));
45 static constexpr gfx::sRGBColor
sColorGrey10Alpha50(
46 gfx::sRGBColor::UnusualFromARGB(0x7fe9e9ed));
47 static constexpr gfx::sRGBColor
sColorGrey20(
48 gfx::sRGBColor::UnusualFromARGB(0xffd0d0d7));
49 static constexpr gfx::sRGBColor
sColorGrey30(
50 gfx::sRGBColor::UnusualFromARGB(0xffb1b1b9));
51 static constexpr gfx::sRGBColor
sColorGrey40(
52 gfx::sRGBColor::UnusualFromARGB(0xff8f8f9d));
53 static constexpr gfx::sRGBColor
sColorGrey40Alpha50(
54 gfx::sRGBColor::UnusualFromARGB(0x7f8f8f9d));
55 static constexpr gfx::sRGBColor
sColorGrey50(
56 gfx::sRGBColor::UnusualFromARGB(0xff676774));
57 static constexpr gfx::sRGBColor
sColorGrey60(
58 gfx::sRGBColor::UnusualFromARGB(0xff484851));
60 static constexpr gfx::sRGBColor
sColorMeterGreen10(
61 gfx::sRGBColor::UnusualFromARGB(0xff00ab60));
62 static constexpr gfx::sRGBColor
sColorMeterGreen20(
63 gfx::sRGBColor::UnusualFromARGB(0xff056139));
64 static constexpr gfx::sRGBColor
sColorMeterYellow10(
65 gfx::sRGBColor::UnusualFromARGB(0xffffbd4f));
66 static constexpr gfx::sRGBColor
sColorMeterYellow20(
67 gfx::sRGBColor::UnusualFromARGB(0xffd2811e));
68 static constexpr gfx::sRGBColor
sColorMeterRed10(
69 gfx::sRGBColor::UnusualFromARGB(0xffe22850));
70 static constexpr gfx::sRGBColor
sColorMeterRed20(
71 gfx::sRGBColor::UnusualFromARGB(0xff810220));
73 static const CSSCoord kMinimumColorPickerHeight
= 32.0f
;
74 static const CSSCoord kMinimumRangeThumbSize
= 20.0f
;
75 static const CSSCoord kMinimumDropdownArrowButtonWidth
= 18.0f
;
76 static const CSSCoord kMinimumSpinnerButtonWidth
= 18.0f
;
77 static const CSSCoord kMinimumSpinnerButtonHeight
= 9.0f
;
78 static const CSSCoord kButtonBorderWidth
= 1.0f
;
79 static const CSSCoord kMenulistBorderWidth
= 1.0f
;
80 static const CSSCoord kTextFieldBorderWidth
= 1.0f
;
81 static const CSSCoord kRangeHeight
= 6.0f
;
82 static const CSSCoord kProgressbarHeight
= 6.0f
;
83 static const CSSCoord kMeterHeight
= 12.0f
;
85 // nsCheckboxRadioFrame takes the bottom of the content box as the baseline.
86 // This border-width makes its baseline 2px under the bottom, which is nice.
87 static constexpr CSSCoord kCheckboxRadioBorderWidth
= 2.0f
;
89 static constexpr sRGBColor sTransparent
= sRGBColor::White(0.0);
91 // This pushes and pops a clip rect to the draw target.
93 // This is done to reduce fuzz in places where we may have antialiasing,
94 // because skia is not clip-invariant: given different clips, it does not
95 // guarantee the same result, even if the painted content doesn't intersect
98 // This is a bit sad, overall, but...
99 struct MOZ_RAII AutoClipRect
{
100 AutoClipRect(DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
) : mDt(aDt
) {
101 mDt
.PushClipRect(aRect
.ToUnknownRect());
104 ~AutoClipRect() { mDt
.PopClip(); }
110 static StaticRefPtr
<Theme
> gNativeInstance
;
111 static StaticRefPtr
<Theme
> gNonNativeInstance
;
112 static StaticRefPtr
<Theme
> gRDMInstance
;
117 already_AddRefed
<Theme
> do_CreateNativeThemeDoNotUseDirectly() {
118 // Android doesn't have a native theme.
119 return do_AddRef(new Theme(Theme::ScrollbarStyle()));
123 already_AddRefed
<nsITheme
> do_GetBasicNativeThemeDoNotUseDirectly() {
124 if (MOZ_UNLIKELY(!gNonNativeInstance
)) {
125 UniquePtr
<ScrollbarDrawing
> scrollbarDrawing
= Theme::ScrollbarStyle();
126 #ifdef MOZ_WIDGET_COCOA
127 gNonNativeInstance
= new ThemeCocoa(std::move(scrollbarDrawing
));
129 gNonNativeInstance
= new Theme(std::move(scrollbarDrawing
));
131 ClearOnShutdown(&gNonNativeInstance
);
133 return do_AddRef(gNonNativeInstance
);
136 already_AddRefed
<nsITheme
> do_GetNativeThemeDoNotUseDirectly() {
137 if (MOZ_UNLIKELY(!gNativeInstance
)) {
138 gNativeInstance
= do_CreateNativeThemeDoNotUseDirectly();
139 ClearOnShutdown(&gNativeInstance
);
141 return do_AddRef(gNativeInstance
);
144 already_AddRefed
<nsITheme
> do_GetRDMThemeDoNotUseDirectly() {
145 if (MOZ_UNLIKELY(!gRDMInstance
)) {
146 gRDMInstance
= new Theme(MakeUnique
<ScrollbarDrawingAndroid
>());
147 ClearOnShutdown(&gRDMInstance
);
149 return do_AddRef(gRDMInstance
);
152 namespace mozilla::widget
{
154 NS_IMPL_ISUPPORTS_INHERITED(Theme
, nsNativeTheme
, nsITheme
)
156 static constexpr nsLiteralCString kPrefs
[] = {
157 "widget.non-native-theme.use-theme-accent"_ns
,
158 "widget.non-native-theme.win.scrollbar.use-system-size"_ns
,
159 "widget.non-native-theme.scrollbar.size.override"_ns
,
160 "widget.non-native-theme.scrollbar.style"_ns
,
164 for (const auto& pref
: kPrefs
) {
165 Preferences::RegisterCallback(PrefChangedCallback
, pref
);
167 LookAndFeelChanged();
170 void Theme::Shutdown() {
171 for (const auto& pref
: kPrefs
) {
172 Preferences::UnregisterCallback(PrefChangedCallback
, pref
);
177 void Theme::LookAndFeelChanged() {
178 ThemeColors::RecomputeAccentColors();
179 if (gNonNativeInstance
) {
180 gNonNativeInstance
->SetScrollbarDrawing(ScrollbarStyle());
182 if (gNativeInstance
) {
183 gNativeInstance
->SetScrollbarDrawing(ScrollbarStyle());
188 auto Theme::GetDPIRatio(nsPresContext
* aPc
, StyleAppearance aAppearance
)
190 // Widgets react to zoom, except scrollbars.
191 if (IsWidgetScrollbarPart(aAppearance
)) {
192 return ScrollbarDrawing::GetDPIRatioForScrollbarPart(aPc
);
194 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc
->AppUnitsPerDevPixel());
198 auto Theme::GetDPIRatio(nsIFrame
* aFrame
, StyleAppearance aAppearance
)
200 return GetDPIRatio(aFrame
->PresContext(), aAppearance
);
203 // Checkbox and radio need to preserve aspect-ratio for compat. We also snap the
204 // size to exact device pixels to avoid snapping disorting the circles.
205 static LayoutDeviceRect
CheckBoxRadioRect(const LayoutDeviceRect
& aRect
) {
206 // Place a square rect in the center of aRect.
207 auto size
= std::trunc(std::min(aRect
.width
, aRect
.height
));
208 auto position
= aRect
.Center() - LayoutDevicePoint(size
* 0.5, size
* 0.5);
209 return LayoutDeviceRect(position
, LayoutDeviceSize(size
, size
));
212 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeCheckboxColors(
213 const EventStates
& aState
, StyleAppearance aAppearance
,
214 const Colors
& aColors
) {
215 MOZ_ASSERT(aAppearance
== StyleAppearance::Checkbox
||
216 aAppearance
== StyleAppearance::Radio
);
218 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
219 bool isChecked
= aState
.HasState(NS_EVENT_STATE_CHECKED
);
220 bool isIndeterminate
= aAppearance
== StyleAppearance::Checkbox
&&
221 aState
.HasState(NS_EVENT_STATE_INDETERMINATE
);
223 if (isChecked
|| isIndeterminate
) {
225 auto color
= ComputeBorderColor(aState
, aColors
, OutlineCoversBorder::No
);
226 return std::make_pair(color
, color
);
230 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
231 bool isHovered
= aState
.HasState(NS_EVENT_STATE_HOVER
);
232 const auto& color
= isActive
? aColors
.Accent().GetDarker()
233 : isHovered
? aColors
.Accent().GetDark()
234 : aColors
.Accent().Get();
235 return std::make_pair(color
, color
);
238 return ComputeTextfieldColors(aState
, aColors
, OutlineCoversBorder::No
);
241 sRGBColor
Theme::ComputeCheckmarkColor(const EventStates
& aState
,
242 const Colors
& aColors
) {
243 if (aColors
.HighContrast()) {
244 return aColors
.System(StyleSystemColor::Selecteditemtext
);
246 if (aState
.HasState(NS_EVENT_STATE_DISABLED
)) {
247 return sRGBColor::White(.8f
);
249 return aColors
.Accent().GetForeground();
252 sRGBColor
Theme::ComputeBorderColor(const EventStates
& aState
,
253 const Colors
& aColors
,
254 OutlineCoversBorder aOutlineCoversBorder
) {
255 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
256 if (aColors
.HighContrast()) {
257 return aColors
.System(isDisabled
? StyleSystemColor::Graytext
258 : StyleSystemColor::Buttontext
);
261 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
262 bool isHovered
= aState
.HasState(NS_EVENT_STATE_HOVER
);
263 bool isFocused
= aState
.HasState(NS_EVENT_STATE_FOCUSRING
);
265 return sColorGrey40Alpha50
;
267 if (isFocused
&& aOutlineCoversBorder
== OutlineCoversBorder::Yes
) {
268 // If we draw the outline over the border, prevent issues where the border
269 // shows underneath if it snaps in the wrong direction by using a
270 // transparent border. An alternative to this is ensuring that we snap the
271 // offset in PaintRoundedFocusRect the same was a we snap border widths, so
272 // that negative offsets are guaranteed to cover the border.
273 // But this looks harder to mess up.
276 bool dark
= aColors
.IsDark();
278 return dark
? sColorGrey20
: sColorGrey60
;
281 return dark
? sColorGrey30
: sColorGrey50
;
286 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeButtonColors(
287 const EventStates
& aState
, const Colors
& aColors
, nsIFrame
* aFrame
) {
289 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
290 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
291 bool isHovered
= aState
.HasState(NS_EVENT_STATE_HOVER
);
293 nscolor backgroundColor
= [&] {
295 return aColors
.SystemNs(StyleSystemColor::MozButtondisabledface
);
298 return aColors
.SystemNs(StyleSystemColor::MozButtonactiveface
);
301 return aColors
.SystemNs(StyleSystemColor::MozButtonhoverface
);
303 return aColors
.SystemNs(StyleSystemColor::Buttonface
);
306 if (aState
.HasState(NS_EVENT_STATE_AUTOFILL
)) {
307 backgroundColor
= NS_ComposeColors(
309 aColors
.SystemNs(StyleSystemColor::MozAutofillBackground
));
312 const sRGBColor borderColor
=
313 ComputeBorderColor(aState
, aColors
, OutlineCoversBorder::Yes
);
314 return std::make_pair(sRGBColor::FromABGR(backgroundColor
), borderColor
);
317 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeTextfieldColors(
318 const EventStates
& aState
, const Colors
& aColors
,
319 OutlineCoversBorder aOutlineCoversBorder
) {
320 nscolor backgroundColor
= [&] {
321 if (aState
.HasState(NS_EVENT_STATE_DISABLED
)) {
322 return aColors
.SystemNs(StyleSystemColor::MozDisabledfield
);
324 return aColors
.SystemNs(StyleSystemColor::Field
);
327 if (aState
.HasState(NS_EVENT_STATE_AUTOFILL
)) {
328 backgroundColor
= NS_ComposeColors(
330 aColors
.SystemNs(StyleSystemColor::MozAutofillBackground
));
333 const sRGBColor borderColor
=
334 ComputeBorderColor(aState
, aColors
, aOutlineCoversBorder
);
335 return std::make_pair(sRGBColor::FromABGR(backgroundColor
), borderColor
);
338 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeRangeProgressColors(
339 const EventStates
& aState
, const Colors
& aColors
) {
340 if (aColors
.HighContrast()) {
341 return aColors
.SystemPair(StyleSystemColor::Selecteditem
,
342 StyleSystemColor::Buttontext
);
346 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
347 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
348 bool isHovered
= aState
.HasState(NS_EVENT_STATE_HOVER
);
351 return std::make_pair(sColorGrey40Alpha50
, sColorGrey40Alpha50
);
353 if (isActive
|| isHovered
) {
354 return std::make_pair(aColors
.Accent().GetDark(),
355 aColors
.Accent().GetDarker());
357 return std::make_pair(aColors
.Accent().Get(), aColors
.Accent().GetDark());
360 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeRangeTrackColors(
361 const EventStates
& aState
, const Colors
& aColors
) {
362 if (aColors
.HighContrast()) {
363 return aColors
.SystemPair(StyleSystemColor::Window
,
364 StyleSystemColor::Buttontext
);
367 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
368 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
369 bool isHovered
= aState
.HasState(NS_EVENT_STATE_HOVER
);
372 return std::make_pair(sColorGrey10Alpha50
, sColorGrey40Alpha50
);
374 if (isActive
|| isHovered
) {
375 return std::make_pair(sColorGrey20
, sColorGrey50
);
377 return std::make_pair(sColorGrey10
, sColorGrey40
);
380 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeRangeThumbColors(
381 const EventStates
& aState
, const Colors
& aColors
) {
382 if (aColors
.HighContrast()) {
383 return aColors
.SystemPair(StyleSystemColor::Selecteditemtext
,
384 StyleSystemColor::Selecteditem
);
388 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
389 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
390 bool isHovered
= aState
.HasState(NS_EVENT_STATE_HOVER
);
392 const sRGBColor
& backgroundColor
= [&] {
397 return aColors
.Accent().Get();
405 const sRGBColor borderColor
= sRGBColor::OpaqueWhite();
406 return std::make_pair(backgroundColor
, borderColor
);
409 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeProgressColors(
410 const Colors
& aColors
) {
411 if (aColors
.HighContrast()) {
412 return aColors
.SystemPair(StyleSystemColor::Selecteditem
,
413 StyleSystemColor::Buttontext
);
415 return std::make_pair(aColors
.Accent().Get(), aColors
.Accent().GetDark());
418 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeProgressTrackColors(
419 const Colors
& aColors
) {
420 if (aColors
.HighContrast()) {
421 return aColors
.SystemPair(StyleSystemColor::Buttonface
,
422 StyleSystemColor::Buttontext
);
424 return std::make_pair(sColorGrey10
, sColorGrey40
);
427 std::pair
<sRGBColor
, sRGBColor
> Theme::ComputeMeterchunkColors(
428 const EventStates
& aMeterState
, const Colors
& aColors
) {
429 if (aColors
.HighContrast()) {
430 return ComputeProgressColors(aColors
);
432 sRGBColor borderColor
= sColorMeterGreen20
;
433 sRGBColor chunkColor
= sColorMeterGreen10
;
435 if (aMeterState
.HasState(NS_EVENT_STATE_SUB_OPTIMUM
)) {
436 borderColor
= sColorMeterYellow20
;
437 chunkColor
= sColorMeterYellow10
;
438 } else if (aMeterState
.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM
)) {
439 borderColor
= sColorMeterRed20
;
440 chunkColor
= sColorMeterRed10
;
443 return std::make_pair(chunkColor
, borderColor
);
446 std::array
<sRGBColor
, 3> Theme::ComputeFocusRectColors(const Colors
& aColors
) {
447 if (aColors
.HighContrast()) {
448 return {aColors
.System(StyleSystemColor::Selecteditem
),
449 aColors
.System(StyleSystemColor::Buttontext
),
450 aColors
.System(StyleSystemColor::Window
)};
452 const auto& accent
= aColors
.Accent();
453 const sRGBColor middle
=
454 aColors
.IsDark() ? sRGBColor::Black(.3f
) : sRGBColor::White(.3f
);
455 return {accent
.Get(), middle
, accent
.GetLight()};
458 static const CSSCoord kInnerFocusOutlineWidth
= 2.0f
;
460 template <typename PaintBackendData
>
461 void Theme::PaintRoundedFocusRect(PaintBackendData
& aBackendData
,
462 const LayoutDeviceRect
& aRect
,
463 const Colors
& aColors
, DPIRatio aDpiRatio
,
464 CSSCoord aRadius
, CSSCoord aOffset
) {
465 // NOTE(emilio): If the widths or offsets here change, make sure to tweak
466 // the GetWidgetOverflow path for FocusOutline.
467 auto [innerColor
, middleColor
, outerColor
] = ComputeFocusRectColors(aColors
);
469 LayoutDeviceRect
focusRect(aRect
);
471 // The focus rect is painted outside of the border area (aRect), see:
473 // data:text/html,<div style="border: 1px solid; outline: 2px solid
476 // But some controls might provide a negative offset to cover the border, if
478 CSSCoord strokeWidth
= kInnerFocusOutlineWidth
;
479 auto strokeWidthDevPx
=
480 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth
, aDpiRatio
));
481 CSSCoord strokeRadius
= aRadius
;
482 focusRect
.Inflate(aOffset
* aDpiRatio
+ strokeWidthDevPx
);
484 ThemeDrawing::PaintRoundedRectWithRadius(
485 aBackendData
, focusRect
, sTransparent
, innerColor
, strokeWidth
,
486 strokeRadius
, aDpiRatio
);
488 strokeWidth
= CSSCoord(1.0f
);
490 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth
, aDpiRatio
));
491 strokeRadius
+= strokeWidth
;
492 focusRect
.Inflate(strokeWidthDevPx
);
494 ThemeDrawing::PaintRoundedRectWithRadius(
495 aBackendData
, focusRect
, sTransparent
, middleColor
, strokeWidth
,
496 strokeRadius
, aDpiRatio
);
498 strokeWidth
= CSSCoord(2.0f
);
500 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth
, aDpiRatio
));
501 strokeRadius
+= strokeWidth
;
502 focusRect
.Inflate(strokeWidthDevPx
);
504 ThemeDrawing::PaintRoundedRectWithRadius(
505 aBackendData
, focusRect
, sTransparent
, outerColor
, strokeWidth
,
506 strokeRadius
, aDpiRatio
);
509 void Theme::PaintCheckboxControl(DrawTarget
& aDrawTarget
,
510 const LayoutDeviceRect
& aRect
,
511 const EventStates
& aState
,
512 const Colors
& aColors
, DPIRatio aDpiRatio
) {
513 auto [backgroundColor
, borderColor
] =
514 ComputeCheckboxColors(aState
, StyleAppearance::Checkbox
, aColors
);
516 const CSSCoord radius
= 2.0f
;
517 CSSCoord borderWidth
= kCheckboxRadioBorderWidth
;
518 if (backgroundColor
== borderColor
) {
521 ThemeDrawing::PaintRoundedRectWithRadius(aDrawTarget
, aRect
,
522 backgroundColor
, borderColor
,
523 borderWidth
, radius
, aDpiRatio
);
526 if (aState
.HasState(NS_EVENT_STATE_INDETERMINATE
)) {
527 PaintIndeterminateMark(aDrawTarget
, aRect
, aState
, aColors
);
528 } else if (aState
.HasState(NS_EVENT_STATE_CHECKED
)) {
529 PaintCheckMark(aDrawTarget
, aRect
, aState
, aColors
);
532 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
533 PaintRoundedFocusRect(aDrawTarget
, aRect
, aColors
, aDpiRatio
, 5.0f
, 1.0f
);
537 constexpr CSSCoord kCheckboxRadioContentBoxSize
= 10.0f
;
538 constexpr CSSCoord kCheckboxRadioBorderBoxSize
=
539 kCheckboxRadioContentBoxSize
+ kCheckboxRadioBorderWidth
* 2.0f
;
541 void Theme::PaintCheckMark(DrawTarget
& aDrawTarget
,
542 const LayoutDeviceRect
& aRect
,
543 const EventStates
& aState
, const Colors
& aColors
) {
544 // Points come from the coordinates on a 14X14 (kCheckboxRadioBorderBoxSize)
545 // unit box centered at 0,0
546 const float checkPolygonX
[] = {-4.5f
, -1.5f
, -0.5f
, 5.0f
, 4.75f
,
547 3.5f
, -0.5f
, -1.5f
, -3.5f
};
548 const float checkPolygonY
[] = {0.5f
, 4.0f
, 4.0f
, -2.5f
, -4.0f
,
549 -4.0f
, 1.0f
, 1.25f
, -1.0f
};
550 const int32_t checkNumPoints
= sizeof(checkPolygonX
) / sizeof(float);
552 ThemeDrawing::ScaleToFillRect(aRect
, kCheckboxRadioBorderBoxSize
);
553 auto center
= aRect
.Center().ToUnknownPoint();
555 RefPtr
<PathBuilder
> builder
= aDrawTarget
.CreatePathBuilder();
556 Point p
= center
+ Point(checkPolygonX
[0] * scale
, checkPolygonY
[0] * scale
);
558 for (int32_t i
= 1; i
< checkNumPoints
; i
++) {
559 p
= center
+ Point(checkPolygonX
[i
] * scale
, checkPolygonY
[i
] * scale
);
562 RefPtr
<Path
> path
= builder
->Finish();
564 sRGBColor fillColor
= ComputeCheckmarkColor(aState
, aColors
);
565 aDrawTarget
.Fill(path
, ColorPattern(ToDeviceColor(fillColor
)));
568 void Theme::PaintIndeterminateMark(DrawTarget
& aDrawTarget
,
569 const LayoutDeviceRect
& aRect
,
570 const EventStates
& aState
,
571 const Colors
& aColors
) {
572 const CSSCoord borderWidth
= 2.0f
;
574 ThemeDrawing::ScaleToFillRect(aRect
, kCheckboxRadioBorderBoxSize
);
576 Rect rect
= aRect
.ToUnknownRect();
577 rect
.y
+= (rect
.height
/ 2) - (borderWidth
* scale
/ 2);
578 rect
.height
= borderWidth
* scale
;
579 rect
.x
+= (borderWidth
* scale
) + (borderWidth
* scale
/ 8);
580 rect
.width
-= ((borderWidth
* scale
) + (borderWidth
* scale
/ 8)) * 2;
582 sRGBColor fillColor
= ComputeCheckmarkColor(aState
, aColors
);
583 aDrawTarget
.FillRect(rect
, ColorPattern(ToDeviceColor(fillColor
)));
586 template <typename PaintBackendData
>
587 void Theme::PaintStrokedCircle(PaintBackendData
& aPaintData
,
588 const LayoutDeviceRect
& aRect
,
589 const sRGBColor
& aBackgroundColor
,
590 const sRGBColor
& aBorderColor
,
591 const CSSCoord aBorderWidth
,
592 DPIRatio aDpiRatio
) {
593 auto radius
= LayoutDeviceCoord(aRect
.Size().width
) / aDpiRatio
;
594 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, aRect
, aBackgroundColor
,
595 aBorderColor
, aBorderWidth
, radius
,
599 void Theme::PaintCircleShadow(WebRenderBackendData
& aWrData
,
600 const LayoutDeviceRect
& aBoxRect
,
601 const LayoutDeviceRect
& aClipRect
,
602 float aShadowAlpha
, const CSSPoint
& aShadowOffset
,
603 CSSCoord aShadowBlurStdDev
, DPIRatio aDpiRatio
) {
604 const bool kBackfaceIsVisible
= true;
605 const LayoutDeviceCoord stdDev
= aShadowBlurStdDev
* aDpiRatio
;
606 const LayoutDevicePoint shadowOffset
= aShadowOffset
* aDpiRatio
;
607 const IntSize inflation
=
608 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev
, stdDev
));
609 LayoutDeviceRect shadowRect
= aBoxRect
;
610 shadowRect
.MoveBy(shadowOffset
);
611 shadowRect
.Inflate(inflation
.width
, inflation
.height
);
612 const auto boxRect
= wr::ToLayoutRect(aBoxRect
);
613 aWrData
.mBuilder
.PushBoxShadow(
614 wr::ToLayoutRect(shadowRect
), wr::ToLayoutRect(aClipRect
),
615 kBackfaceIsVisible
, boxRect
,
616 wr::ToLayoutVector2D(aShadowOffset
* aDpiRatio
),
617 wr::ToColorF(DeviceColor(0.0f
, 0.0f
, 0.0f
, aShadowAlpha
)), stdDev
,
618 /* aSpread = */ 0.0f
,
619 wr::ToBorderRadius(gfx::RectCornerRadii(aBoxRect
.Size().width
)),
620 wr::BoxShadowClipMode::Outset
);
623 void Theme::PaintCircleShadow(DrawTarget
& aDrawTarget
,
624 const LayoutDeviceRect
& aBoxRect
,
625 const LayoutDeviceRect
& aClipRect
,
626 float aShadowAlpha
, const CSSPoint
& aShadowOffset
,
627 CSSCoord aShadowBlurStdDev
, DPIRatio aDpiRatio
) {
628 Float stdDev
= aShadowBlurStdDev
* aDpiRatio
;
629 Point offset
= (aShadowOffset
* aDpiRatio
).ToUnknownPoint();
631 RefPtr
<FilterNode
> blurFilter
=
632 aDrawTarget
.CreateFilter(FilterType::GAUSSIAN_BLUR
);
637 blurFilter
->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION
, stdDev
);
640 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev
, stdDev
));
641 Rect inflatedRect
= aBoxRect
.ToUnknownRect();
642 inflatedRect
.Inflate(inflation
.width
, inflation
.height
);
643 Rect sourceRectInFilterSpace
=
644 inflatedRect
- aBoxRect
.TopLeft().ToUnknownPoint();
645 Point destinationPointOfSourceRect
= inflatedRect
.TopLeft() + offset
;
647 IntSize dtSize
= RoundedToInt(aBoxRect
.Size().ToUnknownSize());
648 RefPtr
<DrawTarget
> ellipseDT
= aDrawTarget
.CreateSimilarDrawTargetForFilter(
649 dtSize
, SurfaceFormat::A8
, blurFilter
, blurFilter
,
650 sourceRectInFilterSpace
, destinationPointOfSourceRect
);
655 AutoClipRect
clipRect(aDrawTarget
, aClipRect
);
657 RefPtr
<Path
> ellipse
= MakePathForEllipse(
658 *ellipseDT
, (aBoxRect
- aBoxRect
.TopLeft()).Center().ToUnknownPoint(),
659 aBoxRect
.Size().ToUnknownSize());
660 ellipseDT
->Fill(ellipse
,
661 ColorPattern(DeviceColor(0.0f
, 0.0f
, 0.0f
, aShadowAlpha
)));
662 RefPtr
<SourceSurface
> ellipseSurface
= ellipseDT
->Snapshot();
664 blurFilter
->SetInput(IN_GAUSSIAN_BLUR_IN
, ellipseSurface
);
665 aDrawTarget
.DrawFilter(blurFilter
, sourceRectInFilterSpace
,
666 destinationPointOfSourceRect
);
669 template <typename PaintBackendData
>
670 void Theme::PaintRadioControl(PaintBackendData
& aPaintData
,
671 const LayoutDeviceRect
& aRect
,
672 const EventStates
& aState
, const Colors
& aColors
,
673 DPIRatio aDpiRatio
) {
674 auto [backgroundColor
, borderColor
] =
675 ComputeCheckboxColors(aState
, StyleAppearance::Radio
, aColors
);
677 CSSCoord borderWidth
= kCheckboxRadioBorderWidth
;
678 if (backgroundColor
== borderColor
) {
681 PaintStrokedCircle(aPaintData
, aRect
, backgroundColor
, borderColor
,
682 borderWidth
, aDpiRatio
);
685 if (aState
.HasState(NS_EVENT_STATE_CHECKED
)) {
686 LayoutDeviceRect
rect(aRect
);
687 auto width
= LayoutDeviceCoord(
688 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth
, aDpiRatio
));
691 auto checkColor
= ComputeCheckmarkColor(aState
, aColors
);
692 PaintStrokedCircle(aPaintData
, rect
, backgroundColor
, checkColor
,
693 kCheckboxRadioBorderWidth
, aDpiRatio
);
696 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
697 PaintRoundedFocusRect(aPaintData
, aRect
, aColors
, aDpiRatio
, 5.0f
, 1.0f
);
701 template <typename PaintBackendData
>
702 void Theme::PaintTextField(PaintBackendData
& aPaintData
,
703 const LayoutDeviceRect
& aRect
,
704 const EventStates
& aState
, const Colors
& aColors
,
705 DPIRatio aDpiRatio
) {
706 auto [backgroundColor
, borderColor
] =
707 ComputeTextfieldColors(aState
, aColors
, OutlineCoversBorder::Yes
);
709 const CSSCoord radius
= 2.0f
;
711 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, aRect
, backgroundColor
,
712 borderColor
, kTextFieldBorderWidth
,
715 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
716 PaintRoundedFocusRect(aPaintData
, aRect
, aColors
, aDpiRatio
,
717 radius
+ kTextFieldBorderWidth
,
718 -kTextFieldBorderWidth
);
722 template <typename PaintBackendData
>
723 void Theme::PaintListbox(PaintBackendData
& aPaintData
,
724 const LayoutDeviceRect
& aRect
,
725 const EventStates
& aState
, const Colors
& aColors
,
726 DPIRatio aDpiRatio
) {
727 const CSSCoord radius
= 2.0f
;
728 auto [backgroundColor
, borderColor
] =
729 ComputeTextfieldColors(aState
, aColors
, OutlineCoversBorder::Yes
);
731 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, aRect
, backgroundColor
,
732 borderColor
, kMenulistBorderWidth
,
735 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
736 PaintRoundedFocusRect(aPaintData
, aRect
, aColors
, aDpiRatio
,
737 radius
+ kMenulistBorderWidth
, -kMenulistBorderWidth
);
741 template <typename PaintBackendData
>
742 void Theme::PaintMenulist(PaintBackendData
& aDrawTarget
,
743 const LayoutDeviceRect
& aRect
,
744 const EventStates
& aState
, const Colors
& aColors
,
745 DPIRatio aDpiRatio
) {
746 const CSSCoord radius
= 4.0f
;
747 auto [backgroundColor
, borderColor
] = ComputeButtonColors(aState
, aColors
);
749 ThemeDrawing::PaintRoundedRectWithRadius(aDrawTarget
, aRect
, backgroundColor
,
750 borderColor
, kMenulistBorderWidth
,
753 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
754 PaintRoundedFocusRect(aDrawTarget
, aRect
, aColors
, aDpiRatio
,
755 radius
+ kMenulistBorderWidth
, -kMenulistBorderWidth
);
759 void Theme::PaintMenulistArrowButton(nsIFrame
* aFrame
, DrawTarget
& aDrawTarget
,
760 const LayoutDeviceRect
& aRect
,
761 const EventStates
& aState
) {
762 const float kPolygonX
[] = {-4.0f
, -0.5f
, 0.5f
, 4.0f
, 4.0f
,
763 3.0f
, 0.0f
, 0.0f
, -3.0f
, -4.0f
};
764 const float kPolygonY
[] = {-1, 3.0f
, 3.0f
, -1.0f
, -2.0f
,
765 -2.0f
, 1.5f
, 1.5f
, -2.0f
, -2.0f
};
767 const float kPolygonSize
= kMinimumDropdownArrowButtonWidth
;
769 const auto arrowColor
= sRGBColor::FromABGR(
770 nsLayoutUtils::GetColor(aFrame
, &nsStyleText::mWebkitTextFillColor
));
771 ThemeDrawing::PaintArrow(aDrawTarget
, aRect
, kPolygonX
, kPolygonY
,
772 kPolygonSize
, ArrayLength(kPolygonX
), arrowColor
);
775 void Theme::PaintSpinnerButton(nsIFrame
* aFrame
, DrawTarget
& aDrawTarget
,
776 const LayoutDeviceRect
& aRect
,
777 const EventStates
& aState
,
778 StyleAppearance aAppearance
,
779 const Colors
& aColors
, DPIRatio aDpiRatio
) {
780 auto [backgroundColor
, borderColor
] = ComputeButtonColors(aState
, aColors
);
782 aDrawTarget
.FillRect(aRect
.ToUnknownRect(),
783 ColorPattern(ToDeviceColor(backgroundColor
)));
785 const float kPolygonX
[] = {-3.5f
, -0.5f
, 0.5f
, 3.5f
, 3.5f
,
786 2.5f
, 0.0f
, 0.0f
, -2.5f
, -3.5f
};
787 float polygonY
[] = {-1.5f
, 1.5f
, 1.5f
, -1.5f
, -2.5f
,
788 -2.5f
, 0.0f
, 0.0f
, -2.5f
, -2.5f
};
790 const float kPolygonSize
= kMinimumSpinnerButtonHeight
;
791 if (aAppearance
== StyleAppearance::SpinnerUpbutton
) {
792 for (auto& coord
: polygonY
) {
797 ThemeDrawing::PaintArrow(aDrawTarget
, aRect
, kPolygonX
, polygonY
,
798 kPolygonSize
, ArrayLength(kPolygonX
), borderColor
);
801 template <typename PaintBackendData
>
802 void Theme::PaintRange(nsIFrame
* aFrame
, PaintBackendData
& aPaintData
,
803 const LayoutDeviceRect
& aRect
, const EventStates
& aState
,
804 const Colors
& aColors
, DPIRatio aDpiRatio
,
806 nsRangeFrame
* rangeFrame
= do_QueryFrame(aFrame
);
811 double progress
= rangeFrame
->GetValueAsFractionOfRange();
813 LayoutDeviceRect
thumbRect(0, 0, kMinimumRangeThumbSize
* aDpiRatio
,
814 kMinimumRangeThumbSize
* aDpiRatio
);
815 LayoutDeviceRect
progressClipRect(aRect
);
816 LayoutDeviceRect
trackClipRect(aRect
);
817 const LayoutDeviceCoord verticalSize
= kRangeHeight
* aDpiRatio
;
819 rect
.height
= verticalSize
;
820 rect
.y
= aRect
.y
+ (aRect
.height
- rect
.height
) / 2;
821 thumbRect
.y
= aRect
.y
+ (aRect
.height
- thumbRect
.height
) / 2;
823 if (IsFrameRTL(aFrame
)) {
825 aRect
.x
+ (aRect
.width
- thumbRect
.width
) * (1.0 - progress
);
826 float midPoint
= thumbRect
.Center().X();
827 trackClipRect
.SetBoxX(aRect
.X(), midPoint
);
828 progressClipRect
.SetBoxX(midPoint
, aRect
.XMost());
830 thumbRect
.x
= aRect
.x
+ (aRect
.width
- thumbRect
.width
) * progress
;
831 float midPoint
= thumbRect
.Center().X();
832 progressClipRect
.SetBoxX(aRect
.X(), midPoint
);
833 trackClipRect
.SetBoxX(midPoint
, aRect
.XMost());
836 rect
.width
= verticalSize
;
837 rect
.x
= aRect
.x
+ (aRect
.width
- rect
.width
) / 2;
838 thumbRect
.x
= aRect
.x
+ (aRect
.width
- thumbRect
.width
) / 2;
841 aRect
.y
+ (aRect
.height
- thumbRect
.height
) * (1.0 - progress
);
842 float midPoint
= thumbRect
.Center().Y();
843 trackClipRect
.SetBoxY(aRect
.Y(), midPoint
);
844 progressClipRect
.SetBoxY(midPoint
, aRect
.YMost());
847 const CSSCoord borderWidth
= 1.0f
;
848 const CSSCoord radius
= 3.0f
;
850 auto [progressColor
, progressBorderColor
] =
851 ComputeRangeProgressColors(aState
, aColors
);
852 auto [trackColor
, trackBorderColor
] =
853 ComputeRangeTrackColors(aState
, aColors
);
855 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, rect
, progressClipRect
,
856 progressColor
, progressBorderColor
,
857 borderWidth
, radius
, aDpiRatio
);
859 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, rect
, trackClipRect
,
860 trackColor
, trackBorderColor
,
861 borderWidth
, radius
, aDpiRatio
);
863 if (!aState
.HasState(NS_EVENT_STATE_DISABLED
)) {
864 // Ensure the shadow doesn't expand outside of our overflow rect declared in
865 // GetWidgetOverflow().
866 auto overflowRect
= aRect
;
867 overflowRect
.Inflate(CSSCoord(6.0f
) * aDpiRatio
);
869 PaintCircleShadow(aPaintData
, thumbRect
, overflowRect
, 0.3f
,
870 CSSPoint(0.0f
, 2.0f
), 2.0f
, aDpiRatio
);
873 // Draw the thumb on top.
874 const CSSCoord thumbBorderWidth
= 2.0f
;
875 auto [thumbColor
, thumbBorderColor
] =
876 ComputeRangeThumbColors(aState
, aColors
);
878 PaintStrokedCircle(aPaintData
, thumbRect
, thumbColor
, thumbBorderColor
,
879 thumbBorderWidth
, aDpiRatio
);
881 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
882 PaintRoundedFocusRect(aPaintData
, aRect
, aColors
, aDpiRatio
, radius
, 1.0f
);
886 template <typename PaintBackendData
>
887 void Theme::PaintProgress(nsIFrame
* aFrame
, PaintBackendData
& aPaintData
,
888 const LayoutDeviceRect
& aRect
,
889 const EventStates
& aState
, const Colors
& aColors
,
890 DPIRatio aDpiRatio
, bool aIsMeter
) {
891 const CSSCoord borderWidth
= 1.0f
;
892 const CSSCoord radius
= aIsMeter
? 6.0f
: 3.0f
;
894 LayoutDeviceRect
rect(aRect
);
895 const LayoutDeviceCoord thickness
=
896 (aIsMeter
? kMeterHeight
: kProgressbarHeight
) * aDpiRatio
;
898 const bool isHorizontal
= !nsNativeTheme::IsVerticalProgress(aFrame
);
900 // Center it vertically.
901 rect
.y
+= (rect
.height
- thickness
) / 2;
902 rect
.height
= thickness
;
904 // Center it horizontally.
905 rect
.x
+= (rect
.width
- thickness
) / 2;
906 rect
.width
= thickness
;
910 // Paint the track, unclipped.
911 auto [backgroundColor
, borderColor
] = ComputeProgressTrackColors(aColors
);
912 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, rect
, rect
,
913 backgroundColor
, borderColor
,
914 borderWidth
, radius
, aDpiRatio
);
917 // Now paint the chunk, clipped as needed.
918 LayoutDeviceRect clipRect
= rect
;
919 if (aState
.HasState(NS_EVENT_STATE_INDETERMINATE
)) {
920 // For indeterminate progress, we paint an animated chunk of 1/3 of the
923 // Animation speed and math borrowed from GTK.
924 const LayoutDeviceCoord size
= isHorizontal
? rect
.width
: rect
.height
;
925 const LayoutDeviceCoord barSize
= size
* 0.3333f
;
926 const LayoutDeviceCoord travel
= 2.0f
* (size
- barSize
);
928 // Period equals to travel / pixelsPerMillisecond where pixelsPerMillisecond
929 // equals progressSize / 1000.0. This is equivalent to 1600.
930 const unsigned kPeriod
= 1600;
932 const int t
= PR_IntervalToMilliseconds(PR_IntervalNow()) % kPeriod
;
933 const LayoutDeviceCoord dx
= travel
* float(t
) / float(kPeriod
);
935 rect
.width
= barSize
;
936 rect
.x
+= (dx
< travel
* .5f
) ? dx
: travel
- dx
;
938 rect
.height
= barSize
;
939 rect
.y
+= (dx
< travel
* .5f
) ? dx
: travel
- dx
;
942 // Queue the next frame if needed.
943 if (!QueueAnimatedContentForRefresh(aFrame
->GetContent(), 60)) {
944 NS_WARNING("Couldn't refresh indeterminate <progress>");
947 // This is the progress chunk, clip it to the right amount.
948 double position
= [&] {
950 auto* meter
= dom::HTMLMeterElement::FromNode(aFrame
->GetContent());
954 return meter
->Position();
956 auto* progress
= dom::HTMLProgressElement::FromNode(aFrame
->GetContent());
960 return progress
->Position();
963 double clipWidth
= rect
.width
* position
;
964 clipRect
.width
= clipWidth
;
965 if (IsFrameRTL(aFrame
)) {
966 clipRect
.x
+= rect
.width
- clipWidth
;
969 double clipHeight
= rect
.height
* position
;
970 clipRect
.height
= clipHeight
;
971 clipRect
.y
+= rect
.height
- clipHeight
;
975 auto [backgroundColor
, borderColor
] =
976 aIsMeter
? ComputeMeterchunkColors(aState
, aColors
)
977 : ComputeProgressColors(aColors
);
978 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, rect
, clipRect
,
979 backgroundColor
, borderColor
,
980 borderWidth
, radius
, aDpiRatio
);
983 template <typename PaintBackendData
>
984 void Theme::PaintButton(nsIFrame
* aFrame
, PaintBackendData
& aPaintData
,
985 const LayoutDeviceRect
& aRect
,
986 const EventStates
& aState
, const Colors
& aColors
,
987 DPIRatio aDpiRatio
) {
988 const CSSCoord radius
= 4.0f
;
989 auto [backgroundColor
, borderColor
] =
990 ComputeButtonColors(aState
, aColors
, aFrame
);
992 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData
, aRect
, backgroundColor
,
993 borderColor
, kButtonBorderWidth
,
996 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
997 PaintRoundedFocusRect(aPaintData
, aRect
, aColors
, aDpiRatio
,
998 radius
+ kButtonBorderWidth
, -kButtonBorderWidth
);
1003 Theme::DrawWidgetBackground(gfxContext
* aContext
, nsIFrame
* aFrame
,
1004 StyleAppearance aAppearance
, const nsRect
& aRect
,
1005 const nsRect
& /* aDirtyRect */,
1006 DrawOverflow aDrawOverflow
) {
1007 if (!DoDrawWidgetBackground(*aContext
->GetDrawTarget(), aFrame
, aAppearance
,
1008 aRect
, aDrawOverflow
)) {
1009 return NS_ERROR_NOT_IMPLEMENTED
;
1014 bool Theme::CreateWebRenderCommandsForWidget(
1015 mozilla::wr::DisplayListBuilder
& aBuilder
,
1016 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
1017 const mozilla::layers::StackingContextHelper
& aSc
,
1018 mozilla::layers::RenderRootStateManager
* aManager
, nsIFrame
* aFrame
,
1019 StyleAppearance aAppearance
, const nsRect
& aRect
) {
1020 if (!StaticPrefs::widget_non_native_theme_webrender()) {
1023 WebRenderBackendData data
{aBuilder
, aResources
, aSc
, aManager
};
1024 return DoDrawWidgetBackground(data
, aFrame
, aAppearance
, aRect
,
1028 static LayoutDeviceRect
ToSnappedRect(const nsRect
& aRect
,
1029 nscoord aTwipsPerPixel
, DrawTarget
& aDt
) {
1030 return LayoutDeviceRect::FromUnknownRect(
1031 NSRectToSnappedRect(aRect
, aTwipsPerPixel
, aDt
));
1034 static LayoutDeviceRect
ToSnappedRect(const nsRect
& aRect
,
1035 nscoord aTwipsPerPixel
,
1036 WebRenderBackendData
& aDt
) {
1037 // TODO: Do we need to do any more snapping here?
1038 return LayoutDeviceRect::FromAppUnits(aRect
, aTwipsPerPixel
);
1041 template <typename PaintBackendData
>
1042 bool Theme::DoDrawWidgetBackground(PaintBackendData
& aPaintData
,
1044 StyleAppearance aAppearance
,
1045 const nsRect
& aRect
,
1046 DrawOverflow aDrawOverflow
) {
1047 static_assert(std::is_same_v
<PaintBackendData
, DrawTarget
> ||
1048 std::is_same_v
<PaintBackendData
, WebRenderBackendData
>);
1050 const nsPresContext
* pc
= aFrame
->PresContext();
1051 const nscoord twipsPerPixel
= pc
->AppUnitsPerDevPixel();
1052 const auto devPxRect
= ToSnappedRect(aRect
, twipsPerPixel
, aPaintData
);
1054 const EventStates docState
= pc
->Document()->GetDocumentState();
1055 EventStates eventState
= GetContentState(aFrame
, aAppearance
);
1056 if (aAppearance
== StyleAppearance::MozMenulistArrowButton
) {
1057 bool isHTML
= IsHTMLContent(aFrame
);
1058 nsIFrame
* parentFrame
= aFrame
->GetParent();
1059 bool isMenulist
= !isHTML
&& parentFrame
->IsMenuFrame();
1060 // HTML select and XUL menulist dropdown buttons get state from the
1062 if (isHTML
|| isMenulist
) {
1063 aFrame
= parentFrame
;
1064 eventState
= GetContentState(parentFrame
, aAppearance
);
1068 // Don't paint the outline if we're asked not to draw overflow, or if the
1069 // author has specified another kind of outline on focus.
1070 if (aDrawOverflow
== DrawOverflow::No
||
1071 !aFrame
->StyleOutline()->mOutlineStyle
.IsAuto()) {
1072 eventState
&= ~NS_EVENT_STATE_FOCUSRING
;
1075 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1076 // overflow devPxRect.
1077 Maybe
<AutoClipRect
> maybeClipRect
;
1078 if constexpr (std::is_same_v
<PaintBackendData
, DrawTarget
>) {
1079 if (aAppearance
!= StyleAppearance::FocusOutline
&&
1080 aAppearance
!= StyleAppearance::Range
&&
1081 !eventState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
1082 maybeClipRect
.emplace(aPaintData
, devPxRect
);
1086 const Colors
colors(aFrame
);
1087 DPIRatio dpiRatio
= GetDPIRatio(aFrame
, aAppearance
);
1089 switch (aAppearance
) {
1090 case StyleAppearance::Radio
: {
1091 auto rect
= CheckBoxRadioRect(devPxRect
);
1092 PaintRadioControl(aPaintData
, rect
, eventState
, colors
, dpiRatio
);
1095 case StyleAppearance::Checkbox
: {
1096 if constexpr (std::is_same_v
<PaintBackendData
, WebRenderBackendData
>) {
1097 // TODO: Need to figure out how to best draw this using WR.
1100 auto rect
= CheckBoxRadioRect(devPxRect
);
1101 PaintCheckboxControl(aPaintData
, rect
, eventState
, colors
, dpiRatio
);
1105 case StyleAppearance::Textarea
:
1106 case StyleAppearance::Textfield
:
1107 case StyleAppearance::NumberInput
:
1108 PaintTextField(aPaintData
, devPxRect
, eventState
, colors
, dpiRatio
);
1110 case StyleAppearance::Listbox
:
1111 PaintListbox(aPaintData
, devPxRect
, eventState
, colors
, dpiRatio
);
1113 case StyleAppearance::MenulistButton
:
1114 case StyleAppearance::Menulist
:
1115 PaintMenulist(aPaintData
, devPxRect
, eventState
, colors
, dpiRatio
);
1117 case StyleAppearance::MozMenulistArrowButton
:
1118 if constexpr (std::is_same_v
<PaintBackendData
, WebRenderBackendData
>) {
1119 // TODO: Need to figure out how to best draw this using WR.
1122 PaintMenulistArrowButton(aFrame
, aPaintData
, devPxRect
, eventState
);
1125 case StyleAppearance::SpinnerUpbutton
:
1126 case StyleAppearance::SpinnerDownbutton
:
1127 if constexpr (std::is_same_v
<PaintBackendData
, WebRenderBackendData
>) {
1128 // TODO: Need to figure out how to best draw this using WR.
1131 PaintSpinnerButton(aFrame
, aPaintData
, devPxRect
, eventState
,
1132 aAppearance
, colors
, dpiRatio
);
1135 case StyleAppearance::Range
:
1136 PaintRange(aFrame
, aPaintData
, devPxRect
, eventState
, colors
, dpiRatio
,
1137 IsRangeHorizontal(aFrame
));
1139 case StyleAppearance::RangeThumb
:
1140 // Painted as part of StyleAppearance::Range.
1142 case StyleAppearance::ProgressBar
:
1143 PaintProgress(aFrame
, aPaintData
, devPxRect
, eventState
, colors
, dpiRatio
,
1144 /* aIsMeter = */ false);
1146 case StyleAppearance::Progresschunk
:
1147 /* Painted as part of the progress bar */
1149 case StyleAppearance::Meter
:
1150 PaintProgress(aFrame
, aPaintData
, devPxRect
, eventState
, colors
, dpiRatio
,
1151 /* aIsMeter = */ true);
1153 case StyleAppearance::Meterchunk
:
1154 /* Painted as part of the meter bar */
1156 case StyleAppearance::ScrollbarthumbHorizontal
:
1157 case StyleAppearance::ScrollbarthumbVertical
: {
1159 aAppearance
== StyleAppearance::ScrollbarthumbHorizontal
;
1160 return GetScrollbarDrawing().PaintScrollbarThumb(
1161 aPaintData
, devPxRect
, isHorizontal
, aFrame
,
1162 *nsLayoutUtils::StyleForScrollbar(aFrame
), eventState
, docState
,
1165 case StyleAppearance::ScrollbartrackHorizontal
:
1166 case StyleAppearance::ScrollbartrackVertical
: {
1168 aAppearance
== StyleAppearance::ScrollbartrackHorizontal
;
1169 return GetScrollbarDrawing().PaintScrollbarTrack(
1170 aPaintData
, devPxRect
, isHorizontal
, aFrame
,
1171 *nsLayoutUtils::StyleForScrollbar(aFrame
), docState
, colors
,
1174 case StyleAppearance::ScrollbarHorizontal
:
1175 case StyleAppearance::ScrollbarVertical
: {
1176 bool isHorizontal
= aAppearance
== StyleAppearance::ScrollbarHorizontal
;
1177 return GetScrollbarDrawing().PaintScrollbar(
1178 aPaintData
, devPxRect
, isHorizontal
, aFrame
,
1179 *nsLayoutUtils::StyleForScrollbar(aFrame
), eventState
, docState
,
1182 case StyleAppearance::Scrollcorner
:
1183 return GetScrollbarDrawing().PaintScrollCorner(
1184 aPaintData
, devPxRect
, aFrame
,
1185 *nsLayoutUtils::StyleForScrollbar(aFrame
), docState
, colors
,
1187 case StyleAppearance::ScrollbarbuttonUp
:
1188 case StyleAppearance::ScrollbarbuttonDown
:
1189 case StyleAppearance::ScrollbarbuttonLeft
:
1190 case StyleAppearance::ScrollbarbuttonRight
:
1191 // For scrollbar-width:thin, we don't display the buttons.
1192 if (!ScrollbarDrawing::IsScrollbarWidthThin(aFrame
)) {
1193 if constexpr (std::is_same_v
<PaintBackendData
, WebRenderBackendData
>) {
1194 // TODO: Need to figure out how to best draw this using WR.
1197 GetScrollbarDrawing().PaintScrollbarButton(
1198 aPaintData
, aAppearance
, devPxRect
, aFrame
,
1199 *nsLayoutUtils::StyleForScrollbar(aFrame
), eventState
, docState
,
1204 case StyleAppearance::Button
:
1205 PaintButton(aFrame
, aPaintData
, devPxRect
, eventState
, colors
, dpiRatio
);
1207 case StyleAppearance::FocusOutline
:
1208 PaintAutoStyleOutline(aFrame
, aPaintData
, devPxRect
, colors
, dpiRatio
);
1211 // Various appearance values are used for XUL elements. Normally these
1212 // will not be available in content documents (and thus in the content
1213 // processes where the native basic theme can be used), but tests are
1214 // run with the remote XUL pref enabled and so we can get in here. So
1215 // we just return an error rather than assert.
1222 template <typename PaintBackendData
>
1223 void Theme::PaintAutoStyleOutline(nsIFrame
* aFrame
,
1224 PaintBackendData
& aPaintData
,
1225 const LayoutDeviceRect
& aRect
,
1226 const Colors
& aColors
, DPIRatio aDpiRatio
) {
1227 auto [innerColor
, middleColor
, outerColor
] = ComputeFocusRectColors(aColors
);
1228 Unused
<< middleColor
;
1229 Unused
<< outerColor
;
1231 LayoutDeviceRect
rect(aRect
);
1232 auto width
= LayoutDeviceCoord(
1233 ThemeDrawing::SnapBorderWidth(kInnerFocusOutlineWidth
, aDpiRatio
));
1234 rect
.Inflate(width
);
1236 const nscoord offset
= aFrame
->StyleOutline()->mOutlineOffset
.ToAppUnits();
1237 nscoord cssRadii
[8];
1238 if (!aFrame
->GetBorderRadii(cssRadii
)) {
1239 const CSSCoord cssOffset
= CSSCoord::FromAppUnits(offset
);
1240 const CSSCoord radius
=
1242 ? kInnerFocusOutlineWidth
1243 : std::max(kInnerFocusOutlineWidth
+ cssOffset
, CSSCoord(0.0f
));
1244 return ThemeDrawing::PaintRoundedRectWithRadius(
1245 aPaintData
, rect
, sRGBColor::White(0.0f
), innerColor
,
1246 kInnerFocusOutlineWidth
, radius
, aDpiRatio
);
1249 nsPresContext
* pc
= aFrame
->PresContext();
1250 const Float devPixelOffset
= pc
->AppUnitsToFloatDevPixels(offset
);
1252 RectCornerRadii innerRadii
;
1253 nsCSSRendering::ComputePixelRadii(cssRadii
, pc
->AppUnitsPerDevPixel(),
1256 const auto borderColor
= ToDeviceColor(innerColor
);
1257 // NOTE(emilio): This doesn't use PaintRoundedRectWithRadius because we need
1258 // to support arbitrary radii.
1259 RectCornerRadii outerRadii
;
1260 if constexpr (std::is_same_v
<PaintBackendData
, WebRenderBackendData
>) {
1261 const Float widths
[4] = {width
+ devPixelOffset
, width
+ devPixelOffset
,
1262 width
+ devPixelOffset
, width
+ devPixelOffset
};
1263 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii
, widths
, &outerRadii
);
1265 const auto dest
= wr::ToLayoutRect(rect
);
1266 const auto side
= wr::ToBorderSide(borderColor
, StyleBorderStyle::Solid
);
1267 const wr::BorderSide sides
[4] = {side
, side
, side
, side
};
1268 const bool kBackfaceIsVisible
= true;
1269 const auto wrWidths
= wr::ToBorderWidths(width
, width
, width
, width
);
1270 const auto wrRadius
= wr::ToBorderRadius(outerRadii
);
1271 aPaintData
.mBuilder
.PushBorder(dest
, dest
, kBackfaceIsVisible
, wrWidths
,
1272 {sides
, 4}, wrRadius
);
1274 const LayoutDeviceCoord halfWidth
= width
* 0.5f
;
1275 rect
.Deflate(halfWidth
);
1276 const Float widths
[4] = {
1277 halfWidth
+ devPixelOffset
, halfWidth
+ devPixelOffset
,
1278 halfWidth
+ devPixelOffset
, halfWidth
+ devPixelOffset
};
1279 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii
, widths
, &outerRadii
);
1281 MakePathForRoundedRect(aPaintData
, rect
.ToUnknownRect(), outerRadii
);
1282 aPaintData
.Stroke(path
, ColorPattern(borderColor
), StrokeOptions(width
));
1286 LayoutDeviceIntMargin
Theme::GetWidgetBorder(nsDeviceContext
* aContext
,
1288 StyleAppearance aAppearance
) {
1289 switch (aAppearance
) {
1290 case StyleAppearance::Textfield
:
1291 case StyleAppearance::Textarea
:
1292 case StyleAppearance::NumberInput
:
1293 case StyleAppearance::Listbox
:
1294 case StyleAppearance::Menulist
:
1295 case StyleAppearance::MenulistButton
:
1296 case StyleAppearance::Button
:
1297 // Return the border size from the UA sheet, even though what we paint
1298 // doesn't actually match that. We know this is the UA sheet border
1299 // because we disable native theming when different border widths are
1300 // specified by authors, see Theme::IsWidgetStyled.
1302 // The Rounded() bit is technically redundant, but needed to appease the
1303 // type system, we should always end up with full device pixels due to
1304 // round_border_to_device_pixels at style time.
1305 return LayoutDeviceIntMargin::FromAppUnits(
1306 aFrame
->StyleBorder()->GetComputedBorder(),
1307 aFrame
->PresContext()->AppUnitsPerDevPixel())
1309 case StyleAppearance::Checkbox
:
1310 case StyleAppearance::Radio
: {
1311 DPIRatio dpiRatio
= GetDPIRatio(aFrame
, aAppearance
);
1312 LayoutDeviceIntCoord w
=
1313 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth
, dpiRatio
);
1314 return LayoutDeviceIntMargin(w
, w
, w
, w
);
1317 return LayoutDeviceIntMargin();
1321 bool Theme::GetWidgetPadding(nsDeviceContext
* aContext
, nsIFrame
* aFrame
,
1322 StyleAppearance aAppearance
,
1323 LayoutDeviceIntMargin
* aResult
) {
1324 switch (aAppearance
) {
1325 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1326 // and have a meaningful baseline, so they can't have
1327 // author-specified padding.
1328 case StyleAppearance::Radio
:
1329 case StyleAppearance::Checkbox
:
1330 aResult
->SizeTo(0, 0, 0, 0);
1338 bool Theme::GetWidgetOverflow(nsDeviceContext
* aContext
, nsIFrame
* aFrame
,
1339 StyleAppearance aAppearance
,
1340 nsRect
* aOverflowRect
) {
1341 nsIntMargin overflow
;
1342 switch (aAppearance
) {
1343 case StyleAppearance::FocusOutline
:
1344 // 2px * one segment
1345 overflow
.SizeTo(2, 2, 2, 2);
1347 case StyleAppearance::Radio
:
1348 case StyleAppearance::Checkbox
:
1349 case StyleAppearance::Range
:
1350 // 2px for each outline segment, plus 1px separation, plus we paint with a
1351 // 1px extra offset, so 6px.
1352 overflow
.SizeTo(6, 6, 6, 6);
1354 case StyleAppearance::Textarea
:
1355 case StyleAppearance::Textfield
:
1356 case StyleAppearance::NumberInput
:
1357 case StyleAppearance::Listbox
:
1358 case StyleAppearance::MenulistButton
:
1359 case StyleAppearance::Menulist
:
1360 case StyleAppearance::Button
:
1361 // 2px for each segment, plus 1px separation, but we paint 1px inside
1362 // the border area so 4px overflow.
1363 overflow
.SizeTo(4, 4, 4, 4);
1369 // TODO: This should convert from device pixels to app units, not from CSS
1370 // pixels. And it should take the dpi ratio into account.
1371 // Using CSS pixels can cause the overflow to be too small if the page is
1373 aOverflowRect
->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow
.top
),
1374 CSSPixel::ToAppUnits(overflow
.right
),
1375 CSSPixel::ToAppUnits(overflow
.bottom
),
1376 CSSPixel::ToAppUnits(overflow
.left
)));
1381 auto Theme::GetScrollbarSizes(nsPresContext
* aPresContext
,
1382 StyleScrollbarWidth aWidth
, Overlay aOverlay
)
1384 return GetScrollbarDrawing().GetScrollbarSizes(aPresContext
, aWidth
,
1388 nscoord
Theme::GetCheckboxRadioPrefSize() {
1389 return CSSPixel::ToAppUnits(kCheckboxRadioContentBoxSize
);
1393 UniquePtr
<ScrollbarDrawing
> Theme::ScrollbarStyle() {
1394 switch (StaticPrefs::widget_non_native_theme_scrollbar_style()) {
1396 return MakeUnique
<ScrollbarDrawingCocoa
>();
1398 return MakeUnique
<ScrollbarDrawingGTK
>();
1400 return MakeUnique
<ScrollbarDrawingAndroid
>();
1402 return MakeUnique
<ScrollbarDrawingWin
>();
1404 return MakeUnique
<ScrollbarDrawingWin11
>();
1408 // Default to native scrollbar style for each platform.
1410 if (IsWin11OrLater()) {
1411 return MakeUnique
<ScrollbarDrawingWin11
>();
1413 return MakeUnique
<ScrollbarDrawingWin
>();
1414 #elif MOZ_WIDGET_COCOA
1415 return MakeUnique
<ScrollbarDrawingCocoa
>();
1416 #elif MOZ_WIDGET_GTK
1417 return MakeUnique
<ScrollbarDrawingGTK
>();
1419 return MakeUnique
<ScrollbarDrawingAndroid
>();
1421 # error "Unknown platform, need scrollbar implementation."
1426 Theme::GetMinimumWidgetSize(nsPresContext
* aPresContext
, nsIFrame
* aFrame
,
1427 StyleAppearance aAppearance
,
1428 LayoutDeviceIntSize
* aResult
,
1429 bool* aIsOverridable
) {
1430 DPIRatio dpiRatio
= GetDPIRatio(aFrame
, aAppearance
);
1432 aResult
->width
= aResult
->height
= 0;
1433 *aIsOverridable
= true;
1435 if (IsWidgetScrollbarPart(aAppearance
)) {
1436 *aResult
= GetScrollbarDrawing().GetMinimumWidgetSize(aPresContext
,
1437 aAppearance
, aFrame
);
1441 switch (aAppearance
) {
1442 case StyleAppearance::Button
:
1443 if (aFrame
->IsColorControlFrame()) {
1444 aResult
->height
= (kMinimumColorPickerHeight
* dpiRatio
).Rounded();
1447 case StyleAppearance::RangeThumb
:
1448 aResult
->SizeTo((kMinimumRangeThumbSize
* dpiRatio
).Rounded(),
1449 (kMinimumRangeThumbSize
* dpiRatio
).Rounded());
1451 case StyleAppearance::MozMenulistArrowButton
:
1452 aResult
->width
= (kMinimumDropdownArrowButtonWidth
* dpiRatio
).Rounded();
1454 case StyleAppearance::SpinnerUpbutton
:
1455 case StyleAppearance::SpinnerDownbutton
:
1456 aResult
->width
= (kMinimumSpinnerButtonWidth
* dpiRatio
).Rounded();
1457 aResult
->height
= (kMinimumSpinnerButtonHeight
* dpiRatio
).Rounded();
1466 nsITheme::Transparency
Theme::GetWidgetTransparency(
1467 nsIFrame
* aFrame
, StyleAppearance aAppearance
) {
1468 return GetScrollbarDrawing()
1469 .GetScrollbarPartTransparency(aFrame
, aAppearance
)
1470 .valueOr(eUnknownTransparency
);
1474 Theme::WidgetStateChanged(nsIFrame
* aFrame
, StyleAppearance aAppearance
,
1475 nsAtom
* aAttribute
, bool* aShouldRepaint
,
1476 const nsAttrValue
* aOldValue
) {
1478 // Hover/focus/active changed. Always repaint.
1479 *aShouldRepaint
= true;
1481 // Check the attribute to see if it's relevant.
1482 // disabled, checked, dlgtype, default, etc.
1483 *aShouldRepaint
= false;
1484 if (aAttribute
== nsGkAtoms::disabled
|| aAttribute
== nsGkAtoms::checked
||
1485 aAttribute
== nsGkAtoms::selected
||
1486 aAttribute
== nsGkAtoms::visuallyselected
||
1487 aAttribute
== nsGkAtoms::menuactive
||
1488 aAttribute
== nsGkAtoms::sortDirection
||
1489 aAttribute
== nsGkAtoms::focused
|| aAttribute
== nsGkAtoms::_default
||
1490 aAttribute
== nsGkAtoms::open
|| aAttribute
== nsGkAtoms::hover
) {
1491 *aShouldRepaint
= true;
1499 Theme::ThemeChanged() { return NS_OK
; }
1501 bool Theme::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance
) {
1502 return IsWidgetScrollbarPart(aAppearance
);
1505 nsITheme::ThemeGeometryType
Theme::ThemeGeometryTypeForWidget(
1506 nsIFrame
* aFrame
, StyleAppearance aAppearance
) {
1507 return eThemeGeometryTypeUnknown
;
1510 bool Theme::ThemeSupportsWidget(nsPresContext
* aPresContext
, nsIFrame
* aFrame
,
1511 StyleAppearance aAppearance
) {
1512 switch (aAppearance
) {
1513 case StyleAppearance::Radio
:
1514 case StyleAppearance::Checkbox
:
1515 case StyleAppearance::FocusOutline
:
1516 case StyleAppearance::Textarea
:
1517 case StyleAppearance::Textfield
:
1518 case StyleAppearance::Range
:
1519 case StyleAppearance::RangeThumb
:
1520 case StyleAppearance::ProgressBar
:
1521 case StyleAppearance::Progresschunk
:
1522 case StyleAppearance::Meter
:
1523 case StyleAppearance::Meterchunk
:
1524 case StyleAppearance::ScrollbarbuttonUp
:
1525 case StyleAppearance::ScrollbarbuttonDown
:
1526 case StyleAppearance::ScrollbarbuttonLeft
:
1527 case StyleAppearance::ScrollbarbuttonRight
:
1528 case StyleAppearance::ScrollbarthumbHorizontal
:
1529 case StyleAppearance::ScrollbarthumbVertical
:
1530 case StyleAppearance::ScrollbartrackHorizontal
:
1531 case StyleAppearance::ScrollbartrackVertical
:
1532 case StyleAppearance::ScrollbarHorizontal
:
1533 case StyleAppearance::ScrollbarVertical
:
1534 case StyleAppearance::Scrollcorner
:
1535 case StyleAppearance::Button
:
1536 case StyleAppearance::Listbox
:
1537 case StyleAppearance::Menulist
:
1538 case StyleAppearance::MenulistButton
:
1539 case StyleAppearance::NumberInput
:
1540 case StyleAppearance::MozMenulistArrowButton
:
1541 case StyleAppearance::SpinnerUpbutton
:
1542 case StyleAppearance::SpinnerDownbutton
:
1543 return !IsWidgetStyled(aPresContext
, aFrame
, aAppearance
);
1549 bool Theme::WidgetIsContainer(StyleAppearance aAppearance
) {
1550 switch (aAppearance
) {
1551 case StyleAppearance::MozMenulistArrowButton
:
1552 case StyleAppearance::Radio
:
1553 case StyleAppearance::Checkbox
:
1560 bool Theme::ThemeDrawsFocusForWidget(nsIFrame
*, StyleAppearance
) {
1564 bool Theme::ThemeNeedsComboboxDropmarker() { return true; }
1566 bool Theme::ThemeSupportsScrollbarButtons() {
1567 return GetScrollbarDrawing().ShouldDrawScrollbarButtons();
1570 } // namespace mozilla::widget