Bug 1768393 - Use the snap position on the axis only if the scroll snap strictness...
[gecko.git] / widget / Theme.cpp
blob00d75897d7d72fb82f877ea0b3e44e9ec7ee28d7
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 "nsIScrollableFrame.h"
27 #include "nsIScrollbarMediator.h"
28 #include "nsDeviceContext.h"
29 #include "nsLayoutUtils.h"
30 #include "nsRangeFrame.h"
31 #include "PathHelpers.h"
32 #include "ScrollbarDrawingAndroid.h"
33 #include "ScrollbarDrawingCocoa.h"
34 #include "ScrollbarDrawingGTK.h"
35 #include "ScrollbarDrawingWin.h"
36 #include "ScrollbarDrawingWin11.h"
38 #ifdef XP_WIN
39 # include "mozilla/WindowsVersion.h"
40 #endif
42 using namespace mozilla;
43 using namespace mozilla::dom;
44 using namespace mozilla::gfx;
45 using namespace mozilla::widget;
47 namespace {
49 static constexpr gfx::sRGBColor sColorGrey10(
50 gfx::sRGBColor::UnusualFromARGB(0xffe9e9ed));
51 static constexpr gfx::sRGBColor sColorGrey10Alpha50(
52 gfx::sRGBColor::UnusualFromARGB(0x7fe9e9ed));
53 static constexpr gfx::sRGBColor sColorGrey20(
54 gfx::sRGBColor::UnusualFromARGB(0xffd0d0d7));
55 static constexpr gfx::sRGBColor sColorGrey30(
56 gfx::sRGBColor::UnusualFromARGB(0xffb1b1b9));
57 static constexpr gfx::sRGBColor sColorGrey40(
58 gfx::sRGBColor::UnusualFromARGB(0xff8f8f9d));
59 static constexpr gfx::sRGBColor sColorGrey40Alpha50(
60 gfx::sRGBColor::UnusualFromARGB(0x7f8f8f9d));
61 static constexpr gfx::sRGBColor sColorGrey50(
62 gfx::sRGBColor::UnusualFromARGB(0xff676774));
63 static constexpr gfx::sRGBColor sColorGrey60(
64 gfx::sRGBColor::UnusualFromARGB(0xff484851));
66 static constexpr gfx::sRGBColor sColorMeterGreen10(
67 gfx::sRGBColor::UnusualFromARGB(0xff00ab60));
68 static constexpr gfx::sRGBColor sColorMeterGreen20(
69 gfx::sRGBColor::UnusualFromARGB(0xff056139));
70 static constexpr gfx::sRGBColor sColorMeterYellow10(
71 gfx::sRGBColor::UnusualFromARGB(0xffffbd4f));
72 static constexpr gfx::sRGBColor sColorMeterYellow20(
73 gfx::sRGBColor::UnusualFromARGB(0xffd2811e));
74 static constexpr gfx::sRGBColor sColorMeterRed10(
75 gfx::sRGBColor::UnusualFromARGB(0xffe22850));
76 static constexpr gfx::sRGBColor sColorMeterRed20(
77 gfx::sRGBColor::UnusualFromARGB(0xff810220));
79 static const CSSCoord kMinimumRangeThumbSize = 20.0f;
80 static const CSSCoord kMinimumDropdownArrowButtonWidth = 18.0f;
81 static const CSSCoord kMinimumSpinnerButtonWidth = 18.0f;
82 static const CSSCoord kMinimumSpinnerButtonHeight = 9.0f;
83 static const CSSCoord kButtonBorderWidth = 1.0f;
84 static const CSSCoord kMenulistBorderWidth = 1.0f;
85 static const CSSCoord kTextFieldBorderWidth = 1.0f;
86 static const CSSCoord kRangeHeight = 6.0f;
87 static const CSSCoord kProgressbarHeight = 6.0f;
88 static const CSSCoord kMeterHeight = 12.0f;
90 // nsCheckboxRadioFrame takes the bottom of the content box as the baseline.
91 // This border-width makes its baseline 2px under the bottom, which is nice.
92 static constexpr CSSCoord kCheckboxRadioBorderWidth = 2.0f;
94 static constexpr sRGBColor sTransparent = sRGBColor::White(0.0);
96 // This pushes and pops a clip rect to the draw target.
98 // This is done to reduce fuzz in places where we may have antialiasing,
99 // because skia is not clip-invariant: given different clips, it does not
100 // guarantee the same result, even if the painted content doesn't intersect
101 // the clips.
103 // This is a bit sad, overall, but...
104 struct MOZ_RAII AutoClipRect {
105 AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
106 mDt.PushClipRect(aRect.ToUnknownRect());
109 ~AutoClipRect() { mDt.PopClip(); }
111 private:
112 DrawTarget& mDt;
115 static StaticRefPtr<Theme> gNativeInstance;
116 static StaticRefPtr<Theme> gNonNativeInstance;
117 static StaticRefPtr<Theme> gRDMInstance;
119 } // namespace
121 #ifdef ANDROID
122 already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
123 // Android doesn't have a native theme.
124 return do_AddRef(new Theme(Theme::ScrollbarStyle()));
126 #endif
128 already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
129 if (MOZ_UNLIKELY(!gNonNativeInstance)) {
130 UniquePtr<ScrollbarDrawing> scrollbarDrawing = Theme::ScrollbarStyle();
131 #ifdef MOZ_WIDGET_COCOA
132 gNonNativeInstance = new ThemeCocoa(std::move(scrollbarDrawing));
133 #else
134 gNonNativeInstance = new Theme(std::move(scrollbarDrawing));
135 #endif
136 ClearOnShutdown(&gNonNativeInstance);
138 return do_AddRef(gNonNativeInstance);
141 already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
142 if (MOZ_UNLIKELY(!gNativeInstance)) {
143 gNativeInstance = do_CreateNativeThemeDoNotUseDirectly();
144 ClearOnShutdown(&gNativeInstance);
146 return do_AddRef(gNativeInstance);
149 already_AddRefed<nsITheme> do_GetRDMThemeDoNotUseDirectly() {
150 if (MOZ_UNLIKELY(!gRDMInstance)) {
151 gRDMInstance = new Theme(MakeUnique<ScrollbarDrawingAndroid>());
152 ClearOnShutdown(&gRDMInstance);
154 return do_AddRef(gRDMInstance);
157 namespace mozilla::widget {
159 NS_IMPL_ISUPPORTS_INHERITED(Theme, nsNativeTheme, nsITheme)
161 static constexpr nsLiteralCString kPrefs[] = {
162 "widget.non-native-theme.use-theme-accent"_ns,
163 "widget.non-native-theme.win.scrollbar.use-system-size"_ns,
164 "widget.non-native-theme.scrollbar.size.override"_ns,
165 "widget.non-native-theme.scrollbar.style"_ns,
168 void Theme::Init() {
169 for (const auto& pref : kPrefs) {
170 Preferences::RegisterCallback(PrefChangedCallback, pref);
172 LookAndFeelChanged();
175 void Theme::Shutdown() {
176 for (const auto& pref : kPrefs) {
177 Preferences::UnregisterCallback(PrefChangedCallback, pref);
181 /* static */
182 void Theme::LookAndFeelChanged() {
183 ThemeColors::RecomputeAccentColors();
184 if (gNonNativeInstance) {
185 gNonNativeInstance->SetScrollbarDrawing(ScrollbarStyle());
187 if (gNativeInstance) {
188 gNativeInstance->SetScrollbarDrawing(ScrollbarStyle());
192 auto Theme::GetDPIRatio(nsPresContext* aPc, StyleAppearance aAppearance)
193 -> DPIRatio {
194 // Widgets react to zoom, except scrollbars.
195 if (IsWidgetScrollbarPart(aAppearance)) {
196 return GetScrollbarDrawing().GetDPIRatioForScrollbarPart(aPc);
198 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc->AppUnitsPerDevPixel());
201 auto Theme::GetDPIRatio(nsIFrame* aFrame, StyleAppearance aAppearance)
202 -> DPIRatio {
203 return GetDPIRatio(aFrame->PresContext(), aAppearance);
206 // Checkbox and radio need to preserve aspect-ratio for compat. We also snap the
207 // size to exact device pixels to avoid snapping disorting the circles.
208 static LayoutDeviceRect CheckBoxRadioRect(const LayoutDeviceRect& aRect) {
209 // Place a square rect in the center of aRect.
210 auto size = std::trunc(std::min(aRect.width, aRect.height));
211 auto position = aRect.Center() - LayoutDevicePoint(size * 0.5, size * 0.5);
212 return LayoutDeviceRect(position, LayoutDeviceSize(size, size));
215 std::tuple<sRGBColor, sRGBColor, sRGBColor> Theme::ComputeCheckboxColors(
216 const ElementState& aState, StyleAppearance aAppearance,
217 const Colors& aColors) {
218 MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
219 aAppearance == StyleAppearance::Radio);
221 bool isDisabled = aState.HasState(ElementState::DISABLED);
222 bool isChecked = aState.HasState(ElementState::CHECKED);
223 bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
224 aState.HasState(ElementState::INDETERMINATE);
226 if (isChecked || isIndeterminate) {
227 if (isDisabled) {
228 auto bg = ComputeBorderColor(aState, aColors, OutlineCoversBorder::No);
229 auto fg = aColors.HighContrast()
230 ? aColors.System(StyleSystemColor::Graytext)
231 : sRGBColor::White(.8f);
232 return std::make_tuple(bg, bg, fg);
235 if (aColors.HighContrast()) {
236 auto bg = aColors.System(StyleSystemColor::Selecteditem);
237 auto fg = aColors.System(StyleSystemColor::Selecteditemtext);
238 return std::make_tuple(bg, bg, fg);
241 bool isActive =
242 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
243 bool isHovered = aState.HasState(ElementState::HOVER);
244 const auto& bg = isActive ? aColors.Accent().GetDarker()
245 : isHovered ? aColors.Accent().GetDark()
246 : aColors.Accent().Get();
247 const auto& fg = aColors.Accent().GetForeground();
248 return std::make_tuple(bg, bg, fg);
251 auto [bg, border] =
252 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::No);
253 // We don't paint a checkmark in this case so any color would do.
254 return std::make_tuple(bg, border, sTransparent);
257 sRGBColor Theme::ComputeBorderColor(const ElementState& aState,
258 const Colors& aColors,
259 OutlineCoversBorder aOutlineCoversBorder) {
260 bool isDisabled = aState.HasState(ElementState::DISABLED);
261 if (aColors.HighContrast()) {
262 return aColors.System(isDisabled ? StyleSystemColor::Graytext
263 : StyleSystemColor::Buttontext);
265 bool isActive =
266 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
267 bool isHovered = aState.HasState(ElementState::HOVER);
268 bool isFocused = aState.HasState(ElementState::FOCUSRING);
269 if (isDisabled) {
270 return sColorGrey40Alpha50;
272 if (isFocused && aOutlineCoversBorder == OutlineCoversBorder::Yes) {
273 // If we draw the outline over the border, prevent issues where the border
274 // shows underneath if it snaps in the wrong direction by using a
275 // transparent border. An alternative to this is ensuring that we snap the
276 // offset in PaintRoundedFocusRect the same was a we snap border widths, so
277 // that negative offsets are guaranteed to cover the border.
278 // But this looks harder to mess up.
279 return sTransparent;
281 bool dark = aColors.IsDark();
282 if (isActive) {
283 return dark ? sColorGrey20 : sColorGrey60;
285 if (isHovered) {
286 return dark ? sColorGrey30 : sColorGrey50;
288 return sColorGrey40;
291 std::pair<sRGBColor, sRGBColor> Theme::ComputeButtonColors(
292 const ElementState& aState, const Colors& aColors, nsIFrame* aFrame) {
293 bool isActive =
294 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
295 bool isDisabled = aState.HasState(ElementState::DISABLED);
296 bool isHovered = aState.HasState(ElementState::HOVER);
298 nscolor backgroundColor = [&] {
299 if (isDisabled) {
300 return aColors.SystemNs(StyleSystemColor::MozButtondisabledface);
302 if (isActive) {
303 return aColors.SystemNs(StyleSystemColor::MozButtonactiveface);
305 if (isHovered) {
306 return aColors.SystemNs(StyleSystemColor::MozButtonhoverface);
308 return aColors.SystemNs(StyleSystemColor::Buttonface);
309 }();
311 if (aState.HasState(ElementState::AUTOFILL)) {
312 backgroundColor = NS_ComposeColors(
313 backgroundColor,
314 aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
317 const sRGBColor borderColor =
318 ComputeBorderColor(aState, aColors, OutlineCoversBorder::Yes);
319 return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
322 std::pair<sRGBColor, sRGBColor> Theme::ComputeTextfieldColors(
323 const ElementState& aState, const Colors& aColors,
324 OutlineCoversBorder aOutlineCoversBorder) {
325 nscolor backgroundColor = [&] {
326 if (aState.HasState(ElementState::DISABLED)) {
327 return aColors.SystemNs(StyleSystemColor::MozDisabledfield);
329 return aColors.SystemNs(StyleSystemColor::Field);
330 }();
332 if (aState.HasState(ElementState::AUTOFILL)) {
333 backgroundColor = NS_ComposeColors(
334 backgroundColor,
335 aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
338 const sRGBColor borderColor =
339 ComputeBorderColor(aState, aColors, aOutlineCoversBorder);
340 return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
343 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeProgressColors(
344 const ElementState& aState, const Colors& aColors) {
345 if (aColors.HighContrast()) {
346 return aColors.SystemPair(StyleSystemColor::Selecteditem,
347 StyleSystemColor::Buttontext);
350 bool isActive =
351 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
352 bool isDisabled = aState.HasState(ElementState::DISABLED);
353 bool isHovered = aState.HasState(ElementState::HOVER);
355 if (isDisabled) {
356 return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
358 if (isActive || isHovered) {
359 return std::make_pair(aColors.Accent().GetDark(),
360 aColors.Accent().GetDarker());
362 return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
365 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeTrackColors(
366 const ElementState& aState, const Colors& aColors) {
367 if (aColors.HighContrast()) {
368 return aColors.SystemPair(StyleSystemColor::Window,
369 StyleSystemColor::Buttontext);
371 bool isActive =
372 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
373 bool isDisabled = aState.HasState(ElementState::DISABLED);
374 bool isHovered = aState.HasState(ElementState::HOVER);
376 if (isDisabled) {
377 return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
379 if (isActive || isHovered) {
380 return std::make_pair(sColorGrey20, sColorGrey50);
382 return std::make_pair(sColorGrey10, sColorGrey40);
385 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeThumbColors(
386 const ElementState& aState, const Colors& aColors) {
387 if (aColors.HighContrast()) {
388 return aColors.SystemPair(StyleSystemColor::Selecteditemtext,
389 StyleSystemColor::Selecteditem);
392 bool isActive =
393 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
394 bool isDisabled = aState.HasState(ElementState::DISABLED);
395 bool isHovered = aState.HasState(ElementState::HOVER);
397 const sRGBColor& backgroundColor = [&] {
398 if (isDisabled) {
399 return sColorGrey40;
401 if (isActive) {
402 return aColors.Accent().Get();
404 if (isHovered) {
405 return sColorGrey60;
407 return sColorGrey50;
408 }();
410 const sRGBColor borderColor = sRGBColor::OpaqueWhite();
411 return std::make_pair(backgroundColor, borderColor);
414 std::pair<sRGBColor, sRGBColor> Theme::ComputeProgressColors(
415 const Colors& aColors) {
416 if (aColors.HighContrast()) {
417 return aColors.SystemPair(StyleSystemColor::Selecteditem,
418 StyleSystemColor::Buttontext);
420 return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
423 std::pair<sRGBColor, sRGBColor> Theme::ComputeProgressTrackColors(
424 const Colors& aColors) {
425 if (aColors.HighContrast()) {
426 return aColors.SystemPair(StyleSystemColor::Buttonface,
427 StyleSystemColor::Buttontext);
429 return std::make_pair(sColorGrey10, sColorGrey40);
432 std::pair<sRGBColor, sRGBColor> Theme::ComputeMeterchunkColors(
433 const ElementState& aMeterState, const Colors& aColors) {
434 if (aColors.HighContrast()) {
435 return ComputeProgressColors(aColors);
437 sRGBColor borderColor = sColorMeterGreen20;
438 sRGBColor chunkColor = sColorMeterGreen10;
440 if (aMeterState.HasState(ElementState::SUB_OPTIMUM)) {
441 borderColor = sColorMeterYellow20;
442 chunkColor = sColorMeterYellow10;
443 } else if (aMeterState.HasState(ElementState::SUB_SUB_OPTIMUM)) {
444 borderColor = sColorMeterRed20;
445 chunkColor = sColorMeterRed10;
448 return std::make_pair(chunkColor, borderColor);
451 std::array<sRGBColor, 3> Theme::ComputeFocusRectColors(const Colors& aColors) {
452 if (aColors.HighContrast()) {
453 return {aColors.System(StyleSystemColor::Selecteditem),
454 aColors.System(StyleSystemColor::Buttontext),
455 aColors.System(StyleSystemColor::Window)};
457 const auto& accent = aColors.Accent();
458 const sRGBColor middle =
459 aColors.IsDark() ? sRGBColor::Black(.3f) : sRGBColor::White(.3f);
460 return {accent.Get(), middle, accent.GetLight()};
463 template <typename PaintBackendData>
464 void Theme::PaintRoundedFocusRect(PaintBackendData& aBackendData,
465 const LayoutDeviceRect& aRect,
466 const Colors& aColors, DPIRatio aDpiRatio,
467 CSSCoord aRadius, CSSCoord aOffset) {
468 // NOTE(emilio): If the widths or offsets here change, make sure to tweak
469 // the GetWidgetOverflow path for FocusOutline.
470 auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors(aColors);
472 LayoutDeviceRect focusRect(aRect);
474 // The focus rect is painted outside of the border area (aRect), see:
476 // data:text/html,<div style="border: 1px solid; outline: 2px solid
477 // red">Foobar</div>
479 // But some controls might provide a negative offset to cover the border, if
480 // necessary.
481 CSSCoord strokeWidth = 2.0f;
482 auto strokeWidthDevPx =
483 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
484 CSSCoord strokeRadius = aRadius;
485 focusRect.Inflate(aOffset * aDpiRatio + strokeWidthDevPx);
487 ThemeDrawing::PaintRoundedRectWithRadius(
488 aBackendData, focusRect, sTransparent, innerColor, strokeWidth,
489 strokeRadius, aDpiRatio);
491 strokeWidth = CSSCoord(1.0f);
492 strokeWidthDevPx =
493 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
494 strokeRadius += strokeWidth;
495 focusRect.Inflate(strokeWidthDevPx);
497 ThemeDrawing::PaintRoundedRectWithRadius(
498 aBackendData, focusRect, sTransparent, middleColor, strokeWidth,
499 strokeRadius, aDpiRatio);
501 strokeWidth = CSSCoord(2.0f);
502 strokeWidthDevPx =
503 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
504 strokeRadius += strokeWidth;
505 focusRect.Inflate(strokeWidthDevPx);
507 ThemeDrawing::PaintRoundedRectWithRadius(
508 aBackendData, focusRect, sTransparent, outerColor, strokeWidth,
509 strokeRadius, aDpiRatio);
512 void Theme::PaintCheckboxControl(DrawTarget& aDrawTarget,
513 const LayoutDeviceRect& aRect,
514 const ElementState& aState,
515 const Colors& aColors, DPIRatio aDpiRatio) {
516 auto [backgroundColor, borderColor, checkColor] =
517 ComputeCheckboxColors(aState, StyleAppearance::Checkbox, aColors);
519 const CSSCoord radius = 2.0f;
520 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
521 if (backgroundColor == borderColor) {
522 borderWidth = 0.0f;
524 ThemeDrawing::PaintRoundedRectWithRadius(aDrawTarget, aRect,
525 backgroundColor, borderColor,
526 borderWidth, radius, aDpiRatio);
529 if (aState.HasState(ElementState::INDETERMINATE)) {
530 PaintIndeterminateMark(aDrawTarget, aRect, checkColor);
531 } else if (aState.HasState(ElementState::CHECKED)) {
532 PaintCheckMark(aDrawTarget, aRect, checkColor);
535 if (aState.HasState(ElementState::FOCUSRING)) {
536 PaintRoundedFocusRect(aDrawTarget, aRect, aColors, aDpiRatio, 5.0f, 1.0f);
540 constexpr CSSCoord kCheckboxRadioContentBoxSize = 10.0f;
541 constexpr CSSCoord kCheckboxRadioBorderBoxSize =
542 kCheckboxRadioContentBoxSize + kCheckboxRadioBorderWidth * 2.0f;
544 void Theme::PaintCheckMark(DrawTarget& aDrawTarget,
545 const LayoutDeviceRect& aRect,
546 const sRGBColor& aColor) {
547 // Points come from the coordinates on a 14X14 (kCheckboxRadioBorderBoxSize)
548 // unit box centered at 0,0
549 const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f,
550 3.5f, -0.5f, -1.5f, -3.5f};
551 const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f,
552 -4.0f, 1.0f, 1.25f, -1.0f};
553 const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
554 const float scale =
555 ThemeDrawing::ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
556 auto center = aRect.Center().ToUnknownPoint();
558 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
559 Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale);
560 builder->MoveTo(p);
561 for (int32_t i = 1; i < checkNumPoints; i++) {
562 p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale);
563 builder->LineTo(p);
565 RefPtr<Path> path = builder->Finish();
567 aDrawTarget.Fill(path, ColorPattern(ToDeviceColor(aColor)));
570 void Theme::PaintIndeterminateMark(DrawTarget& aDrawTarget,
571 const LayoutDeviceRect& aRect,
572 const sRGBColor& aColor) {
573 const CSSCoord borderWidth = 2.0f;
574 const float scale =
575 ThemeDrawing::ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
577 Rect rect = aRect.ToUnknownRect();
578 rect.y += (rect.height / 2) - (borderWidth * scale / 2);
579 rect.height = borderWidth * scale;
580 rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
581 rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
583 aDrawTarget.FillRect(rect, ColorPattern(ToDeviceColor(aColor)));
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,
596 aDpiRatio);
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);
633 if (!blurFilter) {
634 return;
637 blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
639 IntSize inflation =
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);
651 if (!ellipseDT) {
652 return;
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 ElementState& aState, const Colors& aColors,
673 DPIRatio aDpiRatio) {
674 auto [backgroundColor, borderColor, checkColor] =
675 ComputeCheckboxColors(aState, StyleAppearance::Radio, aColors);
677 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
678 if (backgroundColor == borderColor) {
679 borderWidth = 0.0f;
681 PaintStrokedCircle(aPaintData, aRect, backgroundColor, borderColor,
682 borderWidth, aDpiRatio);
685 if (aState.HasState(ElementState::CHECKED)) {
686 LayoutDeviceRect rect(aRect);
687 auto width = LayoutDeviceCoord(
688 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth, aDpiRatio));
689 rect.Deflate(width);
691 PaintStrokedCircle(aPaintData, rect, backgroundColor, checkColor,
692 kCheckboxRadioBorderWidth, aDpiRatio);
695 if (aState.HasState(ElementState::FOCUSRING)) {
696 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, 5.0f, 1.0f);
700 template <typename PaintBackendData>
701 void Theme::PaintTextField(PaintBackendData& aPaintData,
702 const LayoutDeviceRect& aRect,
703 const ElementState& aState, const Colors& aColors,
704 DPIRatio aDpiRatio) {
705 auto [backgroundColor, borderColor] =
706 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::Yes);
708 const CSSCoord radius = 2.0f;
710 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
711 borderColor, kTextFieldBorderWidth,
712 radius, aDpiRatio);
714 if (aState.HasState(ElementState::FOCUSRING)) {
715 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
716 radius + kTextFieldBorderWidth,
717 -kTextFieldBorderWidth);
721 template <typename PaintBackendData>
722 void Theme::PaintListbox(PaintBackendData& aPaintData,
723 const LayoutDeviceRect& aRect,
724 const ElementState& aState, const Colors& aColors,
725 DPIRatio aDpiRatio) {
726 const CSSCoord radius = 2.0f;
727 auto [backgroundColor, borderColor] =
728 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::Yes);
730 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
731 borderColor, kMenulistBorderWidth,
732 radius, aDpiRatio);
734 if (aState.HasState(ElementState::FOCUSRING)) {
735 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
736 radius + kMenulistBorderWidth, -kMenulistBorderWidth);
740 template <typename PaintBackendData>
741 void Theme::PaintMenulist(PaintBackendData& aDrawTarget,
742 const LayoutDeviceRect& aRect,
743 const ElementState& aState, const Colors& aColors,
744 DPIRatio aDpiRatio) {
745 const CSSCoord radius = 4.0f;
746 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
748 ThemeDrawing::PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor,
749 borderColor, kMenulistBorderWidth,
750 radius, aDpiRatio);
752 if (aState.HasState(ElementState::FOCUSRING)) {
753 PaintRoundedFocusRect(aDrawTarget, aRect, aColors, aDpiRatio,
754 radius + kMenulistBorderWidth, -kMenulistBorderWidth);
758 enum class PhysicalArrowDirection {
759 Right,
760 Left,
761 Bottom,
764 void Theme::PaintMenuArrow(StyleAppearance aAppearance, nsIFrame* aFrame,
765 DrawTarget& aDrawTarget,
766 const LayoutDeviceRect& aRect) {
767 // not const: these may be negated in-place below
768 float polygonX[] = {-4.0f, -0.5f, 0.5f, 4.0f, 4.0f,
769 3.0f, 0.0f, 0.0f, -3.0f, -4.0f};
770 float polygonY[] = {-1, 3.0f, 3.0f, -1.0f, -2.0f,
771 -2.0f, 1.5f, 1.5f, -2.0f, -2.0f};
773 const bool isMenuList =
774 aAppearance == StyleAppearance::MozMenulistArrowButton;
775 const float kPolygonSize = kMinimumDropdownArrowButtonWidth;
777 const auto direction = [&] {
778 const auto wm = aFrame->GetWritingMode();
779 if (!isMenuList) {
780 return wm.IsPhysicalRTL() ? PhysicalArrowDirection::Left
781 : PhysicalArrowDirection::Right;
783 switch (wm.GetBlockDir()) {
784 case WritingMode::BlockDir::eBlockLR:
785 return PhysicalArrowDirection::Right;
786 case WritingMode::BlockDir::eBlockRL:
787 return PhysicalArrowDirection::Left;
788 case WritingMode::BlockDir::eBlockTB:
789 return PhysicalArrowDirection::Bottom;
791 MOZ_ASSERT_UNREACHABLE("Unknown direction?");
792 return PhysicalArrowDirection::Bottom;
793 }();
795 auto const [xs, ys] = [&] {
796 using Pair = std::pair<const float*, const float*>;
797 switch (direction) {
798 case PhysicalArrowDirection::Left:
799 // rotate 90°: [[0,1],[-1,0]]
800 for (float& f : polygonY) {
801 f = -f;
803 return Pair(polygonY, polygonX);
805 case PhysicalArrowDirection::Right:
806 // rotate 270°: [[0,-1],[1,0]]
807 for (float& f : polygonX) {
808 f = -f;
810 return Pair(polygonY, polygonX);
812 case PhysicalArrowDirection::Bottom:
813 // rotate 0°: [[1,0],[0,1]]
814 return Pair(polygonX, polygonY);
816 MOZ_ASSERT_UNREACHABLE("Unknown direction?");
817 return Pair(polygonX, polygonY);
818 }();
820 const auto arrowColor = sRGBColor::FromABGR(
821 nsLayoutUtils::GetColor(aFrame, &nsStyleText::mWebkitTextFillColor));
822 ThemeDrawing::PaintArrow(aDrawTarget, aRect, xs, ys, kPolygonSize,
823 ArrayLength(polygonX), arrowColor);
826 void Theme::PaintSpinnerButton(nsIFrame* aFrame, DrawTarget& aDrawTarget,
827 const LayoutDeviceRect& aRect,
828 const ElementState& aState,
829 StyleAppearance aAppearance,
830 const Colors& aColors, DPIRatio aDpiRatio) {
831 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
833 aDrawTarget.FillRect(aRect.ToUnknownRect(),
834 ColorPattern(ToDeviceColor(backgroundColor)));
836 const float kPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
837 2.5f, 0.0f, 0.0f, -2.5f, -3.5f};
838 float polygonY[] = {-1.5f, 1.5f, 1.5f, -1.5f, -2.5f,
839 -2.5f, 0.0f, 0.0f, -2.5f, -2.5f};
841 const float kPolygonSize = kMinimumSpinnerButtonHeight;
842 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
843 for (auto& coord : polygonY) {
844 coord = -coord;
848 ThemeDrawing::PaintArrow(aDrawTarget, aRect, kPolygonX, polygonY,
849 kPolygonSize, ArrayLength(kPolygonX), borderColor);
852 template <typename PaintBackendData>
853 void Theme::PaintRange(nsIFrame* aFrame, PaintBackendData& aPaintData,
854 const LayoutDeviceRect& aRect,
855 const ElementState& aState, const Colors& aColors,
856 DPIRatio aDpiRatio, bool aHorizontal) {
857 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
858 if (!rangeFrame) {
859 return;
862 auto tickMarks = rangeFrame->TickMarks();
863 double progress = rangeFrame->GetValueAsFractionOfRange();
864 auto rect = aRect;
865 LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
866 kMinimumRangeThumbSize * aDpiRatio);
867 LayoutDeviceRect progressClipRect(aRect);
868 LayoutDeviceRect trackClipRect(aRect);
869 const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
870 const LayoutDeviceCoord tickMarkWidth(
871 ThemeDrawing::SnapBorderWidth(1.0f, aDpiRatio));
872 const LayoutDeviceCoord tickMarkHeight(
873 ThemeDrawing::SnapBorderWidth(5.0f, aDpiRatio));
874 LayoutDevicePoint tickMarkOrigin, tickMarkDirection;
875 LayoutDeviceSize tickMarkSize;
876 if (aHorizontal) {
877 rect.height = verticalSize;
878 rect.y = aRect.y + (aRect.height - rect.height) / 2;
879 tickMarkSize = LayoutDeviceSize(tickMarkWidth, tickMarkHeight);
880 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
882 if (IsFrameRTL(aFrame)) {
883 tickMarkOrigin =
884 LayoutDevicePoint(aRect.XMost() - thumbRect.width / 2, aRect.YMost());
885 tickMarkDirection = LayoutDevicePoint(-1.0f, 0.0f);
886 thumbRect.x =
887 aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
888 float midPoint = thumbRect.Center().X();
889 trackClipRect.SetBoxX(aRect.X(), midPoint);
890 progressClipRect.SetBoxX(midPoint, aRect.XMost());
891 } else {
892 tickMarkOrigin =
893 LayoutDevicePoint(aRect.x + thumbRect.width / 2, aRect.YMost());
894 tickMarkDirection = LayoutDevicePoint(1.0, 0.0f);
895 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
896 float midPoint = thumbRect.Center().X();
897 progressClipRect.SetBoxX(aRect.X(), midPoint);
898 trackClipRect.SetBoxX(midPoint, aRect.XMost());
900 } else {
901 rect.width = verticalSize;
902 rect.x = aRect.x + (aRect.width - rect.width) / 2;
903 tickMarkOrigin = LayoutDevicePoint(aRect.XMost() - tickMarkHeight / 4,
904 aRect.YMost() - thumbRect.width / 2);
905 tickMarkDirection = LayoutDevicePoint(0.0f, -1.0f);
906 tickMarkSize = LayoutDeviceSize(tickMarkHeight, tickMarkWidth);
907 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
909 if (rangeFrame->IsUpwards()) {
910 thumbRect.y =
911 aRect.y + (aRect.height - thumbRect.height) * (1.0 - progress);
912 float midPoint = thumbRect.Center().Y();
913 trackClipRect.SetBoxY(aRect.Y(), midPoint);
914 progressClipRect.SetBoxY(midPoint, aRect.YMost());
915 } else {
916 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) * progress;
917 float midPoint = thumbRect.Center().Y();
918 trackClipRect.SetBoxY(midPoint, aRect.YMost());
919 progressClipRect.SetBoxY(aRect.Y(), midPoint);
923 const CSSCoord borderWidth = 1.0f;
924 const CSSCoord radius = 3.0f;
926 auto [progressColor, progressBorderColor] =
927 ComputeRangeProgressColors(aState, aColors);
928 auto [trackColor, trackBorderColor] =
929 ComputeRangeTrackColors(aState, aColors);
930 auto tickMarkColor = trackBorderColor;
932 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, progressClipRect,
933 progressColor, progressBorderColor,
934 borderWidth, radius, aDpiRatio);
936 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, trackClipRect,
937 trackColor, trackBorderColor,
938 borderWidth, radius, aDpiRatio);
940 if (!aState.HasState(ElementState::DISABLED)) {
941 // Ensure the shadow doesn't expand outside of our overflow rect declared in
942 // GetWidgetOverflow().
943 auto overflowRect = aRect;
944 overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio);
945 // Thumb shadow
946 PaintCircleShadow(aPaintData, thumbRect, overflowRect, 0.3f,
947 CSSPoint(0.0f, 2.0f), 2.0f, aDpiRatio);
950 tickMarkDirection.x *= aRect.width - thumbRect.width;
951 tickMarkDirection.y *= aRect.height - thumbRect.height;
952 tickMarkOrigin -=
953 LayoutDevicePoint(tickMarkSize.width, tickMarkSize.height) / 2;
954 auto tickMarkRect = LayoutDeviceRect(tickMarkOrigin, tickMarkSize);
955 for (auto tickMark : tickMarks) {
956 auto tickMarkOffset =
957 tickMarkDirection *
958 float(rangeFrame->GetDoubleAsFractionOfRange(tickMark));
959 ThemeDrawing::FillRect(aPaintData, tickMarkRect + tickMarkOffset,
960 tickMarkColor);
963 // Draw the thumb on top.
964 const CSSCoord thumbBorderWidth = 2.0f;
965 auto [thumbColor, thumbBorderColor] =
966 ComputeRangeThumbColors(aState, aColors);
968 PaintStrokedCircle(aPaintData, thumbRect, thumbColor, thumbBorderColor,
969 thumbBorderWidth, aDpiRatio);
971 if (aState.HasState(ElementState::FOCUSRING)) {
972 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, radius, 1.0f);
976 template <typename PaintBackendData>
977 void Theme::PaintProgress(nsIFrame* aFrame, PaintBackendData& aPaintData,
978 const LayoutDeviceRect& aRect,
979 const ElementState& aState, const Colors& aColors,
980 DPIRatio aDpiRatio, bool aIsMeter) {
981 const CSSCoord borderWidth = 1.0f;
982 const CSSCoord radius = aIsMeter ? 6.0f : 3.0f;
984 LayoutDeviceRect rect(aRect);
985 const LayoutDeviceCoord thickness =
986 (aIsMeter ? kMeterHeight : kProgressbarHeight) * aDpiRatio;
988 const bool isHorizontal = !nsNativeTheme::IsVerticalProgress(aFrame);
989 if (isHorizontal) {
990 // Center it vertically.
991 rect.y += (rect.height - thickness) / 2;
992 rect.height = thickness;
993 } else {
994 // Center it horizontally.
995 rect.x += (rect.width - thickness) / 2;
996 rect.width = thickness;
1000 // Paint the track, unclipped.
1001 auto [backgroundColor, borderColor] = ComputeProgressTrackColors(aColors);
1002 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, rect,
1003 backgroundColor, borderColor,
1004 borderWidth, radius, aDpiRatio);
1007 // Now paint the chunk, clipped as needed.
1008 LayoutDeviceRect clipRect = rect;
1009 if (aState.HasState(ElementState::INDETERMINATE)) {
1010 // For indeterminate progress, we paint an animated chunk of 1/3 of the
1011 // progress size.
1013 // Animation speed and math borrowed from GTK.
1014 const LayoutDeviceCoord size = isHorizontal ? rect.width : rect.height;
1015 const LayoutDeviceCoord barSize = size * 0.3333f;
1016 const LayoutDeviceCoord travel = 2.0f * (size - barSize);
1018 // Period equals to travel / pixelsPerMillisecond where pixelsPerMillisecond
1019 // equals progressSize / 1000.0. This is equivalent to 1600.
1020 const unsigned kPeriod = 1600;
1022 const int t = PR_IntervalToMilliseconds(PR_IntervalNow()) % kPeriod;
1023 const LayoutDeviceCoord dx = travel * float(t) / float(kPeriod);
1024 if (isHorizontal) {
1025 rect.width = barSize;
1026 rect.x += (dx < travel * .5f) ? dx : travel - dx;
1027 } else {
1028 rect.height = barSize;
1029 rect.y += (dx < travel * .5f) ? dx : travel - dx;
1031 clipRect = rect;
1032 // Queue the next frame if needed.
1033 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
1034 NS_WARNING("Couldn't refresh indeterminate <progress>");
1036 } else {
1037 // This is the progress chunk, clip it to the right amount.
1038 double position = [&] {
1039 if (aIsMeter) {
1040 auto* meter = dom::HTMLMeterElement::FromNode(aFrame->GetContent());
1041 if (!meter) {
1042 return 0.0;
1044 return meter->Position();
1046 auto* progress = dom::HTMLProgressElement::FromNode(aFrame->GetContent());
1047 if (!progress) {
1048 return 0.0;
1050 return progress->Position();
1051 }();
1052 if (isHorizontal) {
1053 double clipWidth = rect.width * position;
1054 clipRect.width = clipWidth;
1055 if (IsFrameRTL(aFrame)) {
1056 clipRect.x += rect.width - clipWidth;
1058 } else {
1059 double clipHeight = rect.height * position;
1060 clipRect.height = clipHeight;
1061 clipRect.y += rect.height - clipHeight;
1065 auto [backgroundColor, borderColor] =
1066 aIsMeter ? ComputeMeterchunkColors(aState, aColors)
1067 : ComputeProgressColors(aColors);
1068 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, clipRect,
1069 backgroundColor, borderColor,
1070 borderWidth, radius, aDpiRatio);
1073 template <typename PaintBackendData>
1074 void Theme::PaintButton(nsIFrame* aFrame, PaintBackendData& aPaintData,
1075 const LayoutDeviceRect& aRect,
1076 const ElementState& aState, const Colors& aColors,
1077 DPIRatio aDpiRatio) {
1078 const CSSCoord radius = 4.0f;
1079 auto [backgroundColor, borderColor] =
1080 ComputeButtonColors(aState, aColors, aFrame);
1082 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
1083 borderColor, kButtonBorderWidth,
1084 radius, aDpiRatio);
1086 if (aState.HasState(ElementState::FOCUSRING)) {
1087 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
1088 radius + kButtonBorderWidth, -kButtonBorderWidth);
1092 NS_IMETHODIMP
1093 Theme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1094 StyleAppearance aAppearance, const nsRect& aRect,
1095 const nsRect& /* aDirtyRect */,
1096 DrawOverflow aDrawOverflow) {
1097 if (!DoDrawWidgetBackground(*aContext->GetDrawTarget(), aFrame, aAppearance,
1098 aRect, aDrawOverflow)) {
1099 return NS_ERROR_NOT_IMPLEMENTED;
1101 return NS_OK;
1104 bool Theme::CreateWebRenderCommandsForWidget(
1105 mozilla::wr::DisplayListBuilder& aBuilder,
1106 mozilla::wr::IpcResourceUpdateQueue& aResources,
1107 const mozilla::layers::StackingContextHelper& aSc,
1108 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
1109 StyleAppearance aAppearance, const nsRect& aRect) {
1110 if (!StaticPrefs::widget_non_native_theme_webrender()) {
1111 return false;
1113 WebRenderBackendData data{aBuilder, aResources, aSc, aManager};
1114 return DoDrawWidgetBackground(data, aFrame, aAppearance, aRect,
1115 DrawOverflow::Yes);
1118 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1119 nscoord aTwipsPerPixel, DrawTarget& aDt) {
1120 return LayoutDeviceRect::FromUnknownRect(
1121 NSRectToSnappedRect(aRect, aTwipsPerPixel, aDt));
1124 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1125 nscoord aTwipsPerPixel,
1126 WebRenderBackendData& aDt) {
1127 // TODO: Do we need to do any more snapping here?
1128 return LayoutDeviceRect::FromAppUnits(aRect, aTwipsPerPixel);
1131 static ScrollbarDrawing::ScrollbarKind ComputeScrollbarKind(
1132 nsIFrame* aFrame, bool aIsHorizontal) {
1133 if (aIsHorizontal) {
1134 return ScrollbarDrawing::ScrollbarKind::Horizontal;
1136 nsIFrame* scrollbar = ScrollbarDrawing::GetParentScrollbarFrame(aFrame);
1137 if (NS_WARN_IF(!scrollbar)) {
1138 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1140 MOZ_ASSERT(scrollbar->IsScrollbarFrame());
1141 nsIScrollbarMediator* sm =
1142 static_cast<nsScrollbarFrame*>(scrollbar)->GetScrollbarMediator();
1143 if (NS_WARN_IF(!sm)) {
1144 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1146 return sm->IsScrollbarOnRight()
1147 ? ScrollbarDrawing::ScrollbarKind::VerticalRight
1148 : ScrollbarDrawing::ScrollbarKind::VerticalLeft;
1151 static ScrollbarDrawing::ScrollbarKind ComputeScrollbarKindForScrollCorner(
1152 nsIFrame* aFrame) {
1153 nsIScrollableFrame* sf = do_QueryFrame(aFrame->GetParent());
1154 if (!sf) {
1155 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1157 return sf->IsScrollbarOnRight()
1158 ? ScrollbarDrawing::ScrollbarKind::VerticalRight
1159 : ScrollbarDrawing::ScrollbarKind::VerticalLeft;
1162 template <typename PaintBackendData>
1163 bool Theme::DoDrawWidgetBackground(PaintBackendData& aPaintData,
1164 nsIFrame* aFrame,
1165 StyleAppearance aAppearance,
1166 const nsRect& aRect,
1167 DrawOverflow aDrawOverflow) {
1168 static_assert(std::is_same_v<PaintBackendData, DrawTarget> ||
1169 std::is_same_v<PaintBackendData, WebRenderBackendData>);
1171 const nsPresContext* pc = aFrame->PresContext();
1172 const nscoord twipsPerPixel = pc->AppUnitsPerDevPixel();
1173 const auto devPxRect = ToSnappedRect(aRect, twipsPerPixel, aPaintData);
1175 const DocumentState docState = pc->Document()->GetDocumentState();
1176 ElementState elementState = GetContentState(aFrame, aAppearance);
1177 // Paint the outline iff we're asked to draw overflow and we have
1178 // outline-style: auto.
1179 if (aDrawOverflow == DrawOverflow::Yes &&
1180 aFrame->StyleOutline()->mOutlineStyle.IsAuto()) {
1181 elementState |= ElementState::FOCUSRING;
1182 } else {
1183 elementState &= ~ElementState::FOCUSRING;
1186 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1187 // overflow devPxRect.
1188 Maybe<AutoClipRect> maybeClipRect;
1189 if constexpr (std::is_same_v<PaintBackendData, DrawTarget>) {
1190 if (aAppearance != StyleAppearance::FocusOutline &&
1191 aAppearance != StyleAppearance::Range &&
1192 !elementState.HasState(ElementState::FOCUSRING)) {
1193 maybeClipRect.emplace(aPaintData, devPxRect);
1197 const Colors colors(aFrame, aAppearance);
1198 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1200 switch (aAppearance) {
1201 case StyleAppearance::Radio: {
1202 auto rect = CheckBoxRadioRect(devPxRect);
1203 PaintRadioControl(aPaintData, rect, elementState, colors, dpiRatio);
1204 break;
1206 case StyleAppearance::Checkbox: {
1207 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1208 // TODO: Need to figure out how to best draw this using WR.
1209 return false;
1210 } else {
1211 auto rect = CheckBoxRadioRect(devPxRect);
1212 PaintCheckboxControl(aPaintData, rect, elementState, colors, dpiRatio);
1214 break;
1216 case StyleAppearance::Textarea:
1217 case StyleAppearance::Textfield:
1218 case StyleAppearance::NumberInput:
1219 PaintTextField(aPaintData, devPxRect, elementState, colors, dpiRatio);
1220 break;
1221 case StyleAppearance::Listbox:
1222 PaintListbox(aPaintData, devPxRect, elementState, colors, dpiRatio);
1223 break;
1224 case StyleAppearance::MenulistButton:
1225 case StyleAppearance::Menulist:
1226 PaintMenulist(aPaintData, devPxRect, elementState, colors, dpiRatio);
1227 break;
1228 case StyleAppearance::Menuarrow:
1229 case StyleAppearance::MozMenulistArrowButton:
1230 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1231 // TODO: Need to figure out how to best draw this using WR.
1232 return false;
1233 } else {
1234 PaintMenuArrow(aAppearance, aFrame, aPaintData, devPxRect);
1236 break;
1237 case StyleAppearance::Tooltip: {
1238 const CSSCoord strokeWidth(1.0f);
1239 const CSSCoord strokeRadius(2.0f);
1240 ThemeDrawing::PaintRoundedRectWithRadius(
1241 aPaintData, devPxRect,
1242 colors.System(StyleSystemColor::Infobackground),
1243 colors.System(StyleSystemColor::Infotext), strokeWidth, strokeRadius,
1244 dpiRatio);
1245 break;
1247 case StyleAppearance::Menuitem: {
1248 ThemeDrawing::FillRect(aPaintData, devPxRect, [&] {
1249 if (CheckBooleanAttr(aFrame, nsGkAtoms::menuactive)) {
1250 if (elementState.HasState(ElementState::DISABLED)) {
1251 return colors.System(StyleSystemColor::MozMenuhoverdisabled);
1253 return colors.System(StyleSystemColor::MozMenuhover);
1255 return sTransparent;
1256 }());
1257 break;
1259 case StyleAppearance::SpinnerUpbutton:
1260 case StyleAppearance::SpinnerDownbutton:
1261 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1262 // TODO: Need to figure out how to best draw this using WR.
1263 return false;
1264 } else {
1265 PaintSpinnerButton(aFrame, aPaintData, devPxRect, elementState,
1266 aAppearance, colors, dpiRatio);
1268 break;
1269 case StyleAppearance::Range:
1270 PaintRange(aFrame, aPaintData, devPxRect, elementState, colors, dpiRatio,
1271 IsRangeHorizontal(aFrame));
1272 break;
1273 case StyleAppearance::RangeThumb:
1274 // Painted as part of StyleAppearance::Range.
1275 break;
1276 case StyleAppearance::ProgressBar:
1277 PaintProgress(aFrame, aPaintData, devPxRect, elementState, colors,
1278 dpiRatio,
1279 /* aIsMeter = */ false);
1280 break;
1281 case StyleAppearance::Progresschunk:
1282 /* Painted as part of the progress bar */
1283 break;
1284 case StyleAppearance::Meter:
1285 PaintProgress(aFrame, aPaintData, devPxRect, elementState, colors,
1286 dpiRatio,
1287 /* aIsMeter = */ true);
1288 break;
1289 case StyleAppearance::Meterchunk:
1290 /* Painted as part of the meter bar */
1291 break;
1292 case StyleAppearance::ScrollbarthumbHorizontal:
1293 case StyleAppearance::ScrollbarthumbVertical: {
1294 bool isHorizontal =
1295 aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
1296 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1297 return GetScrollbarDrawing().PaintScrollbarThumb(
1298 aPaintData, devPxRect, kind, aFrame,
1299 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1300 colors, dpiRatio);
1302 case StyleAppearance::ScrollbartrackHorizontal:
1303 case StyleAppearance::ScrollbartrackVertical: {
1304 bool isHorizontal =
1305 aAppearance == StyleAppearance::ScrollbartrackHorizontal;
1306 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1307 return GetScrollbarDrawing().PaintScrollbarTrack(
1308 aPaintData, devPxRect, kind, aFrame,
1309 *nsLayoutUtils::StyleForScrollbar(aFrame), docState, colors,
1310 dpiRatio);
1312 case StyleAppearance::ScrollbarHorizontal:
1313 case StyleAppearance::ScrollbarVertical: {
1314 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
1315 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1316 return GetScrollbarDrawing().PaintScrollbar(
1317 aPaintData, devPxRect, kind, aFrame,
1318 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1319 colors, dpiRatio);
1321 case StyleAppearance::Scrollcorner: {
1322 auto kind = ComputeScrollbarKindForScrollCorner(aFrame);
1323 return GetScrollbarDrawing().PaintScrollCorner(
1324 aPaintData, devPxRect, kind, aFrame,
1325 *nsLayoutUtils::StyleForScrollbar(aFrame), docState, colors,
1326 dpiRatio);
1328 case StyleAppearance::ScrollbarbuttonUp:
1329 case StyleAppearance::ScrollbarbuttonDown:
1330 case StyleAppearance::ScrollbarbuttonLeft:
1331 case StyleAppearance::ScrollbarbuttonRight: {
1332 // For scrollbar-width:thin, we don't display the buttons.
1333 if (!ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) {
1334 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1335 // TODO: Need to figure out how to best draw this using WR.
1336 return false;
1337 } else {
1338 bool isHorizontal =
1339 aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
1340 aAppearance == StyleAppearance::ScrollbarbuttonRight;
1341 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1342 GetScrollbarDrawing().PaintScrollbarButton(
1343 aPaintData, aAppearance, devPxRect, kind, aFrame,
1344 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1345 colors, dpiRatio);
1348 break;
1350 case StyleAppearance::Button:
1351 PaintButton(aFrame, aPaintData, devPxRect, elementState, colors,
1352 dpiRatio);
1353 break;
1354 case StyleAppearance::FocusOutline:
1355 PaintAutoStyleOutline(aFrame, aPaintData, devPxRect, colors, dpiRatio);
1356 break;
1357 default:
1358 // Various appearance values are used for XUL elements. Normally these
1359 // will not be available in content documents (and thus in the content
1360 // processes where the native basic theme can be used), but tests are
1361 // run with the remote XUL pref enabled and so we can get in here. So
1362 // we just return an error rather than assert.
1363 return false;
1366 return true;
1369 template <typename PaintBackendData>
1370 void Theme::PaintAutoStyleOutline(nsIFrame* aFrame,
1371 PaintBackendData& aPaintData,
1372 const LayoutDeviceRect& aRect,
1373 const Colors& aColors, DPIRatio aDpiRatio) {
1374 const auto& accentColor = aColors.Accent();
1375 const bool solid = StaticPrefs::widget_non_native_theme_solid_outline_style();
1376 LayoutDeviceCoord strokeWidth(ThemeDrawing::SnapBorderWidth(2.0f, aDpiRatio));
1378 LayoutDeviceRect rect(aRect);
1379 rect.Inflate(strokeWidth);
1381 const nscoord a2d = aFrame->PresContext()->AppUnitsPerDevPixel();
1382 nscoord cssOffset = aFrame->StyleOutline()->mOutlineOffset.ToAppUnits();
1383 nscoord cssRadii[8] = {0};
1384 if (!aFrame->GetBorderRadii(cssRadii)) {
1385 const auto twoPixels = 2 * AppUnitsPerCSSPixel();
1386 const nscoord radius =
1387 cssOffset >= 0 ? twoPixels : std::max(twoPixels + cssOffset, 0);
1388 cssOffset = -twoPixels;
1389 for (auto& r : cssRadii) {
1390 r = radius;
1394 auto offset = LayoutDevicePixel::FromAppUnits(cssOffset, a2d);
1395 RectCornerRadii innerRadii;
1396 nsCSSRendering::ComputePixelRadii(cssRadii, a2d, &innerRadii);
1398 // NOTE(emilio): This doesn't use PaintRoundedRectWithRadius because we need
1399 // to support arbitrary radii.
1400 auto DrawRect = [&](const sRGBColor& aColor) {
1401 RectCornerRadii outerRadii;
1402 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1403 const Float widths[4] = {strokeWidth + offset, strokeWidth + offset,
1404 strokeWidth + offset, strokeWidth + offset};
1405 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1406 const auto dest = wr::ToLayoutRect(rect);
1407 const auto side =
1408 wr::ToBorderSide(ToDeviceColor(aColor), StyleBorderStyle::Solid);
1409 const wr::BorderSide sides[4] = {side, side, side, side};
1410 const bool kBackfaceIsVisible = true;
1411 const auto wrWidths = wr::ToBorderWidths(strokeWidth, strokeWidth,
1412 strokeWidth, strokeWidth);
1413 const auto wrRadius = wr::ToBorderRadius(outerRadii);
1414 aPaintData.mBuilder.PushBorder(dest, dest, kBackfaceIsVisible, wrWidths,
1415 {sides, 4}, wrRadius);
1416 } else {
1417 const LayoutDeviceCoord halfWidth = strokeWidth * 0.5f;
1418 const Float widths[4] = {halfWidth + offset, halfWidth + offset,
1419 halfWidth + offset, halfWidth + offset};
1420 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1421 LayoutDeviceRect dest(rect);
1422 dest.Deflate(halfWidth);
1423 RefPtr<Path> path =
1424 MakePathForRoundedRect(aPaintData, dest.ToUnknownRect(), outerRadii);
1425 aPaintData.Stroke(path, ColorPattern(ToDeviceColor(aColor)),
1426 StrokeOptions(strokeWidth));
1430 DrawRect(accentColor.Get());
1432 if (solid) {
1433 return;
1436 offset += strokeWidth;
1438 strokeWidth =
1439 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(1.0f, aDpiRatio));
1440 rect.Inflate(strokeWidth);
1442 DrawRect(accentColor.GetForeground());
1445 LayoutDeviceIntMargin Theme::GetWidgetBorder(nsDeviceContext* aContext,
1446 nsIFrame* aFrame,
1447 StyleAppearance aAppearance) {
1448 switch (aAppearance) {
1449 case StyleAppearance::Textfield:
1450 case StyleAppearance::Textarea:
1451 case StyleAppearance::NumberInput:
1452 case StyleAppearance::Listbox:
1453 case StyleAppearance::Menulist:
1454 case StyleAppearance::MenulistButton:
1455 case StyleAppearance::Button:
1456 // Return the border size from the UA sheet, even though what we paint
1457 // doesn't actually match that. We know this is the UA sheet border
1458 // because we disable native theming when different border widths are
1459 // specified by authors, see Theme::IsWidgetStyled.
1461 // The Rounded() bit is technically redundant, but needed to appease the
1462 // type system, we should always end up with full device pixels due to
1463 // round_border_to_device_pixels at style time.
1464 return LayoutDeviceIntMargin::FromAppUnits(
1465 aFrame->StyleBorder()->GetComputedBorder(),
1466 aFrame->PresContext()->AppUnitsPerDevPixel())
1467 .Rounded();
1468 case StyleAppearance::Checkbox:
1469 case StyleAppearance::Radio: {
1470 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1471 LayoutDeviceIntCoord w =
1472 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth, dpiRatio);
1473 return LayoutDeviceIntMargin(w, w, w, w);
1475 default:
1476 return LayoutDeviceIntMargin();
1480 bool Theme::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
1481 StyleAppearance aAppearance,
1482 LayoutDeviceIntMargin* aResult) {
1483 switch (aAppearance) {
1484 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1485 // and have a meaningful baseline, so they can't have
1486 // author-specified padding.
1487 case StyleAppearance::Radio:
1488 case StyleAppearance::Checkbox:
1489 aResult->SizeTo(0, 0, 0, 0);
1490 return true;
1491 default:
1492 break;
1494 return false;
1497 bool Theme::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
1498 StyleAppearance aAppearance,
1499 nsRect* aOverflowRect) {
1500 CSSIntMargin overflow;
1501 switch (aAppearance) {
1502 case StyleAppearance::FocusOutline: {
1503 // 2px * one segment, or 2px + 1px
1504 const auto width =
1505 StaticPrefs::widget_non_native_theme_solid_outline_style() ? 2 : 3;
1506 overflow.SizeTo(width, width, width, width);
1507 break;
1509 case StyleAppearance::Radio:
1510 case StyleAppearance::Checkbox:
1511 case StyleAppearance::Range:
1512 // 2px for each outline segment, plus 1px separation, plus we paint with a
1513 // 1px extra offset, so 6px.
1514 overflow.SizeTo(6, 6, 6, 6);
1515 break;
1516 case StyleAppearance::Textarea:
1517 case StyleAppearance::Textfield:
1518 case StyleAppearance::NumberInput:
1519 case StyleAppearance::Listbox:
1520 case StyleAppearance::MenulistButton:
1521 case StyleAppearance::Menulist:
1522 case StyleAppearance::Button:
1523 // 2px for each segment, plus 1px separation, but we paint 1px inside
1524 // the border area so 4px overflow.
1525 overflow.SizeTo(4, 4, 4, 4);
1526 break;
1527 default:
1528 return false;
1531 // TODO: This should convert from device pixels to app units, not from CSS
1532 // pixels. And it should take the dpi ratio into account.
1533 // Using CSS pixels can cause the overflow to be too small if the page is
1534 // zoomed out.
1535 aOverflowRect->Inflate(CSSPixel::ToAppUnits(overflow));
1536 return true;
1539 LayoutDeviceIntCoord Theme::GetScrollbarSize(const nsPresContext* aPresContext,
1540 StyleScrollbarWidth aWidth,
1541 Overlay aOverlay) {
1542 return GetScrollbarDrawing().GetScrollbarSize(aPresContext, aWidth, aOverlay);
1545 nscoord Theme::GetCheckboxRadioPrefSize() {
1546 return CSSPixel::ToAppUnits(kCheckboxRadioContentBoxSize);
1549 /* static */
1550 UniquePtr<ScrollbarDrawing> Theme::ScrollbarStyle() {
1551 switch (StaticPrefs::widget_non_native_theme_scrollbar_style()) {
1552 case 1:
1553 return MakeUnique<ScrollbarDrawingCocoa>();
1554 case 2:
1555 return MakeUnique<ScrollbarDrawingGTK>();
1556 case 3:
1557 return MakeUnique<ScrollbarDrawingAndroid>();
1558 case 4:
1559 return MakeUnique<ScrollbarDrawingWin>();
1560 case 5:
1561 return MakeUnique<ScrollbarDrawingWin11>();
1562 default:
1563 break;
1565 // Default to native scrollbar style for each platform.
1566 #ifdef XP_WIN
1567 if (IsWin11OrLater()) {
1568 return MakeUnique<ScrollbarDrawingWin11>();
1570 return MakeUnique<ScrollbarDrawingWin>();
1571 #elif MOZ_WIDGET_COCOA
1572 return MakeUnique<ScrollbarDrawingCocoa>();
1573 #elif MOZ_WIDGET_GTK
1574 return MakeUnique<ScrollbarDrawingGTK>();
1575 #elif ANDROID
1576 return MakeUnique<ScrollbarDrawingAndroid>();
1577 #else
1578 # error "Unknown platform, need scrollbar implementation."
1579 #endif
1582 LayoutDeviceIntSize Theme::GetMinimumWidgetSize(nsPresContext* aPresContext,
1583 nsIFrame* aFrame,
1584 StyleAppearance aAppearance) {
1585 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1587 if (IsWidgetScrollbarPart(aAppearance)) {
1588 return GetScrollbarDrawing().GetMinimumWidgetSize(aPresContext, aAppearance,
1589 aFrame);
1592 LayoutDeviceIntSize result;
1593 switch (aAppearance) {
1594 case StyleAppearance::RangeThumb:
1595 result.SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
1596 (kMinimumRangeThumbSize * dpiRatio).Rounded());
1597 break;
1598 case StyleAppearance::MozMenulistArrowButton:
1599 result.width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
1600 break;
1601 case StyleAppearance::SpinnerUpbutton:
1602 case StyleAppearance::SpinnerDownbutton:
1603 result.width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
1604 result.height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
1605 break;
1606 default:
1607 break;
1609 return result;
1612 nsITheme::Transparency Theme::GetWidgetTransparency(
1613 nsIFrame* aFrame, StyleAppearance aAppearance) {
1614 if (auto scrollbar = GetScrollbarDrawing().GetScrollbarPartTransparency(
1615 aFrame, aAppearance)) {
1616 return *scrollbar;
1618 if (aAppearance == StyleAppearance::Tooltip) {
1619 // We draw a rounded rect, so we need transparency.
1620 return eTransparent;
1622 return eUnknownTransparency;
1625 NS_IMETHODIMP
1626 Theme::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
1627 nsAtom* aAttribute, bool* aShouldRepaint,
1628 const nsAttrValue* aOldValue) {
1629 if (!aAttribute) {
1630 // Hover/focus/active changed. Always repaint.
1631 *aShouldRepaint = true;
1632 } else {
1633 // Check the attribute to see if it's relevant.
1634 // disabled, checked, dlgtype, default, etc.
1635 *aShouldRepaint = false;
1636 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
1637 aAttribute == nsGkAtoms::selected ||
1638 aAttribute == nsGkAtoms::visuallyselected ||
1639 aAttribute == nsGkAtoms::menuactive ||
1640 aAttribute == nsGkAtoms::sortDirection ||
1641 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
1642 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover) {
1643 *aShouldRepaint = true;
1647 return NS_OK;
1650 NS_IMETHODIMP
1651 Theme::ThemeChanged() { return NS_OK; }
1653 bool Theme::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
1654 return IsWidgetScrollbarPart(aAppearance);
1657 nsITheme::ThemeGeometryType Theme::ThemeGeometryTypeForWidget(
1658 nsIFrame* aFrame, StyleAppearance aAppearance) {
1659 return eThemeGeometryTypeUnknown;
1662 bool Theme::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
1663 StyleAppearance aAppearance) {
1664 switch (aAppearance) {
1665 case StyleAppearance::Radio:
1666 case StyleAppearance::Checkbox:
1667 case StyleAppearance::FocusOutline:
1668 case StyleAppearance::Textarea:
1669 case StyleAppearance::Textfield:
1670 case StyleAppearance::Range:
1671 case StyleAppearance::RangeThumb:
1672 case StyleAppearance::ProgressBar:
1673 case StyleAppearance::Progresschunk:
1674 case StyleAppearance::Meter:
1675 case StyleAppearance::Meterchunk:
1676 case StyleAppearance::ScrollbarbuttonUp:
1677 case StyleAppearance::ScrollbarbuttonDown:
1678 case StyleAppearance::ScrollbarbuttonLeft:
1679 case StyleAppearance::ScrollbarbuttonRight:
1680 case StyleAppearance::ScrollbarthumbHorizontal:
1681 case StyleAppearance::ScrollbarthumbVertical:
1682 case StyleAppearance::ScrollbartrackHorizontal:
1683 case StyleAppearance::ScrollbartrackVertical:
1684 case StyleAppearance::ScrollbarHorizontal:
1685 case StyleAppearance::ScrollbarVertical:
1686 case StyleAppearance::Scrollcorner:
1687 case StyleAppearance::Button:
1688 case StyleAppearance::Listbox:
1689 case StyleAppearance::Menulist:
1690 case StyleAppearance::MenulistButton:
1691 case StyleAppearance::NumberInput:
1692 case StyleAppearance::MozMenulistArrowButton:
1693 case StyleAppearance::Menuarrow:
1694 case StyleAppearance::SpinnerUpbutton:
1695 case StyleAppearance::SpinnerDownbutton:
1696 case StyleAppearance::Menuitem:
1697 case StyleAppearance::Tooltip:
1698 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
1699 default:
1700 return false;
1704 bool Theme::WidgetIsContainer(StyleAppearance aAppearance) {
1705 switch (aAppearance) {
1706 case StyleAppearance::MozMenulistArrowButton:
1707 case StyleAppearance::Radio:
1708 case StyleAppearance::Checkbox:
1709 return false;
1710 default:
1711 return true;
1715 bool Theme::ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance) {
1716 return true;
1719 bool Theme::ThemeNeedsComboboxDropmarker() { return true; }
1721 bool Theme::ThemeSupportsScrollbarButtons() {
1722 return GetScrollbarDrawing().ShouldDrawScrollbarButtons();
1725 } // namespace mozilla::widget