Bug 1805294 [wpt PR 37463] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / widget / Theme.cpp
blob594a994054a898b571c96411d9fc7ac85a1542b6
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 "Theme.h"
7 #include <utility>
8 #include "ThemeCocoa.h"
10 #include "ThemeDrawing.h"
11 #include "Units.h"
12 #include "mozilla/MathAlgorithms.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "mozilla/dom/Document.h"
15 #include "mozilla/dom/HTMLMeterElement.h"
16 #include "mozilla/dom/HTMLProgressElement.h"
17 #include "mozilla/gfx/Rect.h"
18 #include "mozilla/gfx/Types.h"
19 #include "mozilla/gfx/Filters.h"
20 #include "mozilla/RelativeLuminanceUtils.h"
21 #include "mozilla/StaticPrefs_widget.h"
22 #include "mozilla/webrender/WebRenderAPI.h"
23 #include "nsCSSColorUtils.h"
24 #include "nsCSSRendering.h"
25 #include "nsScrollbarFrame.h"
26 #include "nsIScrollbarMediator.h"
27 #include "nsDeviceContext.h"
28 #include "nsLayoutUtils.h"
29 #include "nsRangeFrame.h"
30 #include "PathHelpers.h"
31 #include "ScrollbarDrawingAndroid.h"
32 #include "ScrollbarDrawingCocoa.h"
33 #include "ScrollbarDrawingGTK.h"
34 #include "ScrollbarDrawingWin.h"
35 #include "ScrollbarDrawingWin11.h"
37 #ifdef XP_WIN
38 # include "mozilla/WindowsVersion.h"
39 #endif
41 using namespace mozilla;
42 using namespace mozilla::dom;
43 using namespace mozilla::gfx;
44 using namespace mozilla::widget;
46 namespace {
48 static constexpr gfx::sRGBColor sColorGrey10(
49 gfx::sRGBColor::UnusualFromARGB(0xffe9e9ed));
50 static constexpr gfx::sRGBColor sColorGrey10Alpha50(
51 gfx::sRGBColor::UnusualFromARGB(0x7fe9e9ed));
52 static constexpr gfx::sRGBColor sColorGrey20(
53 gfx::sRGBColor::UnusualFromARGB(0xffd0d0d7));
54 static constexpr gfx::sRGBColor sColorGrey30(
55 gfx::sRGBColor::UnusualFromARGB(0xffb1b1b9));
56 static constexpr gfx::sRGBColor sColorGrey40(
57 gfx::sRGBColor::UnusualFromARGB(0xff8f8f9d));
58 static constexpr gfx::sRGBColor sColorGrey40Alpha50(
59 gfx::sRGBColor::UnusualFromARGB(0x7f8f8f9d));
60 static constexpr gfx::sRGBColor sColorGrey50(
61 gfx::sRGBColor::UnusualFromARGB(0xff676774));
62 static constexpr gfx::sRGBColor sColorGrey60(
63 gfx::sRGBColor::UnusualFromARGB(0xff484851));
65 static constexpr gfx::sRGBColor sColorMeterGreen10(
66 gfx::sRGBColor::UnusualFromARGB(0xff00ab60));
67 static constexpr gfx::sRGBColor sColorMeterGreen20(
68 gfx::sRGBColor::UnusualFromARGB(0xff056139));
69 static constexpr gfx::sRGBColor sColorMeterYellow10(
70 gfx::sRGBColor::UnusualFromARGB(0xffffbd4f));
71 static constexpr gfx::sRGBColor sColorMeterYellow20(
72 gfx::sRGBColor::UnusualFromARGB(0xffd2811e));
73 static constexpr gfx::sRGBColor sColorMeterRed10(
74 gfx::sRGBColor::UnusualFromARGB(0xffe22850));
75 static constexpr gfx::sRGBColor sColorMeterRed20(
76 gfx::sRGBColor::UnusualFromARGB(0xff810220));
78 static const CSSCoord kMinimumRangeThumbSize = 20.0f;
79 static const CSSCoord kMinimumDropdownArrowButtonWidth = 18.0f;
80 static const CSSCoord kMinimumSpinnerButtonWidth = 18.0f;
81 static const CSSCoord kMinimumSpinnerButtonHeight = 9.0f;
82 static const CSSCoord kButtonBorderWidth = 1.0f;
83 static const CSSCoord kMenulistBorderWidth = 1.0f;
84 static const CSSCoord kTextFieldBorderWidth = 1.0f;
85 static const CSSCoord kRangeHeight = 6.0f;
86 static const CSSCoord kProgressbarHeight = 6.0f;
87 static const CSSCoord kMeterHeight = 12.0f;
89 // nsCheckboxRadioFrame takes the bottom of the content box as the baseline.
90 // This border-width makes its baseline 2px under the bottom, which is nice.
91 static constexpr CSSCoord kCheckboxRadioBorderWidth = 2.0f;
93 static constexpr sRGBColor sTransparent = sRGBColor::White(0.0);
95 // This pushes and pops a clip rect to the draw target.
97 // This is done to reduce fuzz in places where we may have antialiasing,
98 // because skia is not clip-invariant: given different clips, it does not
99 // guarantee the same result, even if the painted content doesn't intersect
100 // the clips.
102 // This is a bit sad, overall, but...
103 struct MOZ_RAII AutoClipRect {
104 AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
105 mDt.PushClipRect(aRect.ToUnknownRect());
108 ~AutoClipRect() { mDt.PopClip(); }
110 private:
111 DrawTarget& mDt;
114 static StaticRefPtr<Theme> gNativeInstance;
115 static StaticRefPtr<Theme> gNonNativeInstance;
116 static StaticRefPtr<Theme> gRDMInstance;
118 } // namespace
120 #ifdef ANDROID
121 already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
122 // Android doesn't have a native theme.
123 return do_AddRef(new Theme(Theme::ScrollbarStyle()));
125 #endif
127 already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
128 if (MOZ_UNLIKELY(!gNonNativeInstance)) {
129 UniquePtr<ScrollbarDrawing> scrollbarDrawing = Theme::ScrollbarStyle();
130 #ifdef MOZ_WIDGET_COCOA
131 gNonNativeInstance = new ThemeCocoa(std::move(scrollbarDrawing));
132 #else
133 gNonNativeInstance = new Theme(std::move(scrollbarDrawing));
134 #endif
135 ClearOnShutdown(&gNonNativeInstance);
137 return do_AddRef(gNonNativeInstance);
140 already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
141 if (MOZ_UNLIKELY(!gNativeInstance)) {
142 gNativeInstance = do_CreateNativeThemeDoNotUseDirectly();
143 ClearOnShutdown(&gNativeInstance);
145 return do_AddRef(gNativeInstance);
148 already_AddRefed<nsITheme> do_GetRDMThemeDoNotUseDirectly() {
149 if (MOZ_UNLIKELY(!gRDMInstance)) {
150 gRDMInstance = new Theme(MakeUnique<ScrollbarDrawingAndroid>());
151 ClearOnShutdown(&gRDMInstance);
153 return do_AddRef(gRDMInstance);
156 namespace mozilla::widget {
158 NS_IMPL_ISUPPORTS_INHERITED(Theme, nsNativeTheme, nsITheme)
160 static constexpr nsLiteralCString kPrefs[] = {
161 "widget.non-native-theme.use-theme-accent"_ns,
162 "widget.non-native-theme.win.scrollbar.use-system-size"_ns,
163 "widget.non-native-theme.scrollbar.size.override"_ns,
164 "widget.non-native-theme.scrollbar.style"_ns,
167 void Theme::Init() {
168 for (const auto& pref : kPrefs) {
169 Preferences::RegisterCallback(PrefChangedCallback, pref);
171 LookAndFeelChanged();
174 void Theme::Shutdown() {
175 for (const auto& pref : kPrefs) {
176 Preferences::UnregisterCallback(PrefChangedCallback, pref);
180 /* static */
181 void Theme::LookAndFeelChanged() {
182 ThemeColors::RecomputeAccentColors();
183 if (gNonNativeInstance) {
184 gNonNativeInstance->SetScrollbarDrawing(ScrollbarStyle());
186 if (gNativeInstance) {
187 gNativeInstance->SetScrollbarDrawing(ScrollbarStyle());
191 /* static */
192 auto Theme::GetDPIRatio(nsPresContext* aPc, StyleAppearance aAppearance)
193 -> DPIRatio {
194 // Widgets react to zoom, except scrollbars.
195 if (IsWidgetScrollbarPart(aAppearance)) {
196 return ScrollbarDrawing::GetDPIRatioForScrollbarPart(aPc);
198 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc->AppUnitsPerDevPixel());
201 /* static */
202 auto Theme::GetDPIRatio(nsIFrame* aFrame, StyleAppearance aAppearance)
203 -> DPIRatio {
204 return GetDPIRatio(aFrame->PresContext(), aAppearance);
207 // Checkbox and radio need to preserve aspect-ratio for compat. We also snap the
208 // size to exact device pixels to avoid snapping disorting the circles.
209 static LayoutDeviceRect CheckBoxRadioRect(const LayoutDeviceRect& aRect) {
210 // Place a square rect in the center of aRect.
211 auto size = std::trunc(std::min(aRect.width, aRect.height));
212 auto position = aRect.Center() - LayoutDevicePoint(size * 0.5, size * 0.5);
213 return LayoutDeviceRect(position, LayoutDeviceSize(size, size));
216 std::tuple<sRGBColor, sRGBColor, sRGBColor> Theme::ComputeCheckboxColors(
217 const ElementState& aState, StyleAppearance aAppearance,
218 const Colors& aColors) {
219 MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
220 aAppearance == StyleAppearance::Radio);
222 bool isDisabled = aState.HasState(ElementState::DISABLED);
223 bool isChecked = aState.HasState(ElementState::CHECKED);
224 bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
225 aState.HasState(ElementState::INDETERMINATE);
227 if (isChecked || isIndeterminate) {
228 if (isDisabled) {
229 auto bg = ComputeBorderColor(aState, aColors, OutlineCoversBorder::No);
230 auto fg = aColors.HighContrast()
231 ? aColors.System(StyleSystemColor::Graytext)
232 : sRGBColor::White(.8f);
233 return std::make_tuple(bg, bg, fg);
236 if (aColors.HighContrast()) {
237 auto bg = aColors.System(StyleSystemColor::Selecteditem);
238 auto fg = aColors.System(StyleSystemColor::Selecteditemtext);
239 return std::make_tuple(bg, bg, fg);
242 bool isActive =
243 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
244 bool isHovered = aState.HasState(ElementState::HOVER);
245 const auto& bg = isActive ? aColors.Accent().GetDarker()
246 : isHovered ? aColors.Accent().GetDark()
247 : aColors.Accent().Get();
248 const auto& fg = aColors.Accent().GetForeground();
249 return std::make_tuple(bg, bg, fg);
252 auto [bg, border] =
253 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::No);
254 // We don't paint a checkmark in this case so any color would do.
255 return std::make_tuple(bg, border, sTransparent);
258 sRGBColor Theme::ComputeBorderColor(const ElementState& aState,
259 const Colors& aColors,
260 OutlineCoversBorder aOutlineCoversBorder) {
261 bool isDisabled = aState.HasState(ElementState::DISABLED);
262 if (aColors.HighContrast()) {
263 return aColors.System(isDisabled ? StyleSystemColor::Graytext
264 : StyleSystemColor::Buttontext);
266 bool isActive =
267 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
268 bool isHovered = aState.HasState(ElementState::HOVER);
269 bool isFocused = aState.HasState(ElementState::FOCUSRING);
270 if (isDisabled) {
271 return sColorGrey40Alpha50;
273 if (isFocused && aOutlineCoversBorder == OutlineCoversBorder::Yes) {
274 // If we draw the outline over the border, prevent issues where the border
275 // shows underneath if it snaps in the wrong direction by using a
276 // transparent border. An alternative to this is ensuring that we snap the
277 // offset in PaintRoundedFocusRect the same was a we snap border widths, so
278 // that negative offsets are guaranteed to cover the border.
279 // But this looks harder to mess up.
280 return sTransparent;
282 bool dark = aColors.IsDark();
283 if (isActive) {
284 return dark ? sColorGrey20 : sColorGrey60;
286 if (isHovered) {
287 return dark ? sColorGrey30 : sColorGrey50;
289 return sColorGrey40;
292 std::pair<sRGBColor, sRGBColor> Theme::ComputeButtonColors(
293 const ElementState& aState, const Colors& aColors, nsIFrame* aFrame) {
294 bool isActive =
295 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
296 bool isDisabled = aState.HasState(ElementState::DISABLED);
297 bool isHovered = aState.HasState(ElementState::HOVER);
299 nscolor backgroundColor = [&] {
300 if (isDisabled) {
301 return aColors.SystemNs(StyleSystemColor::MozButtondisabledface);
303 if (isActive) {
304 return aColors.SystemNs(StyleSystemColor::MozButtonactiveface);
306 if (isHovered) {
307 return aColors.SystemNs(StyleSystemColor::MozButtonhoverface);
309 return aColors.SystemNs(StyleSystemColor::Buttonface);
310 }();
312 if (aState.HasState(ElementState::AUTOFILL)) {
313 backgroundColor = NS_ComposeColors(
314 backgroundColor,
315 aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
318 const sRGBColor borderColor =
319 ComputeBorderColor(aState, aColors, OutlineCoversBorder::Yes);
320 return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
323 std::pair<sRGBColor, sRGBColor> Theme::ComputeTextfieldColors(
324 const ElementState& aState, const Colors& aColors,
325 OutlineCoversBorder aOutlineCoversBorder) {
326 nscolor backgroundColor = [&] {
327 if (aState.HasState(ElementState::DISABLED)) {
328 return aColors.SystemNs(StyleSystemColor::MozDisabledfield);
330 return aColors.SystemNs(StyleSystemColor::Field);
331 }();
333 if (aState.HasState(ElementState::AUTOFILL)) {
334 backgroundColor = NS_ComposeColors(
335 backgroundColor,
336 aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
339 const sRGBColor borderColor =
340 ComputeBorderColor(aState, aColors, aOutlineCoversBorder);
341 return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
344 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeProgressColors(
345 const ElementState& aState, const Colors& aColors) {
346 if (aColors.HighContrast()) {
347 return aColors.SystemPair(StyleSystemColor::Selecteditem,
348 StyleSystemColor::Buttontext);
351 bool isActive =
352 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
353 bool isDisabled = aState.HasState(ElementState::DISABLED);
354 bool isHovered = aState.HasState(ElementState::HOVER);
356 if (isDisabled) {
357 return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
359 if (isActive || isHovered) {
360 return std::make_pair(aColors.Accent().GetDark(),
361 aColors.Accent().GetDarker());
363 return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
366 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeTrackColors(
367 const ElementState& aState, const Colors& aColors) {
368 if (aColors.HighContrast()) {
369 return aColors.SystemPair(StyleSystemColor::Window,
370 StyleSystemColor::Buttontext);
372 bool isActive =
373 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
374 bool isDisabled = aState.HasState(ElementState::DISABLED);
375 bool isHovered = aState.HasState(ElementState::HOVER);
377 if (isDisabled) {
378 return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
380 if (isActive || isHovered) {
381 return std::make_pair(sColorGrey20, sColorGrey50);
383 return std::make_pair(sColorGrey10, sColorGrey40);
386 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeThumbColors(
387 const ElementState& aState, const Colors& aColors) {
388 if (aColors.HighContrast()) {
389 return aColors.SystemPair(StyleSystemColor::Selecteditemtext,
390 StyleSystemColor::Selecteditem);
393 bool isActive =
394 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
395 bool isDisabled = aState.HasState(ElementState::DISABLED);
396 bool isHovered = aState.HasState(ElementState::HOVER);
398 const sRGBColor& backgroundColor = [&] {
399 if (isDisabled) {
400 return sColorGrey40;
402 if (isActive) {
403 return aColors.Accent().Get();
405 if (isHovered) {
406 return sColorGrey60;
408 return sColorGrey50;
409 }();
411 const sRGBColor borderColor = sRGBColor::OpaqueWhite();
412 return std::make_pair(backgroundColor, borderColor);
415 std::pair<sRGBColor, sRGBColor> Theme::ComputeProgressColors(
416 const Colors& aColors) {
417 if (aColors.HighContrast()) {
418 return aColors.SystemPair(StyleSystemColor::Selecteditem,
419 StyleSystemColor::Buttontext);
421 return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
424 std::pair<sRGBColor, sRGBColor> Theme::ComputeProgressTrackColors(
425 const Colors& aColors) {
426 if (aColors.HighContrast()) {
427 return aColors.SystemPair(StyleSystemColor::Buttonface,
428 StyleSystemColor::Buttontext);
430 return std::make_pair(sColorGrey10, sColorGrey40);
433 std::pair<sRGBColor, sRGBColor> Theme::ComputeMeterchunkColors(
434 const ElementState& aMeterState, const Colors& aColors) {
435 if (aColors.HighContrast()) {
436 return ComputeProgressColors(aColors);
438 sRGBColor borderColor = sColorMeterGreen20;
439 sRGBColor chunkColor = sColorMeterGreen10;
441 if (aMeterState.HasState(ElementState::SUB_OPTIMUM)) {
442 borderColor = sColorMeterYellow20;
443 chunkColor = sColorMeterYellow10;
444 } else if (aMeterState.HasState(ElementState::SUB_SUB_OPTIMUM)) {
445 borderColor = sColorMeterRed20;
446 chunkColor = sColorMeterRed10;
449 return std::make_pair(chunkColor, borderColor);
452 std::array<sRGBColor, 3> Theme::ComputeFocusRectColors(const Colors& aColors) {
453 if (aColors.HighContrast()) {
454 return {aColors.System(StyleSystemColor::Selecteditem),
455 aColors.System(StyleSystemColor::Buttontext),
456 aColors.System(StyleSystemColor::Window)};
458 const auto& accent = aColors.Accent();
459 const sRGBColor middle =
460 aColors.IsDark() ? sRGBColor::Black(.3f) : sRGBColor::White(.3f);
461 return {accent.Get(), middle, accent.GetLight()};
464 template <typename PaintBackendData>
465 void Theme::PaintRoundedFocusRect(PaintBackendData& aBackendData,
466 const LayoutDeviceRect& aRect,
467 const Colors& aColors, DPIRatio aDpiRatio,
468 CSSCoord aRadius, CSSCoord aOffset) {
469 // NOTE(emilio): If the widths or offsets here change, make sure to tweak
470 // the GetWidgetOverflow path for FocusOutline.
471 auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors(aColors);
473 LayoutDeviceRect focusRect(aRect);
475 // The focus rect is painted outside of the border area (aRect), see:
477 // data:text/html,<div style="border: 1px solid; outline: 2px solid
478 // red">Foobar</div>
480 // But some controls might provide a negative offset to cover the border, if
481 // necessary.
482 CSSCoord strokeWidth = 2.0f;
483 auto strokeWidthDevPx =
484 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
485 CSSCoord strokeRadius = aRadius;
486 focusRect.Inflate(aOffset * aDpiRatio + strokeWidthDevPx);
488 ThemeDrawing::PaintRoundedRectWithRadius(
489 aBackendData, focusRect, sTransparent, innerColor, strokeWidth,
490 strokeRadius, aDpiRatio);
492 strokeWidth = CSSCoord(1.0f);
493 strokeWidthDevPx =
494 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
495 strokeRadius += strokeWidth;
496 focusRect.Inflate(strokeWidthDevPx);
498 ThemeDrawing::PaintRoundedRectWithRadius(
499 aBackendData, focusRect, sTransparent, middleColor, strokeWidth,
500 strokeRadius, aDpiRatio);
502 strokeWidth = CSSCoord(2.0f);
503 strokeWidthDevPx =
504 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
505 strokeRadius += strokeWidth;
506 focusRect.Inflate(strokeWidthDevPx);
508 ThemeDrawing::PaintRoundedRectWithRadius(
509 aBackendData, focusRect, sTransparent, outerColor, strokeWidth,
510 strokeRadius, aDpiRatio);
513 void Theme::PaintCheckboxControl(DrawTarget& aDrawTarget,
514 const LayoutDeviceRect& aRect,
515 const ElementState& aState,
516 const Colors& aColors, DPIRatio aDpiRatio) {
517 auto [backgroundColor, borderColor, checkColor] =
518 ComputeCheckboxColors(aState, StyleAppearance::Checkbox, aColors);
520 const CSSCoord radius = 2.0f;
521 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
522 if (backgroundColor == borderColor) {
523 borderWidth = 0.0f;
525 ThemeDrawing::PaintRoundedRectWithRadius(aDrawTarget, aRect,
526 backgroundColor, borderColor,
527 borderWidth, radius, aDpiRatio);
530 if (aState.HasState(ElementState::INDETERMINATE)) {
531 PaintIndeterminateMark(aDrawTarget, aRect, checkColor);
532 } else if (aState.HasState(ElementState::CHECKED)) {
533 PaintCheckMark(aDrawTarget, aRect, checkColor);
536 if (aState.HasState(ElementState::FOCUSRING)) {
537 PaintRoundedFocusRect(aDrawTarget, aRect, aColors, aDpiRatio, 5.0f, 1.0f);
541 constexpr CSSCoord kCheckboxRadioContentBoxSize = 10.0f;
542 constexpr CSSCoord kCheckboxRadioBorderBoxSize =
543 kCheckboxRadioContentBoxSize + kCheckboxRadioBorderWidth * 2.0f;
545 void Theme::PaintCheckMark(DrawTarget& aDrawTarget,
546 const LayoutDeviceRect& aRect,
547 const sRGBColor& aColor) {
548 // Points come from the coordinates on a 14X14 (kCheckboxRadioBorderBoxSize)
549 // unit box centered at 0,0
550 const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f,
551 3.5f, -0.5f, -1.5f, -3.5f};
552 const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f,
553 -4.0f, 1.0f, 1.25f, -1.0f};
554 const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
555 const float scale =
556 ThemeDrawing::ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
557 auto center = aRect.Center().ToUnknownPoint();
559 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
560 Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale);
561 builder->MoveTo(p);
562 for (int32_t i = 1; i < checkNumPoints; i++) {
563 p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale);
564 builder->LineTo(p);
566 RefPtr<Path> path = builder->Finish();
568 aDrawTarget.Fill(path, ColorPattern(ToDeviceColor(aColor)));
571 void Theme::PaintIndeterminateMark(DrawTarget& aDrawTarget,
572 const LayoutDeviceRect& aRect,
573 const sRGBColor& aColor) {
574 const CSSCoord borderWidth = 2.0f;
575 const float scale =
576 ThemeDrawing::ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
578 Rect rect = aRect.ToUnknownRect();
579 rect.y += (rect.height / 2) - (borderWidth * scale / 2);
580 rect.height = borderWidth * scale;
581 rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
582 rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
584 aDrawTarget.FillRect(rect, ColorPattern(ToDeviceColor(aColor)));
587 template <typename PaintBackendData>
588 void Theme::PaintStrokedCircle(PaintBackendData& aPaintData,
589 const LayoutDeviceRect& aRect,
590 const sRGBColor& aBackgroundColor,
591 const sRGBColor& aBorderColor,
592 const CSSCoord aBorderWidth,
593 DPIRatio aDpiRatio) {
594 auto radius = LayoutDeviceCoord(aRect.Size().width) / aDpiRatio;
595 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, aBackgroundColor,
596 aBorderColor, aBorderWidth, radius,
597 aDpiRatio);
600 void Theme::PaintCircleShadow(WebRenderBackendData& aWrData,
601 const LayoutDeviceRect& aBoxRect,
602 const LayoutDeviceRect& aClipRect,
603 float aShadowAlpha, const CSSPoint& aShadowOffset,
604 CSSCoord aShadowBlurStdDev, DPIRatio aDpiRatio) {
605 const bool kBackfaceIsVisible = true;
606 const LayoutDeviceCoord stdDev = aShadowBlurStdDev * aDpiRatio;
607 const LayoutDevicePoint shadowOffset = aShadowOffset * aDpiRatio;
608 const IntSize inflation =
609 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
610 LayoutDeviceRect shadowRect = aBoxRect;
611 shadowRect.MoveBy(shadowOffset);
612 shadowRect.Inflate(inflation.width, inflation.height);
613 const auto boxRect = wr::ToLayoutRect(aBoxRect);
614 aWrData.mBuilder.PushBoxShadow(
615 wr::ToLayoutRect(shadowRect), wr::ToLayoutRect(aClipRect),
616 kBackfaceIsVisible, boxRect,
617 wr::ToLayoutVector2D(aShadowOffset * aDpiRatio),
618 wr::ToColorF(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)), stdDev,
619 /* aSpread = */ 0.0f,
620 wr::ToBorderRadius(gfx::RectCornerRadii(aBoxRect.Size().width)),
621 wr::BoxShadowClipMode::Outset);
624 void Theme::PaintCircleShadow(DrawTarget& aDrawTarget,
625 const LayoutDeviceRect& aBoxRect,
626 const LayoutDeviceRect& aClipRect,
627 float aShadowAlpha, const CSSPoint& aShadowOffset,
628 CSSCoord aShadowBlurStdDev, DPIRatio aDpiRatio) {
629 Float stdDev = aShadowBlurStdDev * aDpiRatio;
630 Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint();
632 RefPtr<FilterNode> blurFilter =
633 aDrawTarget.CreateFilter(FilterType::GAUSSIAN_BLUR);
634 if (!blurFilter) {
635 return;
638 blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
640 IntSize inflation =
641 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
642 Rect inflatedRect = aBoxRect.ToUnknownRect();
643 inflatedRect.Inflate(inflation.width, inflation.height);
644 Rect sourceRectInFilterSpace =
645 inflatedRect - aBoxRect.TopLeft().ToUnknownPoint();
646 Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset;
648 IntSize dtSize = RoundedToInt(aBoxRect.Size().ToUnknownSize());
649 RefPtr<DrawTarget> ellipseDT = aDrawTarget.CreateSimilarDrawTargetForFilter(
650 dtSize, SurfaceFormat::A8, blurFilter, blurFilter,
651 sourceRectInFilterSpace, destinationPointOfSourceRect);
652 if (!ellipseDT) {
653 return;
656 AutoClipRect clipRect(aDrawTarget, aClipRect);
658 RefPtr<Path> ellipse = MakePathForEllipse(
659 *ellipseDT, (aBoxRect - aBoxRect.TopLeft()).Center().ToUnknownPoint(),
660 aBoxRect.Size().ToUnknownSize());
661 ellipseDT->Fill(ellipse,
662 ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)));
663 RefPtr<SourceSurface> ellipseSurface = ellipseDT->Snapshot();
665 blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface);
666 aDrawTarget.DrawFilter(blurFilter, sourceRectInFilterSpace,
667 destinationPointOfSourceRect);
670 template <typename PaintBackendData>
671 void Theme::PaintRadioControl(PaintBackendData& aPaintData,
672 const LayoutDeviceRect& aRect,
673 const ElementState& aState, const Colors& aColors,
674 DPIRatio aDpiRatio) {
675 auto [backgroundColor, borderColor, checkColor] =
676 ComputeCheckboxColors(aState, StyleAppearance::Radio, aColors);
678 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
679 if (backgroundColor == borderColor) {
680 borderWidth = 0.0f;
682 PaintStrokedCircle(aPaintData, aRect, backgroundColor, borderColor,
683 borderWidth, aDpiRatio);
686 if (aState.HasState(ElementState::CHECKED)) {
687 LayoutDeviceRect rect(aRect);
688 auto width = LayoutDeviceCoord(
689 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth, aDpiRatio));
690 rect.Deflate(width);
692 PaintStrokedCircle(aPaintData, rect, backgroundColor, checkColor,
693 kCheckboxRadioBorderWidth, aDpiRatio);
696 if (aState.HasState(ElementState::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 ElementState& 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,
713 radius, aDpiRatio);
715 if (aState.HasState(ElementState::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 ElementState& 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,
733 radius, aDpiRatio);
735 if (aState.HasState(ElementState::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 ElementState& 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,
751 radius, aDpiRatio);
753 if (aState.HasState(ElementState::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 ElementState& aState) {
762 // not const: these may be negated in-place below
763 float polygonX[] = {-4.0f, -0.5f, 0.5f, 4.0f, 4.0f,
764 3.0f, 0.0f, 0.0f, -3.0f, -4.0f};
765 float polygonY[] = {-1, 3.0f, 3.0f, -1.0f, -2.0f,
766 -2.0f, 1.5f, 1.5f, -2.0f, -2.0f};
768 const float kPolygonSize = kMinimumDropdownArrowButtonWidth;
770 auto const [xs, ys] = [&] {
771 using Pair = std::pair<const float*, const float*>;
772 switch (aFrame->GetWritingMode().GetBlockDir()) {
773 case WritingMode::BlockDir::eBlockRL:
774 // rotate 90°: [[0,1],[-1,0]]
775 for (float& f : polygonY) {
776 f = -f;
778 return Pair(polygonY, polygonX);
780 case WritingMode::BlockDir::eBlockLR:
781 // rotate 270°: [[0,-1],[1,0]]
782 for (float& f : polygonX) {
783 f = -f;
785 return Pair(polygonY, polygonX);
787 case WritingMode::BlockDir::eBlockTB:
788 // rotate 0°: [[1,0],[0,1]]
789 return Pair(polygonX, polygonY);
791 MOZ_ASSERT_UNREACHABLE("unhandled BlockDir");
792 return Pair(polygonX, polygonY);
793 }();
795 const auto arrowColor = sRGBColor::FromABGR(
796 nsLayoutUtils::GetColor(aFrame, &nsStyleText::mWebkitTextFillColor));
797 ThemeDrawing::PaintArrow(aDrawTarget, aRect, xs, ys, kPolygonSize,
798 ArrayLength(polygonX), arrowColor);
801 void Theme::PaintSpinnerButton(nsIFrame* aFrame, DrawTarget& aDrawTarget,
802 const LayoutDeviceRect& aRect,
803 const ElementState& aState,
804 StyleAppearance aAppearance,
805 const Colors& aColors, DPIRatio aDpiRatio) {
806 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
808 aDrawTarget.FillRect(aRect.ToUnknownRect(),
809 ColorPattern(ToDeviceColor(backgroundColor)));
811 const float kPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
812 2.5f, 0.0f, 0.0f, -2.5f, -3.5f};
813 float polygonY[] = {-1.5f, 1.5f, 1.5f, -1.5f, -2.5f,
814 -2.5f, 0.0f, 0.0f, -2.5f, -2.5f};
816 const float kPolygonSize = kMinimumSpinnerButtonHeight;
817 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
818 for (auto& coord : polygonY) {
819 coord = -coord;
823 ThemeDrawing::PaintArrow(aDrawTarget, aRect, kPolygonX, polygonY,
824 kPolygonSize, ArrayLength(kPolygonX), borderColor);
827 template <typename PaintBackendData>
828 void Theme::PaintRange(nsIFrame* aFrame, PaintBackendData& aPaintData,
829 const LayoutDeviceRect& aRect,
830 const ElementState& aState, const Colors& aColors,
831 DPIRatio aDpiRatio, bool aHorizontal) {
832 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
833 if (!rangeFrame) {
834 return;
837 auto tickMarks = rangeFrame->TickMarks();
838 double progress = rangeFrame->GetValueAsFractionOfRange();
839 auto rect = aRect;
840 LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
841 kMinimumRangeThumbSize * aDpiRatio);
842 LayoutDeviceRect progressClipRect(aRect);
843 LayoutDeviceRect trackClipRect(aRect);
844 const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
845 const LayoutDeviceCoord tickMarkWidth(
846 ThemeDrawing::SnapBorderWidth(1.0f, aDpiRatio));
847 const LayoutDeviceCoord tickMarkHeight(
848 ThemeDrawing::SnapBorderWidth(5.0f, aDpiRatio));
849 LayoutDevicePoint tickMarkOrigin, tickMarkDirection;
850 LayoutDeviceSize tickMarkSize;
851 if (aHorizontal) {
852 rect.height = verticalSize;
853 rect.y = aRect.y + (aRect.height - rect.height) / 2;
854 tickMarkSize = LayoutDeviceSize(tickMarkWidth, tickMarkHeight);
855 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
857 if (IsFrameRTL(aFrame)) {
858 tickMarkOrigin =
859 LayoutDevicePoint(aRect.XMost() - thumbRect.width / 2, aRect.YMost());
860 tickMarkDirection = LayoutDevicePoint(-1.0f, 0.0f);
861 thumbRect.x =
862 aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
863 float midPoint = thumbRect.Center().X();
864 trackClipRect.SetBoxX(aRect.X(), midPoint);
865 progressClipRect.SetBoxX(midPoint, aRect.XMost());
866 } else {
867 tickMarkOrigin =
868 LayoutDevicePoint(aRect.x + thumbRect.width / 2, aRect.YMost());
869 tickMarkDirection = LayoutDevicePoint(1.0, 0.0f);
870 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
871 float midPoint = thumbRect.Center().X();
872 progressClipRect.SetBoxX(aRect.X(), midPoint);
873 trackClipRect.SetBoxX(midPoint, aRect.XMost());
875 } else {
876 rect.width = verticalSize;
877 rect.x = aRect.x + (aRect.width - rect.width) / 2;
878 tickMarkOrigin = LayoutDevicePoint(aRect.XMost() - tickMarkHeight / 4,
879 aRect.YMost() - thumbRect.width / 2);
880 tickMarkDirection = LayoutDevicePoint(0.0f, -1.0f);
881 tickMarkSize = LayoutDeviceSize(tickMarkHeight, tickMarkWidth);
882 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
884 thumbRect.y =
885 aRect.y + (aRect.height - thumbRect.height) * (1.0 - progress);
886 float midPoint = thumbRect.Center().Y();
887 trackClipRect.SetBoxY(aRect.Y(), midPoint);
888 progressClipRect.SetBoxY(midPoint, aRect.YMost());
891 const CSSCoord borderWidth = 1.0f;
892 const CSSCoord radius = 3.0f;
894 auto [progressColor, progressBorderColor] =
895 ComputeRangeProgressColors(aState, aColors);
896 auto [trackColor, trackBorderColor] =
897 ComputeRangeTrackColors(aState, aColors);
898 auto tickMarkColor = trackBorderColor;
900 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, progressClipRect,
901 progressColor, progressBorderColor,
902 borderWidth, radius, aDpiRatio);
904 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, trackClipRect,
905 trackColor, trackBorderColor,
906 borderWidth, radius, aDpiRatio);
908 if (!aState.HasState(ElementState::DISABLED)) {
909 // Ensure the shadow doesn't expand outside of our overflow rect declared in
910 // GetWidgetOverflow().
911 auto overflowRect = aRect;
912 overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio);
913 // Thumb shadow
914 PaintCircleShadow(aPaintData, thumbRect, overflowRect, 0.3f,
915 CSSPoint(0.0f, 2.0f), 2.0f, aDpiRatio);
918 tickMarkDirection.x *= aRect.width - thumbRect.width;
919 tickMarkDirection.y *= aRect.height - thumbRect.height;
920 tickMarkOrigin -=
921 LayoutDevicePoint(tickMarkSize.width, tickMarkSize.height) / 2;
922 auto tickMarkRect = LayoutDeviceRect(tickMarkOrigin, tickMarkSize);
923 for (auto tickMark : tickMarks) {
924 auto tickMarkOffset =
925 tickMarkDirection *
926 float(rangeFrame->GetDoubleAsFractionOfRange(tickMark));
927 ThemeDrawing::FillRect(aPaintData, tickMarkRect + tickMarkOffset,
928 tickMarkColor);
931 // Draw the thumb on top.
932 const CSSCoord thumbBorderWidth = 2.0f;
933 auto [thumbColor, thumbBorderColor] =
934 ComputeRangeThumbColors(aState, aColors);
936 PaintStrokedCircle(aPaintData, thumbRect, thumbColor, thumbBorderColor,
937 thumbBorderWidth, aDpiRatio);
939 if (aState.HasState(ElementState::FOCUSRING)) {
940 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, radius, 1.0f);
944 template <typename PaintBackendData>
945 void Theme::PaintProgress(nsIFrame* aFrame, PaintBackendData& aPaintData,
946 const LayoutDeviceRect& aRect,
947 const ElementState& aState, const Colors& aColors,
948 DPIRatio aDpiRatio, bool aIsMeter) {
949 const CSSCoord borderWidth = 1.0f;
950 const CSSCoord radius = aIsMeter ? 6.0f : 3.0f;
952 LayoutDeviceRect rect(aRect);
953 const LayoutDeviceCoord thickness =
954 (aIsMeter ? kMeterHeight : kProgressbarHeight) * aDpiRatio;
956 const bool isHorizontal = !nsNativeTheme::IsVerticalProgress(aFrame);
957 if (isHorizontal) {
958 // Center it vertically.
959 rect.y += (rect.height - thickness) / 2;
960 rect.height = thickness;
961 } else {
962 // Center it horizontally.
963 rect.x += (rect.width - thickness) / 2;
964 rect.width = thickness;
968 // Paint the track, unclipped.
969 auto [backgroundColor, borderColor] = ComputeProgressTrackColors(aColors);
970 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, rect,
971 backgroundColor, borderColor,
972 borderWidth, radius, aDpiRatio);
975 // Now paint the chunk, clipped as needed.
976 LayoutDeviceRect clipRect = rect;
977 if (aState.HasState(ElementState::INDETERMINATE)) {
978 // For indeterminate progress, we paint an animated chunk of 1/3 of the
979 // progress size.
981 // Animation speed and math borrowed from GTK.
982 const LayoutDeviceCoord size = isHorizontal ? rect.width : rect.height;
983 const LayoutDeviceCoord barSize = size * 0.3333f;
984 const LayoutDeviceCoord travel = 2.0f * (size - barSize);
986 // Period equals to travel / pixelsPerMillisecond where pixelsPerMillisecond
987 // equals progressSize / 1000.0. This is equivalent to 1600.
988 const unsigned kPeriod = 1600;
990 const int t = PR_IntervalToMilliseconds(PR_IntervalNow()) % kPeriod;
991 const LayoutDeviceCoord dx = travel * float(t) / float(kPeriod);
992 if (isHorizontal) {
993 rect.width = barSize;
994 rect.x += (dx < travel * .5f) ? dx : travel - dx;
995 } else {
996 rect.height = barSize;
997 rect.y += (dx < travel * .5f) ? dx : travel - dx;
999 clipRect = rect;
1000 // Queue the next frame if needed.
1001 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
1002 NS_WARNING("Couldn't refresh indeterminate <progress>");
1004 } else {
1005 // This is the progress chunk, clip it to the right amount.
1006 double position = [&] {
1007 if (aIsMeter) {
1008 auto* meter = dom::HTMLMeterElement::FromNode(aFrame->GetContent());
1009 if (!meter) {
1010 return 0.0;
1012 return meter->Position();
1014 auto* progress = dom::HTMLProgressElement::FromNode(aFrame->GetContent());
1015 if (!progress) {
1016 return 0.0;
1018 return progress->Position();
1019 }();
1020 if (isHorizontal) {
1021 double clipWidth = rect.width * position;
1022 clipRect.width = clipWidth;
1023 if (IsFrameRTL(aFrame)) {
1024 clipRect.x += rect.width - clipWidth;
1026 } else {
1027 double clipHeight = rect.height * position;
1028 clipRect.height = clipHeight;
1029 clipRect.y += rect.height - clipHeight;
1033 auto [backgroundColor, borderColor] =
1034 aIsMeter ? ComputeMeterchunkColors(aState, aColors)
1035 : ComputeProgressColors(aColors);
1036 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, clipRect,
1037 backgroundColor, borderColor,
1038 borderWidth, radius, aDpiRatio);
1041 template <typename PaintBackendData>
1042 void Theme::PaintButton(nsIFrame* aFrame, PaintBackendData& aPaintData,
1043 const LayoutDeviceRect& aRect,
1044 const ElementState& aState, const Colors& aColors,
1045 DPIRatio aDpiRatio) {
1046 const CSSCoord radius = 4.0f;
1047 auto [backgroundColor, borderColor] =
1048 ComputeButtonColors(aState, aColors, aFrame);
1050 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
1051 borderColor, kButtonBorderWidth,
1052 radius, aDpiRatio);
1054 if (aState.HasState(ElementState::FOCUSRING)) {
1055 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
1056 radius + kButtonBorderWidth, -kButtonBorderWidth);
1060 NS_IMETHODIMP
1061 Theme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1062 StyleAppearance aAppearance, const nsRect& aRect,
1063 const nsRect& /* aDirtyRect */,
1064 DrawOverflow aDrawOverflow) {
1065 if (!DoDrawWidgetBackground(*aContext->GetDrawTarget(), aFrame, aAppearance,
1066 aRect, aDrawOverflow)) {
1067 return NS_ERROR_NOT_IMPLEMENTED;
1069 return NS_OK;
1072 bool Theme::CreateWebRenderCommandsForWidget(
1073 mozilla::wr::DisplayListBuilder& aBuilder,
1074 mozilla::wr::IpcResourceUpdateQueue& aResources,
1075 const mozilla::layers::StackingContextHelper& aSc,
1076 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
1077 StyleAppearance aAppearance, const nsRect& aRect) {
1078 if (!StaticPrefs::widget_non_native_theme_webrender()) {
1079 return false;
1081 WebRenderBackendData data{aBuilder, aResources, aSc, aManager};
1082 return DoDrawWidgetBackground(data, aFrame, aAppearance, aRect,
1083 DrawOverflow::Yes);
1086 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1087 nscoord aTwipsPerPixel, DrawTarget& aDt) {
1088 return LayoutDeviceRect::FromUnknownRect(
1089 NSRectToSnappedRect(aRect, aTwipsPerPixel, aDt));
1092 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1093 nscoord aTwipsPerPixel,
1094 WebRenderBackendData& aDt) {
1095 // TODO: Do we need to do any more snapping here?
1096 return LayoutDeviceRect::FromAppUnits(aRect, aTwipsPerPixel);
1099 static ScrollbarDrawing::ScrollbarKind ComputeScrollbarKind(
1100 nsIFrame* aFrame, bool aIsHorizontal) {
1101 if (aIsHorizontal) {
1102 return ScrollbarDrawing::ScrollbarKind::Horizontal;
1104 nsIFrame* scrollbar = ScrollbarDrawing::GetParentScrollbarFrame(aFrame);
1105 if (NS_WARN_IF(!scrollbar)) {
1106 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1108 MOZ_ASSERT(scrollbar->IsScrollbarFrame());
1109 nsIScrollbarMediator* sm =
1110 static_cast<nsScrollbarFrame*>(scrollbar)->GetScrollbarMediator();
1111 if (NS_WARN_IF(!sm)) {
1112 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1114 return sm->IsScrollbarOnRight()
1115 ? ScrollbarDrawing::ScrollbarKind::VerticalRight
1116 : ScrollbarDrawing::ScrollbarKind::VerticalLeft;
1119 template <typename PaintBackendData>
1120 bool Theme::DoDrawWidgetBackground(PaintBackendData& aPaintData,
1121 nsIFrame* aFrame,
1122 StyleAppearance aAppearance,
1123 const nsRect& aRect,
1124 DrawOverflow aDrawOverflow) {
1125 static_assert(std::is_same_v<PaintBackendData, DrawTarget> ||
1126 std::is_same_v<PaintBackendData, WebRenderBackendData>);
1128 const nsPresContext* pc = aFrame->PresContext();
1129 const nscoord twipsPerPixel = pc->AppUnitsPerDevPixel();
1130 const auto devPxRect = ToSnappedRect(aRect, twipsPerPixel, aPaintData);
1132 const DocumentState docState = pc->Document()->GetDocumentState();
1133 ElementState elementState = GetContentState(aFrame, aAppearance);
1134 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
1135 bool isHTML = IsHTMLContent(aFrame);
1136 nsIFrame* parentFrame = aFrame->GetParent();
1137 bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
1138 // HTML select and XUL menulist dropdown buttons get state from the
1139 // parent.
1140 if (isHTML || isMenulist) {
1141 aFrame = parentFrame;
1142 elementState = GetContentState(parentFrame, aAppearance);
1146 // Paint the outline iff we're asked to draw overflow and we have
1147 // outline-style: auto.
1148 if (aDrawOverflow == DrawOverflow::Yes &&
1149 aFrame->StyleOutline()->mOutlineStyle.IsAuto()) {
1150 elementState |= ElementState::FOCUSRING;
1151 } else {
1152 elementState &= ~ElementState::FOCUSRING;
1155 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1156 // overflow devPxRect.
1157 Maybe<AutoClipRect> maybeClipRect;
1158 if constexpr (std::is_same_v<PaintBackendData, DrawTarget>) {
1159 if (aAppearance != StyleAppearance::FocusOutline &&
1160 aAppearance != StyleAppearance::Range &&
1161 !elementState.HasState(ElementState::FOCUSRING)) {
1162 maybeClipRect.emplace(aPaintData, devPxRect);
1166 const Colors colors(aFrame, aAppearance);
1167 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1169 switch (aAppearance) {
1170 case StyleAppearance::Radio: {
1171 auto rect = CheckBoxRadioRect(devPxRect);
1172 PaintRadioControl(aPaintData, rect, elementState, colors, dpiRatio);
1173 break;
1175 case StyleAppearance::Checkbox: {
1176 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1177 // TODO: Need to figure out how to best draw this using WR.
1178 return false;
1179 } else {
1180 auto rect = CheckBoxRadioRect(devPxRect);
1181 PaintCheckboxControl(aPaintData, rect, elementState, colors, dpiRatio);
1183 break;
1185 case StyleAppearance::Textarea:
1186 case StyleAppearance::Textfield:
1187 case StyleAppearance::NumberInput:
1188 PaintTextField(aPaintData, devPxRect, elementState, colors, dpiRatio);
1189 break;
1190 case StyleAppearance::Listbox:
1191 PaintListbox(aPaintData, devPxRect, elementState, colors, dpiRatio);
1192 break;
1193 case StyleAppearance::MenulistButton:
1194 case StyleAppearance::Menulist:
1195 PaintMenulist(aPaintData, devPxRect, elementState, colors, dpiRatio);
1196 break;
1197 case StyleAppearance::MozMenulistArrowButton:
1198 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1199 // TODO: Need to figure out how to best draw this using WR.
1200 return false;
1201 } else {
1202 PaintMenulistArrowButton(aFrame, aPaintData, devPxRect, elementState);
1204 break;
1205 case StyleAppearance::Tooltip: {
1206 const CSSCoord strokeWidth(1.0f);
1207 const CSSCoord strokeRadius(2.0f);
1208 ThemeDrawing::PaintRoundedRectWithRadius(
1209 aPaintData, devPxRect,
1210 colors.System(StyleSystemColor::Infobackground),
1211 colors.System(StyleSystemColor::Infotext), strokeWidth, strokeRadius,
1212 dpiRatio);
1213 break;
1215 case StyleAppearance::Menuitem: {
1216 ThemeDrawing::FillRect(aPaintData, devPxRect, [&] {
1217 if (CheckBooleanAttr(aFrame, nsGkAtoms::menuactive)) {
1218 if (elementState.HasState(ElementState::DISABLED)) {
1219 return colors.System(StyleSystemColor::MozMenuhoverdisabled);
1221 return colors.System(StyleSystemColor::MozMenuhover);
1223 return sTransparent;
1224 }());
1225 break;
1227 case StyleAppearance::SpinnerUpbutton:
1228 case StyleAppearance::SpinnerDownbutton:
1229 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1230 // TODO: Need to figure out how to best draw this using WR.
1231 return false;
1232 } else {
1233 PaintSpinnerButton(aFrame, aPaintData, devPxRect, elementState,
1234 aAppearance, colors, dpiRatio);
1236 break;
1237 case StyleAppearance::Range:
1238 PaintRange(aFrame, aPaintData, devPxRect, elementState, colors, dpiRatio,
1239 IsRangeHorizontal(aFrame));
1240 break;
1241 case StyleAppearance::RangeThumb:
1242 // Painted as part of StyleAppearance::Range.
1243 break;
1244 case StyleAppearance::ProgressBar:
1245 PaintProgress(aFrame, aPaintData, devPxRect, elementState, colors,
1246 dpiRatio,
1247 /* aIsMeter = */ false);
1248 break;
1249 case StyleAppearance::Progresschunk:
1250 /* Painted as part of the progress bar */
1251 break;
1252 case StyleAppearance::Meter:
1253 PaintProgress(aFrame, aPaintData, devPxRect, elementState, colors,
1254 dpiRatio,
1255 /* aIsMeter = */ true);
1256 break;
1257 case StyleAppearance::Meterchunk:
1258 /* Painted as part of the meter bar */
1259 break;
1260 case StyleAppearance::ScrollbarthumbHorizontal:
1261 case StyleAppearance::ScrollbarthumbVertical: {
1262 bool isHorizontal =
1263 aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
1264 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1265 return GetScrollbarDrawing().PaintScrollbarThumb(
1266 aPaintData, devPxRect, kind, aFrame,
1267 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1268 colors, dpiRatio);
1270 case StyleAppearance::ScrollbartrackHorizontal:
1271 case StyleAppearance::ScrollbartrackVertical: {
1272 bool isHorizontal =
1273 aAppearance == StyleAppearance::ScrollbartrackHorizontal;
1274 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1275 return GetScrollbarDrawing().PaintScrollbarTrack(
1276 aPaintData, devPxRect, kind, aFrame,
1277 *nsLayoutUtils::StyleForScrollbar(aFrame), docState, colors,
1278 dpiRatio);
1280 case StyleAppearance::ScrollbarHorizontal:
1281 case StyleAppearance::ScrollbarVertical: {
1282 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
1283 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1284 return GetScrollbarDrawing().PaintScrollbar(
1285 aPaintData, devPxRect, kind, aFrame,
1286 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1287 colors, dpiRatio);
1289 case StyleAppearance::Scrollcorner: {
1290 auto kind = ComputeScrollbarKind(aFrame, false);
1291 return GetScrollbarDrawing().PaintScrollCorner(
1292 aPaintData, devPxRect, kind, aFrame,
1293 *nsLayoutUtils::StyleForScrollbar(aFrame), docState, colors,
1294 dpiRatio);
1296 case StyleAppearance::ScrollbarbuttonUp:
1297 case StyleAppearance::ScrollbarbuttonDown:
1298 case StyleAppearance::ScrollbarbuttonLeft:
1299 case StyleAppearance::ScrollbarbuttonRight: {
1300 // For scrollbar-width:thin, we don't display the buttons.
1301 if (!ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) {
1302 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1303 // TODO: Need to figure out how to best draw this using WR.
1304 return false;
1305 } else {
1306 bool isHorizontal =
1307 aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
1308 aAppearance == StyleAppearance::ScrollbarbuttonRight;
1309 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1310 GetScrollbarDrawing().PaintScrollbarButton(
1311 aPaintData, aAppearance, devPxRect, kind, aFrame,
1312 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1313 colors, dpiRatio);
1316 break;
1318 case StyleAppearance::Button:
1319 PaintButton(aFrame, aPaintData, devPxRect, elementState, colors,
1320 dpiRatio);
1321 break;
1322 case StyleAppearance::FocusOutline:
1323 PaintAutoStyleOutline(aFrame, aPaintData, devPxRect, colors, dpiRatio);
1324 break;
1325 default:
1326 // Various appearance values are used for XUL elements. Normally these
1327 // will not be available in content documents (and thus in the content
1328 // processes where the native basic theme can be used), but tests are
1329 // run with the remote XUL pref enabled and so we can get in here. So
1330 // we just return an error rather than assert.
1331 return false;
1334 return true;
1337 template <typename PaintBackendData>
1338 void Theme::PaintAutoStyleOutline(nsIFrame* aFrame,
1339 PaintBackendData& aPaintData,
1340 const LayoutDeviceRect& aRect,
1341 const Colors& aColors, DPIRatio aDpiRatio) {
1342 const auto& accentColor = aColors.Accent();
1343 const bool solid = StaticPrefs::widget_non_native_theme_solid_outline_style();
1344 LayoutDeviceCoord strokeWidth(ThemeDrawing::SnapBorderWidth(2.0f, aDpiRatio));
1346 LayoutDeviceRect rect(aRect);
1347 rect.Inflate(strokeWidth);
1349 const nscoord a2d = aFrame->PresContext()->AppUnitsPerDevPixel();
1350 nscoord cssOffset = aFrame->StyleOutline()->mOutlineOffset.ToAppUnits();
1351 nscoord cssRadii[8] = {0};
1352 if (!aFrame->GetBorderRadii(cssRadii)) {
1353 const auto twoPixels = 2 * AppUnitsPerCSSPixel();
1354 const nscoord radius =
1355 cssOffset >= 0 ? twoPixels : std::max(twoPixels + cssOffset, 0);
1356 cssOffset = -twoPixels;
1357 for (auto& r : cssRadii) {
1358 r = radius;
1362 auto offset = LayoutDevicePixel::FromAppUnits(cssOffset, a2d);
1363 RectCornerRadii innerRadii;
1364 nsCSSRendering::ComputePixelRadii(cssRadii, a2d, &innerRadii);
1366 // NOTE(emilio): This doesn't use PaintRoundedRectWithRadius because we need
1367 // to support arbitrary radii.
1368 auto DrawRect = [&](const sRGBColor& aColor) {
1369 RectCornerRadii outerRadii;
1370 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1371 const Float widths[4] = {strokeWidth + offset, strokeWidth + offset,
1372 strokeWidth + offset, strokeWidth + offset};
1373 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1374 const auto dest = wr::ToLayoutRect(rect);
1375 const auto side =
1376 wr::ToBorderSide(ToDeviceColor(aColor), StyleBorderStyle::Solid);
1377 const wr::BorderSide sides[4] = {side, side, side, side};
1378 const bool kBackfaceIsVisible = true;
1379 const auto wrWidths = wr::ToBorderWidths(strokeWidth, strokeWidth,
1380 strokeWidth, strokeWidth);
1381 const auto wrRadius = wr::ToBorderRadius(outerRadii);
1382 aPaintData.mBuilder.PushBorder(dest, dest, kBackfaceIsVisible, wrWidths,
1383 {sides, 4}, wrRadius);
1384 } else {
1385 const LayoutDeviceCoord halfWidth = strokeWidth * 0.5f;
1386 const Float widths[4] = {halfWidth + offset, halfWidth + offset,
1387 halfWidth + offset, halfWidth + offset};
1388 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1389 LayoutDeviceRect dest(rect);
1390 dest.Deflate(halfWidth);
1391 RefPtr<Path> path =
1392 MakePathForRoundedRect(aPaintData, dest.ToUnknownRect(), outerRadii);
1393 aPaintData.Stroke(path, ColorPattern(ToDeviceColor(aColor)),
1394 StrokeOptions(strokeWidth));
1398 DrawRect(accentColor.Get());
1400 if (solid) {
1401 return;
1404 offset += strokeWidth;
1406 strokeWidth =
1407 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(1.0f, aDpiRatio));
1408 rect.Inflate(strokeWidth);
1410 DrawRect(accentColor.GetForeground());
1413 LayoutDeviceIntMargin Theme::GetWidgetBorder(nsDeviceContext* aContext,
1414 nsIFrame* aFrame,
1415 StyleAppearance aAppearance) {
1416 switch (aAppearance) {
1417 case StyleAppearance::Textfield:
1418 case StyleAppearance::Textarea:
1419 case StyleAppearance::NumberInput:
1420 case StyleAppearance::Listbox:
1421 case StyleAppearance::Menulist:
1422 case StyleAppearance::MenulistButton:
1423 case StyleAppearance::Button:
1424 // Return the border size from the UA sheet, even though what we paint
1425 // doesn't actually match that. We know this is the UA sheet border
1426 // because we disable native theming when different border widths are
1427 // specified by authors, see Theme::IsWidgetStyled.
1429 // The Rounded() bit is technically redundant, but needed to appease the
1430 // type system, we should always end up with full device pixels due to
1431 // round_border_to_device_pixels at style time.
1432 return LayoutDeviceIntMargin::FromAppUnits(
1433 aFrame->StyleBorder()->GetComputedBorder(),
1434 aFrame->PresContext()->AppUnitsPerDevPixel())
1435 .Rounded();
1436 case StyleAppearance::Checkbox:
1437 case StyleAppearance::Radio: {
1438 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1439 LayoutDeviceIntCoord w =
1440 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth, dpiRatio);
1441 return LayoutDeviceIntMargin(w, w, w, w);
1443 default:
1444 return LayoutDeviceIntMargin();
1448 bool Theme::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
1449 StyleAppearance aAppearance,
1450 LayoutDeviceIntMargin* aResult) {
1451 switch (aAppearance) {
1452 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1453 // and have a meaningful baseline, so they can't have
1454 // author-specified padding.
1455 case StyleAppearance::Radio:
1456 case StyleAppearance::Checkbox:
1457 aResult->SizeTo(0, 0, 0, 0);
1458 return true;
1459 default:
1460 break;
1462 return false;
1465 bool Theme::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
1466 StyleAppearance aAppearance,
1467 nsRect* aOverflowRect) {
1468 CSSIntMargin overflow;
1469 switch (aAppearance) {
1470 case StyleAppearance::FocusOutline: {
1471 // 2px * one segment, or 2px + 1px
1472 const auto width =
1473 StaticPrefs::widget_non_native_theme_solid_outline_style() ? 2 : 3;
1474 overflow.SizeTo(width, width, width, width);
1475 break;
1477 case StyleAppearance::Radio:
1478 case StyleAppearance::Checkbox:
1479 case StyleAppearance::Range:
1480 // 2px for each outline segment, plus 1px separation, plus we paint with a
1481 // 1px extra offset, so 6px.
1482 overflow.SizeTo(6, 6, 6, 6);
1483 break;
1484 case StyleAppearance::Textarea:
1485 case StyleAppearance::Textfield:
1486 case StyleAppearance::NumberInput:
1487 case StyleAppearance::Listbox:
1488 case StyleAppearance::MenulistButton:
1489 case StyleAppearance::Menulist:
1490 case StyleAppearance::Button:
1491 // 2px for each segment, plus 1px separation, but we paint 1px inside
1492 // the border area so 4px overflow.
1493 overflow.SizeTo(4, 4, 4, 4);
1494 break;
1495 default:
1496 return false;
1499 // TODO: This should convert from device pixels to app units, not from CSS
1500 // pixels. And it should take the dpi ratio into account.
1501 // Using CSS pixels can cause the overflow to be too small if the page is
1502 // zoomed out.
1503 aOverflowRect->Inflate(CSSPixel::ToAppUnits(overflow));
1504 return true;
1507 auto Theme::GetScrollbarSizes(nsPresContext* aPresContext,
1508 StyleScrollbarWidth aWidth, Overlay aOverlay)
1509 -> ScrollbarSizes {
1510 return GetScrollbarDrawing().GetScrollbarSizes(aPresContext, aWidth,
1511 aOverlay);
1514 nscoord Theme::GetCheckboxRadioPrefSize() {
1515 return CSSPixel::ToAppUnits(kCheckboxRadioContentBoxSize);
1518 /* static */
1519 UniquePtr<ScrollbarDrawing> Theme::ScrollbarStyle() {
1520 switch (StaticPrefs::widget_non_native_theme_scrollbar_style()) {
1521 case 1:
1522 return MakeUnique<ScrollbarDrawingCocoa>();
1523 case 2:
1524 return MakeUnique<ScrollbarDrawingGTK>();
1525 case 3:
1526 return MakeUnique<ScrollbarDrawingAndroid>();
1527 case 4:
1528 return MakeUnique<ScrollbarDrawingWin>();
1529 case 5:
1530 return MakeUnique<ScrollbarDrawingWin11>();
1531 default:
1532 break;
1534 // Default to native scrollbar style for each platform.
1535 #ifdef XP_WIN
1536 if (IsWin11OrLater()) {
1537 return MakeUnique<ScrollbarDrawingWin11>();
1539 return MakeUnique<ScrollbarDrawingWin>();
1540 #elif MOZ_WIDGET_COCOA
1541 return MakeUnique<ScrollbarDrawingCocoa>();
1542 #elif MOZ_WIDGET_GTK
1543 return MakeUnique<ScrollbarDrawingGTK>();
1544 #elif ANDROID
1545 return MakeUnique<ScrollbarDrawingAndroid>();
1546 #else
1547 # error "Unknown platform, need scrollbar implementation."
1548 #endif
1551 LayoutDeviceIntSize Theme::GetMinimumWidgetSize(nsPresContext* aPresContext,
1552 nsIFrame* aFrame,
1553 StyleAppearance aAppearance) {
1554 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1556 if (IsWidgetScrollbarPart(aAppearance)) {
1557 return GetScrollbarDrawing().GetMinimumWidgetSize(aPresContext, aAppearance,
1558 aFrame);
1561 LayoutDeviceIntSize result;
1562 switch (aAppearance) {
1563 case StyleAppearance::RangeThumb:
1564 result.SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
1565 (kMinimumRangeThumbSize * dpiRatio).Rounded());
1566 break;
1567 case StyleAppearance::MozMenulistArrowButton:
1568 result.width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
1569 break;
1570 case StyleAppearance::SpinnerUpbutton:
1571 case StyleAppearance::SpinnerDownbutton:
1572 result.width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
1573 result.height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
1574 break;
1575 default:
1576 break;
1578 return result;
1581 nsITheme::Transparency Theme::GetWidgetTransparency(
1582 nsIFrame* aFrame, StyleAppearance aAppearance) {
1583 if (auto scrollbar = GetScrollbarDrawing().GetScrollbarPartTransparency(
1584 aFrame, aAppearance)) {
1585 return *scrollbar;
1587 if (aAppearance == StyleAppearance::Tooltip) {
1588 // We draw a rounded rect, so we need transparency.
1589 return eTransparent;
1591 return eUnknownTransparency;
1594 NS_IMETHODIMP
1595 Theme::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
1596 nsAtom* aAttribute, bool* aShouldRepaint,
1597 const nsAttrValue* aOldValue) {
1598 if (!aAttribute) {
1599 // Hover/focus/active changed. Always repaint.
1600 *aShouldRepaint = true;
1601 } else {
1602 // Check the attribute to see if it's relevant.
1603 // disabled, checked, dlgtype, default, etc.
1604 *aShouldRepaint = false;
1605 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
1606 aAttribute == nsGkAtoms::selected ||
1607 aAttribute == nsGkAtoms::visuallyselected ||
1608 aAttribute == nsGkAtoms::menuactive ||
1609 aAttribute == nsGkAtoms::sortDirection ||
1610 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
1611 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover) {
1612 *aShouldRepaint = true;
1616 return NS_OK;
1619 NS_IMETHODIMP
1620 Theme::ThemeChanged() { return NS_OK; }
1622 bool Theme::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
1623 return IsWidgetScrollbarPart(aAppearance);
1626 nsITheme::ThemeGeometryType Theme::ThemeGeometryTypeForWidget(
1627 nsIFrame* aFrame, StyleAppearance aAppearance) {
1628 return eThemeGeometryTypeUnknown;
1631 bool Theme::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
1632 StyleAppearance aAppearance) {
1633 switch (aAppearance) {
1634 case StyleAppearance::Radio:
1635 case StyleAppearance::Checkbox:
1636 case StyleAppearance::FocusOutline:
1637 case StyleAppearance::Textarea:
1638 case StyleAppearance::Textfield:
1639 case StyleAppearance::Range:
1640 case StyleAppearance::RangeThumb:
1641 case StyleAppearance::ProgressBar:
1642 case StyleAppearance::Progresschunk:
1643 case StyleAppearance::Meter:
1644 case StyleAppearance::Meterchunk:
1645 case StyleAppearance::ScrollbarbuttonUp:
1646 case StyleAppearance::ScrollbarbuttonDown:
1647 case StyleAppearance::ScrollbarbuttonLeft:
1648 case StyleAppearance::ScrollbarbuttonRight:
1649 case StyleAppearance::ScrollbarthumbHorizontal:
1650 case StyleAppearance::ScrollbarthumbVertical:
1651 case StyleAppearance::ScrollbartrackHorizontal:
1652 case StyleAppearance::ScrollbartrackVertical:
1653 case StyleAppearance::ScrollbarHorizontal:
1654 case StyleAppearance::ScrollbarVertical:
1655 case StyleAppearance::Scrollcorner:
1656 case StyleAppearance::Button:
1657 case StyleAppearance::Listbox:
1658 case StyleAppearance::Menulist:
1659 case StyleAppearance::MenulistButton:
1660 case StyleAppearance::NumberInput:
1661 case StyleAppearance::MozMenulistArrowButton:
1662 case StyleAppearance::SpinnerUpbutton:
1663 case StyleAppearance::SpinnerDownbutton:
1664 case StyleAppearance::Menuitem:
1665 case StyleAppearance::Tooltip:
1666 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
1667 default:
1668 return false;
1672 bool Theme::WidgetIsContainer(StyleAppearance aAppearance) {
1673 switch (aAppearance) {
1674 case StyleAppearance::MozMenulistArrowButton:
1675 case StyleAppearance::Radio:
1676 case StyleAppearance::Checkbox:
1677 return false;
1678 default:
1679 return true;
1683 bool Theme::ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance) {
1684 return true;
1687 bool Theme::ThemeNeedsComboboxDropmarker() { return true; }
1689 bool Theme::ThemeSupportsScrollbarButtons() {
1690 return GetScrollbarDrawing().ShouldDrawScrollbarButtons();
1693 } // namespace mozilla::widget