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