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 "nsNativeBasicTheme.h"
9 #include "mozilla/MathAlgorithms.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/gfx/Rect.h"
12 #include "mozilla/gfx/Types.h"
13 #include "mozilla/gfx/Filters.h"
14 #include "nsCSSColorUtils.h"
15 #include "nsCSSRendering.h"
16 #include "nsLayoutUtils.h"
17 #include "PathHelpers.h"
19 using namespace mozilla
;
20 using namespace mozilla::widget
;
21 using namespace mozilla::gfx
;
23 NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme
, nsNativeTheme
, nsITheme
)
27 // This pushes and pops a clip rect to the draw target.
29 // This is done to reduce fuzz in places where we may have antialiasing,
30 // because skia is not clip-invariant: given different clips, it does not
31 // guarantee the same result, even if the painted content doesn't intersect
34 // This is a bit sad, overall, but...
35 struct MOZ_RAII AutoClipRect
{
36 AutoClipRect(DrawTarget
& aDt
, const LayoutDeviceRect
& aRect
) : mDt(aDt
) {
37 mDt
.PushClipRect(aRect
.ToUnknownRect());
40 ~AutoClipRect() { mDt
.PopClip(); }
46 static LayoutDeviceIntCoord
SnapBorderWidth(
47 CSSCoord aCssWidth
, nsNativeBasicTheme::DPIRatio aDpiRatio
) {
48 if (aCssWidth
== 0.0f
) {
51 return std::max(LayoutDeviceIntCoord(1), (aCssWidth
* aDpiRatio
).Truncated());
56 static bool IsScrollbarWidthThin(nsIFrame
* aFrame
) {
57 ComputedStyle
* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
58 auto scrollbarWidth
= style
->StyleUIReset()->mScrollbarWidth
;
59 return scrollbarWidth
== StyleScrollbarWidth::Thin
;
63 auto nsNativeBasicTheme::GetDPIRatioForScrollbarPart(nsPresContext
* aPc
)
65 return DPIRatio(float(AppUnitsPerCSSPixel()) /
66 aPc
->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
70 auto nsNativeBasicTheme::GetDPIRatio(nsPresContext
* aPc
,
71 StyleAppearance aAppearance
) -> DPIRatio
{
72 // Widgets react to zoom, except scrollbars.
73 if (IsWidgetScrollbarPart(aAppearance
)) {
74 return GetDPIRatioForScrollbarPart(aPc
);
76 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc
->AppUnitsPerDevPixel());
80 auto nsNativeBasicTheme::GetDPIRatio(nsIFrame
* aFrame
,
81 StyleAppearance aAppearance
) -> DPIRatio
{
82 return GetDPIRatio(aFrame
->PresContext(), aAppearance
);
86 bool nsNativeBasicTheme::IsDateTimeResetButton(nsIFrame
* aFrame
) {
91 nsIFrame
* parent
= aFrame
->GetParent();
92 if (parent
&& (parent
= parent
->GetParent()) &&
93 (parent
= parent
->GetParent())) {
94 nsDateTimeControlFrame
* dateTimeFrame
= do_QueryFrame(parent
);
103 bool nsNativeBasicTheme::IsColorPickerButton(nsIFrame
* aFrame
) {
104 nsColorControlFrame
* colorPickerButton
= do_QueryFrame(aFrame
);
105 return colorPickerButton
;
109 LayoutDeviceRect
nsNativeBasicTheme::FixAspectRatio(
110 const LayoutDeviceRect
& aRect
) {
111 // Checkbox and radio need to preserve aspect-ratio for compat.
112 LayoutDeviceRect
rect(aRect
);
113 if (rect
.width
== rect
.height
) {
117 if (rect
.width
> rect
.height
) {
118 auto diff
= rect
.width
- rect
.height
;
119 rect
.width
= rect
.height
;
122 auto diff
= rect
.height
- rect
.width
;
123 rect
.height
= rect
.width
;
130 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeCheckboxColors(
131 const EventStates
& aState
, StyleAppearance aAppearance
) {
132 MOZ_ASSERT(aAppearance
== StyleAppearance::Checkbox
||
133 aAppearance
== StyleAppearance::Radio
);
135 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
136 bool isPressed
= !isDisabled
&& aState
.HasAllStates(NS_EVENT_STATE_HOVER
|
137 NS_EVENT_STATE_ACTIVE
);
138 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
139 bool isChecked
= aState
.HasState(NS_EVENT_STATE_CHECKED
);
140 bool isIndeterminate
= aAppearance
== StyleAppearance::Checkbox
&&
141 aState
.HasState(NS_EVENT_STATE_INDETERMINATE
);
143 sRGBColor backgroundColor
= sColorWhite
;
144 sRGBColor borderColor
= sColorGrey40
;
146 if (isChecked
|| isIndeterminate
) {
147 backgroundColor
= borderColor
= sColorGrey40Alpha50
;
149 backgroundColor
= sColorWhiteAlpha50
;
150 borderColor
= sColorGrey40Alpha50
;
153 if (isChecked
|| isIndeterminate
) {
155 backgroundColor
= borderColor
= sColorAccentDarker
;
156 } else if (isHovered
) {
157 backgroundColor
= borderColor
= sColorAccentDark
;
159 backgroundColor
= borderColor
= sColorAccent
;
161 } else if (isPressed
) {
162 backgroundColor
= sColorGrey20
;
163 borderColor
= sColorGrey60
;
164 } else if (isHovered
) {
165 backgroundColor
= sColorWhite
;
166 borderColor
= sColorGrey50
;
168 backgroundColor
= sColorWhite
;
169 borderColor
= sColorGrey40
;
173 return std::make_pair(backgroundColor
, borderColor
);
176 sRGBColor
nsNativeBasicTheme::ComputeCheckmarkColor(const EventStates
& aState
) {
177 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
178 return isDisabled
? sColorWhiteAlpha50
: sColorWhite
;
181 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeRadioCheckmarkColors(
182 const EventStates
& aState
) {
183 auto [unusedColor
, checkColor
] =
184 ComputeCheckboxColors(aState
, StyleAppearance::Radio
);
185 Unused
<< unusedColor
;
187 return std::make_pair(sColorWhite
, checkColor
);
190 sRGBColor
nsNativeBasicTheme::ComputeBorderColor(const EventStates
& aState
) {
191 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
193 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
194 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
195 bool isFocused
= aState
.HasState(NS_EVENT_STATE_FOCUSRING
);
197 return sColorGrey40Alpha50
;
211 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeButtonColors(
212 const EventStates
& aState
, nsIFrame
* aFrame
) {
214 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
215 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
216 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
218 const sRGBColor
& backgroundColor
= [&] {
220 return sColorGrey10Alpha50
;
222 if (IsDateTimeResetButton(aFrame
)) {
234 const sRGBColor borderColor
= ComputeBorderColor(aState
);
235 return std::make_pair(backgroundColor
, borderColor
);
238 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeTextfieldColors(
239 const EventStates
& aState
) {
240 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
241 const sRGBColor
& backgroundColor
=
242 isDisabled
? sColorWhiteAlpha50
: sColorWhite
;
243 const sRGBColor borderColor
= ComputeBorderColor(aState
);
245 return std::make_pair(backgroundColor
, borderColor
);
248 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeRangeProgressColors(
249 const EventStates
& aState
) {
251 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
252 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
253 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
256 return std::make_pair(sColorGrey40Alpha50
, sColorGrey40Alpha50
);
258 if (isActive
|| isHovered
) {
259 return std::make_pair(sColorAccentDark
, sColorAccentDarker
);
261 return std::make_pair(sColorAccent
, sColorAccentDark
);
264 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeRangeTrackColors(
265 const EventStates
& aState
) {
267 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
268 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
269 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
272 return std::make_pair(sColorGrey10Alpha50
, sColorGrey40Alpha50
);
274 if (isActive
|| isHovered
) {
275 return std::make_pair(sColorGrey20
, sColorGrey50
);
277 return std::make_pair(sColorGrey10
, sColorGrey40
);
280 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeRangeThumbColors(
281 const EventStates
& aState
) {
283 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
284 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
285 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
287 const sRGBColor
& backgroundColor
= [&] {
289 return sColorGrey50Alpha50
;
300 const sRGBColor borderColor
= sColorWhite
;
302 return std::make_pair(backgroundColor
, borderColor
);
305 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeProgressColors() {
306 return std::make_pair(sColorAccent
, sColorAccentDark
);
309 std::pair
<sRGBColor
, sRGBColor
>
310 nsNativeBasicTheme::ComputeProgressTrackColors() {
311 return std::make_pair(sColorGrey10
, sColorGrey40
);
314 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeMeterchunkColors(
315 const EventStates
& aMeterState
) {
316 sRGBColor borderColor
= sColorMeterGreen20
;
317 sRGBColor chunkColor
= sColorMeterGreen10
;
319 if (aMeterState
.HasState(NS_EVENT_STATE_SUB_OPTIMUM
)) {
320 borderColor
= sColorMeterYellow20
;
321 chunkColor
= sColorMeterYellow10
;
322 } else if (aMeterState
.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM
)) {
323 borderColor
= sColorMeterRed20
;
324 chunkColor
= sColorMeterRed10
;
327 return std::make_pair(chunkColor
, borderColor
);
330 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeMeterTrackColors() {
331 return std::make_pair(sColorGrey10
, sColorGrey40
);
334 sRGBColor
nsNativeBasicTheme::ComputeMenulistArrowButtonColor(
335 const EventStates
& aState
) {
336 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
337 return isDisabled
? sColorGrey60Alpha50
: sColorGrey60
;
340 std::array
<sRGBColor
, 3> nsNativeBasicTheme::ComputeFocusRectColors() {
341 return {sColorAccent
, sColorWhiteAlpha80
, sColorAccentLight
};
344 std::pair
<sRGBColor
, sRGBColor
> nsNativeBasicTheme::ComputeScrollbarColors(
345 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
346 const EventStates
& aDocumentState
, bool aIsRoot
) {
347 const nsStyleUI
* ui
= aStyle
.StyleUI();
349 if (ui
->mScrollbarColor
.IsColors()) {
350 color
= ui
->mScrollbarColor
.AsColors().track
.CalcColor(aStyle
);
351 } else if (aDocumentState
.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE
)) {
352 color
= LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarInactive
,
353 sScrollbarColor
.ToABGR());
355 color
= LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbar
,
356 sScrollbarColor
.ToABGR());
359 // Root scrollbars must be opaque.
360 nscolor bg
= LookAndFeel::GetColor(LookAndFeel::ColorID::WindowBackground
,
361 NS_RGB(0xff, 0xff, 0xff));
362 color
= NS_ComposeColors(bg
, color
);
364 return std::make_pair(gfx::sRGBColor::FromABGR(color
), sScrollbarBorderColor
);
367 sRGBColor
nsNativeBasicTheme::ComputeScrollbarThumbColor(
368 nsIFrame
* aFrame
, const ComputedStyle
& aStyle
,
369 const EventStates
& aElementState
, const EventStates
& aDocumentState
) {
370 const nsStyleUI
* ui
= aStyle
.StyleUI();
372 if (ui
->mScrollbarColor
.IsColors()) {
373 color
= ui
->mScrollbarColor
.AsColors().thumb
.CalcColor(aStyle
);
374 } else if (aDocumentState
.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE
)) {
375 color
= LookAndFeel::GetColor(
376 LookAndFeel::ColorID::ThemedScrollbarThumbInactive
,
377 sScrollbarThumbColor
.ToABGR());
378 } else if (aElementState
.HasAllStates(NS_EVENT_STATE_ACTIVE
)) {
380 LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbActive
,
381 sScrollbarThumbColorActive
.ToABGR());
382 } else if (aElementState
.HasAllStates(NS_EVENT_STATE_HOVER
)) {
384 LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumbHover
,
385 sScrollbarThumbColorHover
.ToABGR());
387 color
= LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarThumb
,
388 sScrollbarThumbColor
.ToABGR());
390 return gfx::sRGBColor::FromABGR(color
);
393 std::array
<sRGBColor
, 3> nsNativeBasicTheme::ComputeScrollbarButtonColors(
394 nsIFrame
* aFrame
, StyleAppearance aAppearance
, const ComputedStyle
& aStyle
,
395 const EventStates
& aElementState
, const EventStates
& aDocumentState
) {
396 bool isActive
= aElementState
.HasState(NS_EVENT_STATE_ACTIVE
);
397 bool isHovered
= aElementState
.HasState(NS_EVENT_STATE_HOVER
);
399 bool hasCustomColor
= aStyle
.StyleUI()->mScrollbarColor
.IsColors();
400 sRGBColor buttonColor
;
401 if (hasCustomColor
) {
402 // When scrollbar-color is in use, use the thumb color for the button.
403 buttonColor
= ComputeScrollbarThumbColor(aFrame
, aStyle
, aElementState
,
405 } else if (isActive
) {
406 buttonColor
= sScrollbarButtonActiveColor
;
407 } else if (!hasCustomColor
&& isHovered
) {
408 buttonColor
= sScrollbarButtonHoverColor
;
410 buttonColor
= sScrollbarColor
;
413 sRGBColor arrowColor
;
414 if (hasCustomColor
) {
415 // When scrollbar-color is in use, derive the arrow color from the button
417 nscolor bg
= buttonColor
.ToABGR();
418 bool darken
= NS_GetLuminosity(bg
) >= NS_MAX_LUMINOSITY
/ 2;
420 float c
= darken
? 0.0f
: 1.0f
;
421 arrowColor
= sRGBColor(c
, c
, c
);
423 uint8_t c
= darken
? 0 : 255;
425 sRGBColor::FromABGR(NS_ComposeColors(bg
, NS_RGBA(c
, c
, c
, 160)));
427 } else if (isActive
) {
428 arrowColor
= sScrollbarArrowColorActive
;
429 } else if (isHovered
) {
430 arrowColor
= sScrollbarArrowColorHover
;
432 arrowColor
= sScrollbarArrowColor
;
435 return {buttonColor
, arrowColor
, sScrollbarBorderColor
};
438 static already_AddRefed
<Path
> GetFocusStrokePath(
439 DrawTarget
* aDrawTarget
, LayoutDeviceRect
& aFocusRect
,
440 LayoutDeviceCoord aOffset
, const LayoutDeviceCoord aRadius
,
441 LayoutDeviceCoord aFocusWidth
) {
442 RectCornerRadii
radii(aRadius
, aRadius
, aRadius
, aRadius
);
443 aFocusRect
.Inflate(aOffset
);
445 LayoutDeviceRect
focusRect(aFocusRect
);
446 // Deflate the rect by half the border width, so that the middle of the
447 // stroke fills exactly the area we want to fill and not more.
448 focusRect
.Deflate(aFocusWidth
* 0.5f
);
450 return MakePathForRoundedRect(*aDrawTarget
, focusRect
.ToUnknownRect(), radii
);
453 void nsNativeBasicTheme::PaintRoundedFocusRect(DrawTarget
* aDrawTarget
,
454 const LayoutDeviceRect
& aRect
,
458 // NOTE(emilio): If the widths or offsets here change, make sure to tweak
459 // the GetWidgetOverflow path for FocusOutline.
460 auto [innerColor
, middleColor
, outerColor
] = ComputeFocusRectColors();
462 LayoutDeviceRect
focusRect(aRect
);
464 // The focus rect is painted outside of the border area (aRect), see:
466 // data:text/html,<div style="border: 1px solid; outline: 2px solid
469 // But some controls might provide a negative offset to cover the border, if
471 LayoutDeviceCoord offset
= aOffset
* aDpiRatio
;
472 LayoutDeviceCoord strokeWidth
= CSSCoord(2.0f
) * aDpiRatio
;
473 focusRect
.Inflate(strokeWidth
);
475 LayoutDeviceCoord strokeRadius
= aRadius
* aDpiRatio
;
476 RefPtr
<Path
> roundedRect
= GetFocusStrokePath(aDrawTarget
, focusRect
, offset
,
477 strokeRadius
, strokeWidth
);
478 aDrawTarget
->Stroke(roundedRect
, ColorPattern(ToDeviceColor(innerColor
)),
479 StrokeOptions(strokeWidth
));
481 offset
= CSSCoord(1.0f
) * aDpiRatio
;
482 strokeRadius
+= offset
;
483 strokeWidth
= CSSCoord(1.0f
) * aDpiRatio
;
484 roundedRect
= GetFocusStrokePath(aDrawTarget
, focusRect
, offset
, strokeRadius
,
486 aDrawTarget
->Stroke(roundedRect
, ColorPattern(ToDeviceColor(middleColor
)),
487 StrokeOptions(strokeWidth
));
489 offset
= CSSCoord(2.0f
) * aDpiRatio
;
490 strokeRadius
+= offset
;
491 strokeWidth
= CSSCoord(2.0f
) * aDpiRatio
;
492 roundedRect
= GetFocusStrokePath(aDrawTarget
, focusRect
, offset
, strokeRadius
,
494 aDrawTarget
->Stroke(roundedRect
, ColorPattern(ToDeviceColor(outerColor
)),
495 StrokeOptions(strokeWidth
));
498 void nsNativeBasicTheme::PaintRoundedRectWithRadius(
499 DrawTarget
* aDrawTarget
, const LayoutDeviceRect
& aRect
,
500 const sRGBColor
& aBackgroundColor
, const sRGBColor
& aBorderColor
,
501 CSSCoord aBorderWidth
, CSSCoord aRadius
, DPIRatio aDpiRatio
) {
502 const LayoutDeviceCoord
borderWidth(SnapBorderWidth(aBorderWidth
, aDpiRatio
));
504 LayoutDeviceRect
rect(aRect
);
505 // Deflate the rect by half the border width, so that the middle of the
506 // stroke fills exactly the area we want to fill and not more.
507 rect
.Deflate(borderWidth
* 0.5f
);
509 LayoutDeviceCoord
radius(aRadius
* aDpiRatio
);
510 // Fix up the radius if it's too large with the rect we're going to paint.
512 LayoutDeviceCoord min
= std::min(rect
.width
, rect
.height
);
513 if (radius
* 2.0f
> min
) {
518 RectCornerRadii
radii(radius
, radius
, radius
, radius
);
519 RefPtr
<Path
> roundedRect
=
520 MakePathForRoundedRect(*aDrawTarget
, rect
.ToUnknownRect(), radii
);
522 aDrawTarget
->Fill(roundedRect
, ColorPattern(ToDeviceColor(aBackgroundColor
)));
523 aDrawTarget
->Stroke(roundedRect
, ColorPattern(ToDeviceColor(aBorderColor
)),
524 StrokeOptions(borderWidth
));
527 void nsNativeBasicTheme::PaintCheckboxControl(DrawTarget
* aDrawTarget
,
528 const LayoutDeviceRect
& aRect
,
529 const EventStates
& aState
,
530 DPIRatio aDpiRatio
) {
531 const CSSCoord radius
= 2.0f
;
532 auto [backgroundColor
, borderColor
] =
533 ComputeCheckboxColors(aState
, StyleAppearance::Checkbox
);
534 PaintRoundedRectWithRadius(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
535 kCheckboxRadioBorderWidth
, radius
, aDpiRatio
);
537 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
538 PaintRoundedFocusRect(aDrawTarget
, aRect
, aDpiRatio
, 5.0f
, 1.0f
);
542 // Returns the right scale to cover aRect in the smaller dimension.
543 static float ScaleToWidgetRect(const LayoutDeviceRect
& aRect
) {
544 return std::min(aRect
.width
, aRect
.height
) / kMinimumWidgetSize
;
547 void nsNativeBasicTheme::PaintCheckMark(DrawTarget
* aDrawTarget
,
548 const LayoutDeviceRect
& aRect
,
549 const EventStates
& aState
) {
550 // Points come from the coordinates on a 14X14 unit box centered at 0,0
551 const float checkPolygonX
[] = {-4.5f
, -1.5f
, -0.5f
, 5.0f
, 4.75f
,
552 3.5f
, -0.5f
, -1.5f
, -3.5f
};
553 const float checkPolygonY
[] = {0.5f
, 4.0f
, 4.0f
, -2.5f
, -4.0f
,
554 -4.0f
, 1.0f
, 1.25f
, -1.0f
};
555 const int32_t checkNumPoints
= sizeof(checkPolygonX
) / sizeof(float);
556 const float scale
= ScaleToWidgetRect(aRect
);
557 auto center
= aRect
.Center().ToUnknownPoint();
559 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
560 Point p
= center
+ Point(checkPolygonX
[0] * scale
, checkPolygonY
[0] * scale
);
562 for (int32_t i
= 1; i
< checkNumPoints
; i
++) {
563 p
= center
+ Point(checkPolygonX
[i
] * scale
, checkPolygonY
[i
] * scale
);
566 RefPtr
<Path
> path
= builder
->Finish();
568 sRGBColor fillColor
= ComputeCheckmarkColor(aState
);
569 aDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(fillColor
)));
572 void nsNativeBasicTheme::PaintIndeterminateMark(DrawTarget
* aDrawTarget
,
573 const LayoutDeviceRect
& aRect
,
574 const EventStates
& aState
) {
575 const CSSCoord borderWidth
= 2.0f
;
576 const float scale
= ScaleToWidgetRect(aRect
);
578 Rect rect
= aRect
.ToUnknownRect();
579 rect
.y
+= (rect
.height
/ 2) - (borderWidth
* scale
/ 2);
580 rect
.height
= borderWidth
* scale
;
581 rect
.x
+= (borderWidth
* scale
) + (borderWidth
* scale
/ 8);
582 rect
.width
-= ((borderWidth
* scale
) + (borderWidth
* scale
/ 8)) * 2;
584 sRGBColor fillColor
= ComputeCheckmarkColor(aState
);
585 aDrawTarget
->FillRect(rect
, ColorPattern(ToDeviceColor(fillColor
)));
588 void nsNativeBasicTheme::PaintStrokedEllipse(DrawTarget
* aDrawTarget
,
589 const LayoutDeviceRect
& aRect
,
590 const sRGBColor
& aBackgroundColor
,
591 const sRGBColor
& aBorderColor
,
592 const CSSCoord aBorderWidth
,
593 DPIRatio aDpiRatio
) {
594 const LayoutDeviceCoord
borderWidth(aBorderWidth
* aDpiRatio
);
595 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
597 // Deflate for the same reason as PaintRoundedRectWithRadius. Note that the
598 // size is the diameter, so we just shrink by the border width once.
599 auto size
= aRect
.Size() - LayoutDeviceSize(borderWidth
, borderWidth
);
600 AppendEllipseToPath(builder
, aRect
.Center().ToUnknownPoint(),
601 size
.ToUnknownSize());
602 RefPtr
<Path
> ellipse
= builder
->Finish();
604 aDrawTarget
->Fill(ellipse
, ColorPattern(ToDeviceColor(aBackgroundColor
)));
605 aDrawTarget
->Stroke(ellipse
, ColorPattern(ToDeviceColor(aBorderColor
)),
606 StrokeOptions(borderWidth
));
609 void nsNativeBasicTheme::PaintEllipseShadow(DrawTarget
* aDrawTarget
,
610 const LayoutDeviceRect
& aRect
,
612 const CSSPoint
& aShadowOffset
,
613 CSSCoord aShadowBlurStdDev
,
614 DPIRatio aDpiRatio
) {
615 Float stdDev
= aShadowBlurStdDev
* aDpiRatio
;
616 Point offset
= (aShadowOffset
* aDpiRatio
).ToUnknownPoint();
618 RefPtr
<FilterNode
> blurFilter
=
619 aDrawTarget
->CreateFilter(FilterType::GAUSSIAN_BLUR
);
624 blurFilter
->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION
, stdDev
);
627 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev
, stdDev
));
628 Rect inflatedRect
= aRect
.ToUnknownRect();
629 inflatedRect
.Inflate(inflation
.width
, inflation
.height
);
630 Rect sourceRectInFilterSpace
=
631 inflatedRect
- aRect
.TopLeft().ToUnknownPoint();
632 Point destinationPointOfSourceRect
= inflatedRect
.TopLeft() + offset
;
634 IntSize dtSize
= RoundedToInt(aRect
.Size().ToUnknownSize());
635 RefPtr
<DrawTarget
> ellipseDT
= aDrawTarget
->CreateSimilarDrawTargetForFilter(
636 dtSize
, SurfaceFormat::A8
, blurFilter
, blurFilter
,
637 sourceRectInFilterSpace
, destinationPointOfSourceRect
);
642 RefPtr
<Path
> ellipse
= MakePathForEllipse(
643 *ellipseDT
, (aRect
- aRect
.TopLeft()).Center().ToUnknownPoint(),
644 aRect
.Size().ToUnknownSize());
645 ellipseDT
->Fill(ellipse
,
646 ColorPattern(DeviceColor(0.0f
, 0.0f
, 0.0f
, aShadowAlpha
)));
647 RefPtr
<SourceSurface
> ellipseSurface
= ellipseDT
->Snapshot();
649 blurFilter
->SetInput(IN_GAUSSIAN_BLUR_IN
, ellipseSurface
);
650 aDrawTarget
->DrawFilter(blurFilter
, sourceRectInFilterSpace
,
651 destinationPointOfSourceRect
);
654 void nsNativeBasicTheme::PaintRadioControl(DrawTarget
* aDrawTarget
,
655 const LayoutDeviceRect
& aRect
,
656 const EventStates
& aState
,
657 DPIRatio aDpiRatio
) {
658 const CSSCoord borderWidth
= 2.0f
;
659 auto [backgroundColor
, borderColor
] =
660 ComputeCheckboxColors(aState
, StyleAppearance::Radio
);
662 PaintStrokedEllipse(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
663 borderWidth
, aDpiRatio
);
665 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
666 PaintRoundedFocusRect(aDrawTarget
, aRect
, aDpiRatio
, 5.0f
, 1.0f
);
670 void nsNativeBasicTheme::PaintRadioCheckmark(DrawTarget
* aDrawTarget
,
671 const LayoutDeviceRect
& aRect
,
672 const EventStates
& aState
,
673 DPIRatio aDpiRatio
) {
674 const CSSCoord borderWidth
= 2.0f
;
675 const float scale
= ScaleToWidgetRect(aRect
);
676 auto [backgroundColor
, checkColor
] = ComputeRadioCheckmarkColors(aState
);
678 LayoutDeviceRect
rect(aRect
);
679 rect
.Deflate(borderWidth
* scale
);
681 PaintStrokedEllipse(aDrawTarget
, rect
, checkColor
, backgroundColor
,
682 borderWidth
, aDpiRatio
);
685 void nsNativeBasicTheme::PaintTextField(DrawTarget
* aDrawTarget
,
686 const LayoutDeviceRect
& aRect
,
687 const EventStates
& aState
,
688 DPIRatio aDpiRatio
) {
689 auto [backgroundColor
, borderColor
] = ComputeTextfieldColors(aState
);
691 const CSSCoord radius
= 2.0f
;
693 PaintRoundedRectWithRadius(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
694 kTextFieldBorderWidth
, radius
, aDpiRatio
);
696 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
697 PaintRoundedFocusRect(aDrawTarget
, aRect
, aDpiRatio
, radius
,
698 -kTextFieldBorderWidth
);
702 void nsNativeBasicTheme::PaintListbox(DrawTarget
* aDrawTarget
,
703 const LayoutDeviceRect
& aRect
,
704 const EventStates
& aState
,
705 DPIRatio aDpiRatio
) {
706 const CSSCoord radius
= 2.0f
;
707 auto [backgroundColor
, borderColor
] = ComputeTextfieldColors(aState
);
709 PaintRoundedRectWithRadius(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
710 kMenulistBorderWidth
, radius
, aDpiRatio
);
712 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
713 PaintRoundedFocusRect(aDrawTarget
, aRect
, aDpiRatio
, radius
,
714 -kMenulistBorderWidth
);
718 void nsNativeBasicTheme::PaintMenulist(DrawTarget
* aDrawTarget
,
719 const LayoutDeviceRect
& aRect
,
720 const EventStates
& aState
,
721 DPIRatio aDpiRatio
) {
722 const CSSCoord radius
= 4.0f
;
723 auto [backgroundColor
, borderColor
] = ComputeButtonColors(aState
);
725 PaintRoundedRectWithRadius(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
726 kMenulistBorderWidth
, radius
, aDpiRatio
);
728 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
729 PaintRoundedFocusRect(aDrawTarget
, aRect
, aDpiRatio
, radius
,
730 -kMenulistBorderWidth
);
734 void nsNativeBasicTheme::PaintArrow(DrawTarget
* aDrawTarget
,
735 const LayoutDeviceRect
& aRect
,
736 const float aArrowPolygonX
[],
737 const float aArrowPolygonY
[],
738 const int32_t aArrowNumPoints
,
739 const sRGBColor aFillColor
) {
740 const float scale
= ScaleToWidgetRect(aRect
);
742 auto center
= aRect
.Center().ToUnknownPoint();
744 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
746 center
+ Point(aArrowPolygonX
[0] * scale
, aArrowPolygonY
[0] * scale
);
748 for (int32_t i
= 1; i
< aArrowNumPoints
; i
++) {
749 p
= center
+ Point(aArrowPolygonX
[i
] * scale
, aArrowPolygonY
[i
] * scale
);
752 RefPtr
<Path
> path
= builder
->Finish();
754 aDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(aFillColor
)));
757 void nsNativeBasicTheme::PaintMenulistArrowButton(nsIFrame
* aFrame
,
758 DrawTarget
* aDrawTarget
,
759 const LayoutDeviceRect
& aRect
,
760 const EventStates
& aState
) {
761 const float arrowPolygonX
[] = {-3.5f
, -0.5f
, 0.5f
, 3.5f
, 3.5f
,
762 3.0f
, 0.5f
, -0.5f
, -3.0f
, -3.5f
};
763 const float arrowPolygonY
[] = {-0.5f
, 2.5f
, 2.5f
, -0.5f
, -2.0f
,
764 -2.0f
, 1.0f
, 1.0f
, -2.0f
, -2.0f
};
765 const int32_t arrowNumPoints
= ArrayLength(arrowPolygonX
);
766 sRGBColor arrowColor
= ComputeMenulistArrowButtonColor(aState
);
767 PaintArrow(aDrawTarget
, aRect
, arrowPolygonX
, arrowPolygonY
, arrowNumPoints
,
771 void nsNativeBasicTheme::PaintSpinnerButton(nsIFrame
* aFrame
,
772 DrawTarget
* aDrawTarget
,
773 const LayoutDeviceRect
& aRect
,
774 const EventStates
& aState
,
775 StyleAppearance aAppearance
,
776 DPIRatio aDpiRatio
) {
777 auto [backgroundColor
, borderColor
] = ComputeButtonColors(aState
);
779 aDrawTarget
->FillRect(aRect
.ToUnknownRect(),
780 ColorPattern(ToDeviceColor(backgroundColor
)));
782 const float arrowPolygonX
[] = {-5.25f
, -0.75f
, 0.75f
, 5.25f
, 5.25f
,
783 4.5f
, 0.75f
, -0.75f
, -4.5f
, -5.25f
};
784 const float arrowPolygonY
[] = {-1.875f
, 2.625f
, 2.625f
, -1.875f
, -4.125f
,
785 -4.125f
, 0.375f
, 0.375f
, -4.125f
, -4.125f
};
786 const int32_t arrowNumPoints
= ArrayLength(arrowPolygonX
);
787 const float scaleX
= ScaleToWidgetRect(aRect
);
789 aAppearance
== StyleAppearance::SpinnerDownbutton
? scaleX
: -scaleX
;
791 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
792 auto center
= aRect
.Center().ToUnknownPoint();
794 center
+ Point(arrowPolygonX
[0] * scaleX
, arrowPolygonY
[0] * scaleY
);
796 for (int32_t i
= 1; i
< arrowNumPoints
; i
++) {
797 p
= center
+ Point(arrowPolygonX
[i
] * scaleX
, arrowPolygonY
[i
] * scaleY
);
800 RefPtr
<Path
> path
= builder
->Finish();
801 aDrawTarget
->Fill(path
, ColorPattern(ToDeviceColor(borderColor
)));
804 void nsNativeBasicTheme::PaintRange(nsIFrame
* aFrame
, DrawTarget
* aDrawTarget
,
805 const LayoutDeviceRect
& aRect
,
806 const EventStates
& aState
,
807 DPIRatio aDpiRatio
, bool aHorizontal
) {
808 nsRangeFrame
* rangeFrame
= do_QueryFrame(aFrame
);
813 double progress
= rangeFrame
->GetValueAsFractionOfRange();
815 LayoutDeviceRect
thumbRect(0, 0, kMinimumRangeThumbSize
* aDpiRatio
,
816 kMinimumRangeThumbSize
* aDpiRatio
);
817 Rect overflowRect
= aRect
.ToUnknownRect();
818 overflowRect
.Inflate(CSSCoord(6.0f
) * aDpiRatio
); // See GetWidgetOverflow
819 Rect
progressClipRect(overflowRect
);
820 Rect
trackClipRect(overflowRect
);
821 const LayoutDeviceCoord verticalSize
= kRangeHeight
* aDpiRatio
;
823 rect
.height
= verticalSize
;
824 rect
.y
= aRect
.y
+ (aRect
.height
- rect
.height
) / 2;
825 thumbRect
.y
= aRect
.y
+ (aRect
.height
- thumbRect
.height
) / 2;
827 if (IsFrameRTL(aFrame
)) {
829 aRect
.x
+ (aRect
.width
- thumbRect
.width
) * (1.0 - progress
);
830 float midPoint
= thumbRect
.Center().X();
831 trackClipRect
.SetBoxX(overflowRect
.X(), midPoint
);
832 progressClipRect
.SetBoxX(midPoint
, overflowRect
.XMost());
834 thumbRect
.x
= aRect
.x
+ (aRect
.width
- thumbRect
.width
) * progress
;
835 float midPoint
= thumbRect
.Center().X();
836 progressClipRect
.SetBoxX(overflowRect
.X(), midPoint
);
837 trackClipRect
.SetBoxX(midPoint
, overflowRect
.XMost());
840 rect
.width
= verticalSize
;
841 rect
.x
= aRect
.x
+ (aRect
.width
- rect
.width
) / 2;
842 thumbRect
.x
= aRect
.x
+ (aRect
.width
- thumbRect
.width
) / 2;
844 thumbRect
.y
= aRect
.y
+ (aRect
.height
- thumbRect
.height
) * progress
;
845 float midPoint
= thumbRect
.Center().Y();
846 trackClipRect
.SetBoxY(overflowRect
.Y(), midPoint
);
847 progressClipRect
.SetBoxY(midPoint
, overflowRect
.YMost());
850 const CSSCoord borderWidth
= 1.0f
;
851 const CSSCoord radius
= 2.0f
;
853 auto [progressColor
, progressBorderColor
] =
854 ComputeRangeProgressColors(aState
);
855 auto [trackColor
, trackBorderColor
] = ComputeRangeTrackColors(aState
);
857 // Make a path that clips out the range thumb.
858 RefPtr
<PathBuilder
> builder
=
859 aDrawTarget
->CreatePathBuilder(FillRule::FILL_EVEN_ODD
);
860 AppendRectToPath(builder
, overflowRect
);
861 AppendEllipseToPath(builder
, thumbRect
.Center().ToUnknownPoint(),
862 thumbRect
.Size().ToUnknownSize());
863 RefPtr
<Path
> path
= builder
->Finish();
865 // Draw the progress and track pieces with the thumb clipped out, so that
866 // they're not visible behind the thumb even if the thumb is partially
867 // transparent (which is the case in the disabled state).
868 aDrawTarget
->PushClip(path
);
870 aDrawTarget
->PushClipRect(progressClipRect
);
871 PaintRoundedRectWithRadius(aDrawTarget
, rect
, progressColor
,
872 progressBorderColor
, borderWidth
, radius
,
874 aDrawTarget
->PopClip();
876 aDrawTarget
->PushClipRect(trackClipRect
);
877 PaintRoundedRectWithRadius(aDrawTarget
, rect
, trackColor
, trackBorderColor
,
878 borderWidth
, radius
, aDpiRatio
);
879 aDrawTarget
->PopClip();
881 if (!aState
.HasState(NS_EVENT_STATE_DISABLED
)) {
883 PaintEllipseShadow(aDrawTarget
, thumbRect
, 0.3f
, CSSPoint(0.0f
, 2.0f
),
887 aDrawTarget
->PopClip();
889 // Draw the thumb on top.
890 const CSSCoord thumbBorderWidth
= 2.0f
;
891 auto [thumbColor
, thumbBorderColor
] = ComputeRangeThumbColors(aState
);
893 PaintStrokedEllipse(aDrawTarget
, thumbRect
, thumbColor
, thumbBorderColor
,
894 thumbBorderWidth
, aDpiRatio
);
896 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
897 PaintRoundedFocusRect(aDrawTarget
, aRect
, aDpiRatio
, radius
, 1.0f
);
902 // TODO: Indeterminate state.
903 void nsNativeBasicTheme::PaintProgress(
904 nsIFrame
* aFrame
, DrawTarget
* aDrawTarget
, const LayoutDeviceRect
& aRect
,
905 const EventStates
& aState
, DPIRatio aDpiRatio
, bool aIsMeter
, bool aBar
) {
906 auto [backgroundColor
, borderColor
] = [&] {
908 return aBar
? ComputeMeterTrackColors() : ComputeMeterchunkColors(aState
);
910 return aBar
? ComputeProgressTrackColors() : ComputeProgressColors();
913 const CSSCoord borderWidth
= 1.0f
;
914 const CSSCoord radius
= aIsMeter
? 5.0f
: 2.0f
;
916 // Center it vertically.
917 LayoutDeviceRect
rect(aRect
);
918 const LayoutDeviceCoord height
=
919 (aIsMeter
? kMeterHeight
: kProgressbarHeight
) * aDpiRatio
;
920 rect
.y
+= (rect
.height
- height
) / 2;
921 rect
.height
= height
;
923 // This is the progress chunk, clip it to the right amount.
925 double position
= [&] {
927 auto* meter
= dom::HTMLMeterElement::FromNode(aFrame
->GetContent());
931 return meter
->Value() / meter
->Max();
933 auto* progress
= dom::HTMLProgressElement::FromNode(aFrame
->GetContent());
937 return progress
->Value() / progress
->Max();
939 LayoutDeviceRect clipRect
= rect
;
940 double clipWidth
= rect
.width
* position
;
941 clipRect
.width
= clipWidth
;
942 if (IsFrameRTL(aFrame
)) {
943 clipRect
.x
+= rect
.width
- clipWidth
;
945 aDrawTarget
->PushClipRect(clipRect
.ToUnknownRect());
948 PaintRoundedRectWithRadius(aDrawTarget
, rect
, backgroundColor
, borderColor
,
949 borderWidth
, radius
, aDpiRatio
);
952 aDrawTarget
->PopClip();
956 void nsNativeBasicTheme::PaintButton(nsIFrame
* aFrame
, DrawTarget
* aDrawTarget
,
957 const LayoutDeviceRect
& aRect
,
958 const EventStates
& aState
,
959 DPIRatio aDpiRatio
) {
960 const CSSCoord radius
= 4.0f
;
961 auto [backgroundColor
, borderColor
] = ComputeButtonColors(aState
, aFrame
);
963 PaintRoundedRectWithRadius(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
964 kButtonBorderWidth
, radius
, aDpiRatio
);
966 if (aState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
967 PaintRoundedFocusRect(aDrawTarget
, aRect
, aDpiRatio
, radius
,
968 -kButtonBorderWidth
);
972 void nsNativeBasicTheme::PaintScrollbarThumb(DrawTarget
* aDrawTarget
,
973 const LayoutDeviceRect
& aRect
,
974 bool aHorizontal
, nsIFrame
* aFrame
,
975 const ComputedStyle
& aStyle
,
976 const EventStates
& aElementState
,
977 const EventStates
& aDocumentState
,
978 DPIRatio aDpiRatio
) {
979 sRGBColor thumbColor
=
980 ComputeScrollbarThumbColor(aFrame
, aStyle
, aElementState
, aDocumentState
);
982 aDrawTarget
->FillRect(aRect
.ToUnknownRect(),
983 ColorPattern(ToDeviceColor(thumbColor
)));
986 void nsNativeBasicTheme::PaintScrollbarTrack(DrawTarget
* aDrawTarget
,
987 const LayoutDeviceRect
& aRect
,
988 bool aHorizontal
, nsIFrame
* aFrame
,
989 const ComputedStyle
& aStyle
,
990 const EventStates
& aDocumentState
,
991 DPIRatio aDpiRatio
, bool aIsRoot
) {
992 // Draw nothing by default. Subclasses can override this.
995 void nsNativeBasicTheme::PaintScrollbar(DrawTarget
* aDrawTarget
,
996 const LayoutDeviceRect
& aRect
,
997 bool aHorizontal
, nsIFrame
* aFrame
,
998 const ComputedStyle
& aStyle
,
999 const EventStates
& aDocumentState
,
1000 DPIRatio aDpiRatio
, bool aIsRoot
) {
1001 auto [scrollbarColor
, borderColor
] =
1002 ComputeScrollbarColors(aFrame
, aStyle
, aDocumentState
, aIsRoot
);
1003 aDrawTarget
->FillRect(aRect
.ToUnknownRect(),
1004 ColorPattern(ToDeviceColor(scrollbarColor
)));
1005 // FIXME(heycam): We should probably derive the border color when custom
1006 // scrollbar colors are in use too. But for now, just skip painting it,
1007 // to avoid ugliness.
1008 if (aStyle
.StyleUI()->mScrollbarColor
.IsAuto()) {
1009 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
1010 LayoutDeviceRect
strokeRect(aRect
);
1011 strokeRect
.Deflate(CSSCoord(0.5f
) * aDpiRatio
);
1012 builder
->MoveTo(strokeRect
.TopLeft().ToUnknownPoint());
1014 (aHorizontal
? strokeRect
.TopRight() : strokeRect
.BottomLeft())
1016 RefPtr
<Path
> path
= builder
->Finish();
1017 aDrawTarget
->Stroke(path
, ColorPattern(ToDeviceColor(borderColor
)),
1018 StrokeOptions(CSSCoord(1.0f
) * aDpiRatio
));
1022 void nsNativeBasicTheme::PaintScrollCorner(DrawTarget
* aDrawTarget
,
1023 const LayoutDeviceRect
& aRect
,
1025 const ComputedStyle
& aStyle
,
1026 const EventStates
& aDocumentState
,
1027 DPIRatio aDpiRatio
, bool aIsRoot
) {
1028 auto [scrollbarColor
, borderColor
] =
1029 ComputeScrollbarColors(aFrame
, aStyle
, aDocumentState
, aIsRoot
);
1030 Unused
<< borderColor
;
1031 aDrawTarget
->FillRect(aRect
.ToUnknownRect(),
1032 ColorPattern(ToDeviceColor(scrollbarColor
)));
1035 void nsNativeBasicTheme::PaintScrollbarButton(
1036 DrawTarget
* aDrawTarget
, StyleAppearance aAppearance
,
1037 const LayoutDeviceRect
& aRect
, nsIFrame
* aFrame
,
1038 const ComputedStyle
& aStyle
, const EventStates
& aElementState
,
1039 const EventStates
& aDocumentState
, DPIRatio aDpiRatio
) {
1040 bool hasCustomColor
= aStyle
.StyleUI()->mScrollbarColor
.IsColors();
1041 auto [buttonColor
, arrowColor
, borderColor
] = ComputeScrollbarButtonColors(
1042 aFrame
, aAppearance
, aStyle
, aElementState
, aDocumentState
);
1043 aDrawTarget
->FillRect(aRect
.ToUnknownRect(),
1044 ColorPattern(ToDeviceColor(buttonColor
)));
1046 // Start with Up arrow.
1047 float arrowPolygonX
[] = {-3.0f
, 0.0f
, 3.0f
, 3.0f
, 0.0f
, -3.0f
};
1048 float arrowPolygonY
[] = {0.4f
, -3.1f
, 0.4f
, 2.7f
, -0.8f
, 2.7f
};
1050 const int32_t arrowNumPoints
= ArrayLength(arrowPolygonX
);
1051 switch (aAppearance
) {
1052 case StyleAppearance::ScrollbarbuttonUp
:
1054 case StyleAppearance::ScrollbarbuttonDown
:
1055 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
1056 arrowPolygonY
[i
] *= -1;
1059 case StyleAppearance::ScrollbarbuttonLeft
:
1060 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
1061 int32_t temp
= arrowPolygonX
[i
];
1062 arrowPolygonX
[i
] = arrowPolygonY
[i
];
1063 arrowPolygonY
[i
] = temp
;
1066 case StyleAppearance::ScrollbarbuttonRight
:
1067 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
1068 int32_t temp
= arrowPolygonX
[i
];
1069 arrowPolygonX
[i
] = arrowPolygonY
[i
] * -1;
1070 arrowPolygonY
[i
] = temp
;
1076 PaintArrow(aDrawTarget
, aRect
, arrowPolygonX
, arrowPolygonY
, arrowNumPoints
,
1079 // FIXME(heycam): We should probably derive the border color when custom
1080 // scrollbar colors are in use too. But for now, just skip painting it,
1081 // to avoid ugliness.
1082 if (!hasCustomColor
) {
1083 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
1084 builder
->MoveTo(Point(aRect
.x
, aRect
.y
));
1085 if (aAppearance
== StyleAppearance::ScrollbarbuttonUp
||
1086 aAppearance
== StyleAppearance::ScrollbarbuttonDown
) {
1087 builder
->LineTo(Point(aRect
.x
, aRect
.y
+ aRect
.height
));
1089 builder
->LineTo(Point(aRect
.x
+ aRect
.width
, aRect
.y
));
1092 RefPtr
<Path
> path
= builder
->Finish();
1093 aDrawTarget
->Stroke(path
, ColorPattern(ToDeviceColor(borderColor
)),
1094 StrokeOptions(CSSCoord(1.0f
) * aDpiRatio
));
1098 // Checks whether the frame is for a root <scrollbar> or <scrollcorner>, which
1099 // influences some platforms' scrollbar rendering.
1100 bool nsNativeBasicTheme::IsRootScrollbar(nsIFrame
* aFrame
) {
1101 return CheckBooleanAttr(aFrame
, nsGkAtoms::root_
) &&
1102 aFrame
->PresContext()->IsRootContentDocument() &&
1103 aFrame
->GetContent() &&
1104 aFrame
->GetContent()->IsInNamespace(kNameSpaceID_XUL
);
1108 nsNativeBasicTheme::DrawWidgetBackground(gfxContext
* aContext
, nsIFrame
* aFrame
,
1109 StyleAppearance aAppearance
,
1110 const nsRect
& aRect
,
1111 const nsRect
& /* aDirtyRect */) {
1112 DrawTarget
* dt
= aContext
->GetDrawTarget();
1113 const nscoord twipsPerPixel
= aFrame
->PresContext()->AppUnitsPerDevPixel();
1114 EventStates eventState
= GetContentState(aFrame
, aAppearance
);
1115 EventStates docState
= aFrame
->GetContent()->OwnerDoc()->GetDocumentState();
1116 auto devPxRect
= LayoutDeviceRect::FromUnknownRect(
1117 NSRectToSnappedRect(aRect
, twipsPerPixel
, *dt
));
1119 if (aAppearance
== StyleAppearance::MozMenulistArrowButton
) {
1120 bool isHTML
= IsHTMLContent(aFrame
);
1121 nsIFrame
* parentFrame
= aFrame
->GetParent();
1122 bool isMenulist
= !isHTML
&& parentFrame
->IsMenuFrame();
1123 // HTML select and XUL menulist dropdown buttons get state from the
1125 if (isHTML
|| isMenulist
) {
1126 aFrame
= parentFrame
;
1127 eventState
= GetContentState(parentFrame
, aAppearance
);
1131 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1132 // overflow devPxRect.
1133 Maybe
<AutoClipRect
> maybeClipRect
;
1134 if (aAppearance
!= StyleAppearance::FocusOutline
&&
1135 aAppearance
!= StyleAppearance::Range
&&
1136 !eventState
.HasState(NS_EVENT_STATE_FOCUSRING
)) {
1137 maybeClipRect
.emplace(*dt
, devPxRect
);
1140 DPIRatio dpiRatio
= GetDPIRatio(aFrame
, aAppearance
);
1142 switch (aAppearance
) {
1143 case StyleAppearance::Radio
: {
1144 auto rect
= FixAspectRatio(devPxRect
);
1145 PaintRadioControl(dt
, rect
, eventState
, dpiRatio
);
1146 if (IsSelected(aFrame
)) {
1147 PaintRadioCheckmark(dt
, rect
, eventState
, dpiRatio
);
1151 case StyleAppearance::Checkbox
: {
1152 auto rect
= FixAspectRatio(devPxRect
);
1153 PaintCheckboxControl(dt
, rect
, eventState
, dpiRatio
);
1154 if (GetIndeterminate(aFrame
)) {
1155 PaintIndeterminateMark(dt
, rect
, eventState
);
1156 } else if (IsChecked(aFrame
)) {
1157 PaintCheckMark(dt
, rect
, eventState
);
1161 case StyleAppearance::Textarea
:
1162 case StyleAppearance::Textfield
:
1163 case StyleAppearance::NumberInput
:
1164 PaintTextField(dt
, devPxRect
, eventState
, dpiRatio
);
1166 case StyleAppearance::Listbox
:
1167 PaintListbox(dt
, devPxRect
, eventState
, dpiRatio
);
1169 case StyleAppearance::MenulistButton
:
1170 case StyleAppearance::Menulist
:
1171 PaintMenulist(dt
, devPxRect
, eventState
, dpiRatio
);
1173 case StyleAppearance::MozMenulistArrowButton
:
1174 PaintMenulistArrowButton(aFrame
, dt
, devPxRect
, eventState
);
1176 case StyleAppearance::SpinnerUpbutton
:
1177 case StyleAppearance::SpinnerDownbutton
:
1178 PaintSpinnerButton(aFrame
, dt
, devPxRect
, eventState
, aAppearance
,
1181 case StyleAppearance::Range
:
1182 PaintRange(aFrame
, dt
, devPxRect
, eventState
, dpiRatio
,
1183 IsRangeHorizontal(aFrame
));
1185 case StyleAppearance::RangeThumb
:
1186 // Painted as part of StyleAppearance::Range.
1188 case StyleAppearance::ProgressBar
:
1189 PaintProgress(aFrame
, dt
, devPxRect
, eventState
, dpiRatio
,
1190 /* aMeter = */ false, /* aBar = */ true);
1192 case StyleAppearance::Progresschunk
:
1193 if (nsProgressFrame
* f
= do_QueryFrame(aFrame
->GetParent())) {
1194 PaintProgress(f
, dt
, devPxRect
, f
->GetContent()->AsElement()->State(),
1195 dpiRatio
, /* aMeter = */ false, /* aBar = */ false);
1198 case StyleAppearance::Meter
:
1199 PaintProgress(aFrame
, dt
, devPxRect
, eventState
, dpiRatio
,
1200 /* aMeter = */ true, /* aBar = */ true);
1202 case StyleAppearance::Meterchunk
:
1203 if (nsMeterFrame
* f
= do_QueryFrame(aFrame
->GetParent())) {
1204 PaintProgress(f
, dt
, devPxRect
, f
->GetContent()->AsElement()->State(),
1205 dpiRatio
, /* aMeter = */ true, /* aBar = */ false);
1208 case StyleAppearance::ScrollbarthumbHorizontal
:
1209 case StyleAppearance::ScrollbarthumbVertical
: {
1211 aAppearance
== StyleAppearance::ScrollbarthumbHorizontal
;
1212 PaintScrollbarThumb(dt
, devPxRect
, isHorizontal
, aFrame
,
1213 *nsLayoutUtils::StyleForScrollbar(aFrame
), eventState
,
1214 docState
, dpiRatio
);
1217 case StyleAppearance::ScrollbartrackHorizontal
:
1218 case StyleAppearance::ScrollbartrackVertical
: {
1220 aAppearance
== StyleAppearance::ScrollbartrackHorizontal
;
1221 PaintScrollbarTrack(dt
, devPxRect
, isHorizontal
, aFrame
,
1222 *nsLayoutUtils::StyleForScrollbar(aFrame
), docState
,
1223 dpiRatio
, IsRootScrollbar(aFrame
));
1226 case StyleAppearance::ScrollbarHorizontal
:
1227 case StyleAppearance::ScrollbarVertical
: {
1228 bool isHorizontal
= aAppearance
== StyleAppearance::ScrollbarHorizontal
;
1229 PaintScrollbar(dt
, devPxRect
, isHorizontal
, aFrame
,
1230 *nsLayoutUtils::StyleForScrollbar(aFrame
), docState
,
1231 dpiRatio
, IsRootScrollbar(aFrame
));
1234 case StyleAppearance::Scrollcorner
:
1235 PaintScrollCorner(dt
, devPxRect
, aFrame
,
1236 *nsLayoutUtils::StyleForScrollbar(aFrame
), docState
,
1237 dpiRatio
, IsRootScrollbar(aFrame
));
1239 case StyleAppearance::ScrollbarbuttonUp
:
1240 case StyleAppearance::ScrollbarbuttonDown
:
1241 case StyleAppearance::ScrollbarbuttonLeft
:
1242 case StyleAppearance::ScrollbarbuttonRight
:
1243 // For scrollbar-width:thin, we don't display the buttons.
1244 if (!IsScrollbarWidthThin(aFrame
)) {
1245 PaintScrollbarButton(dt
, aAppearance
, devPxRect
, aFrame
,
1246 *nsLayoutUtils::StyleForScrollbar(aFrame
),
1247 eventState
, docState
, dpiRatio
);
1250 case StyleAppearance::Button
:
1251 PaintButton(aFrame
, dt
, devPxRect
, eventState
, dpiRatio
);
1253 case StyleAppearance::FocusOutline
:
1254 // TODO(emilio): Consider supporting outline-radius / outline-offset?
1255 PaintRoundedFocusRect(dt
, devPxRect
, dpiRatio
, 0.0f
, 0.0f
);
1258 // Various appearance values are used for XUL elements. Normally these
1259 // will not be available in content documents (and thus in the content
1260 // processes where the native basic theme can be used), but tests are
1261 // run with the remote XUL pref enabled and so we can get in here. So
1262 // we just return an error rather than assert.
1263 return NS_ERROR_NOT_IMPLEMENTED
;
1270 nsNativeBasicTheme::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
1271 aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
1272 mozilla::layers::StackingContextHelper& aSc,
1273 mozilla::layers::RenderRootStateManager*
1274 aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect)
1278 LayoutDeviceIntMargin
nsNativeBasicTheme::GetWidgetBorder(
1279 nsDeviceContext
* aContext
, nsIFrame
* aFrame
, StyleAppearance aAppearance
) {
1280 switch (aAppearance
) {
1281 case StyleAppearance::Textfield
:
1282 case StyleAppearance::Textarea
:
1283 case StyleAppearance::NumberInput
:
1284 case StyleAppearance::Listbox
:
1285 case StyleAppearance::Menulist
:
1286 case StyleAppearance::MenulistButton
:
1287 case StyleAppearance::Button
:
1288 // Return the border size from the UA sheet, even though what we paint
1289 // doesn't actually match that. We know this is the UA sheet border
1290 // because we disable native theming when different border widths are
1291 // specified by authors, see nsNativeBasicTheme::IsWidgetStyled.
1293 // The Rounded() bit is technically redundant, but needed to appease the
1294 // type system, we should always end up with full device pixels due to
1295 // round_border_to_device_pixels at style time.
1296 return LayoutDeviceIntMargin::FromAppUnits(
1297 aFrame
->StyleBorder()->GetComputedBorder(),
1298 aFrame
->PresContext()->AppUnitsPerDevPixel())
1300 case StyleAppearance::Checkbox
:
1301 case StyleAppearance::Radio
: {
1302 DPIRatio dpiRatio
= GetDPIRatio(aFrame
, aAppearance
);
1303 LayoutDeviceIntCoord w
=
1304 SnapBorderWidth(kCheckboxRadioBorderWidth
, dpiRatio
);
1305 return LayoutDeviceIntMargin(w
, w
, w
, w
);
1308 return LayoutDeviceIntMargin();
1312 bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext
* aContext
,
1314 StyleAppearance aAppearance
,
1315 LayoutDeviceIntMargin
* aResult
) {
1316 switch (aAppearance
) {
1317 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1318 // and have a meaningful baseline, so they can't have
1319 // author-specified padding.
1320 case StyleAppearance::Radio
:
1321 case StyleAppearance::Checkbox
:
1322 aResult
->SizeTo(0, 0, 0, 0);
1330 static int GetScrollbarButtonCount() {
1331 int32_t buttons
= LookAndFeel::GetInt(LookAndFeel::IntID::ScrollArrowStyle
);
1332 return CountPopulation32(static_cast<uint32_t>(buttons
));
1335 bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext
* aContext
,
1337 StyleAppearance aAppearance
,
1338 nsRect
* aOverflowRect
) {
1339 nsIntMargin overflow
;
1340 switch (aAppearance
) {
1341 case StyleAppearance::FocusOutline
:
1342 // 2px * each of the segments + 1 px for the separation between them.
1343 overflow
.SizeTo(5, 5, 5, 5);
1345 case StyleAppearance::Radio
:
1346 case StyleAppearance::Checkbox
:
1347 case StyleAppearance::Range
:
1348 overflow
.SizeTo(6, 6, 6, 6);
1350 case StyleAppearance::Textarea
:
1351 case StyleAppearance::Textfield
:
1352 case StyleAppearance::NumberInput
:
1353 case StyleAppearance::Listbox
:
1354 case StyleAppearance::MenulistButton
:
1355 case StyleAppearance::Menulist
:
1356 case StyleAppearance::Button
:
1357 // 2px for each segment, plus 1px separation, but we paint 1px inside
1358 // the border area so 4px overflow.
1359 overflow
.SizeTo(4, 4, 4, 4);
1365 // TODO: This should convert from device pixels to app units, not from CSS
1366 // pixels. And it should take the dpi ratio into account.
1367 // Using CSS pixels can cause the overflow to be too small if the page is
1369 aOverflowRect
->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow
.top
),
1370 CSSPixel::ToAppUnits(overflow
.right
),
1371 CSSPixel::ToAppUnits(overflow
.bottom
),
1372 CSSPixel::ToAppUnits(overflow
.left
)));
1377 auto nsNativeBasicTheme::GetScrollbarSizes(nsPresContext
* aPresContext
,
1378 StyleScrollbarWidth aWidth
, Overlay
)
1380 CSSCoord size
= aWidth
== StyleScrollbarWidth::Thin
1381 ? kMinimumThinScrollbarSize
1382 : kMinimumScrollbarSize
;
1383 LayoutDeviceIntCoord s
=
1384 (size
* GetDPIRatioForScrollbarPart(aPresContext
)).Rounded();
1389 nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext
* aPresContext
,
1391 StyleAppearance aAppearance
,
1392 LayoutDeviceIntSize
* aResult
,
1393 bool* aIsOverridable
) {
1394 DPIRatio dpiRatio
= GetDPIRatio(aFrame
, aAppearance
);
1396 aResult
->width
= aResult
->height
= (kMinimumWidgetSize
* dpiRatio
).Rounded();
1398 switch (aAppearance
) {
1399 case StyleAppearance::Button
:
1400 if (IsColorPickerButton(aFrame
)) {
1401 aResult
->height
= (kMinimumColorPickerHeight
* dpiRatio
).Rounded();
1404 case StyleAppearance::RangeThumb
:
1405 aResult
->SizeTo((kMinimumRangeThumbSize
* dpiRatio
).Rounded(),
1406 (kMinimumRangeThumbSize
* dpiRatio
).Rounded());
1408 case StyleAppearance::MozMenulistArrowButton
:
1409 aResult
->width
= (kMinimumDropdownArrowButtonWidth
* dpiRatio
).Rounded();
1411 case StyleAppearance::SpinnerUpbutton
:
1412 case StyleAppearance::SpinnerDownbutton
:
1413 aResult
->width
= (kMinimumSpinnerButtonWidth
* dpiRatio
).Rounded();
1414 aResult
->height
= (kMinimumSpinnerButtonHeight
* dpiRatio
).Rounded();
1416 case StyleAppearance::ScrollbarbuttonUp
:
1417 case StyleAppearance::ScrollbarbuttonDown
:
1418 case StyleAppearance::ScrollbarbuttonLeft
:
1419 case StyleAppearance::ScrollbarbuttonRight
:
1420 // For scrollbar-width:thin, we don't display the buttons.
1421 if (IsScrollbarWidthThin(aFrame
)) {
1422 aResult
->SizeTo(0, 0);
1426 case StyleAppearance::ScrollbarthumbVertical
:
1427 case StyleAppearance::ScrollbarthumbHorizontal
:
1428 case StyleAppearance::ScrollbarVertical
:
1429 case StyleAppearance::ScrollbarHorizontal
:
1430 case StyleAppearance::ScrollbartrackHorizontal
:
1431 case StyleAppearance::ScrollbartrackVertical
:
1432 case StyleAppearance::Scrollcorner
: {
1433 auto* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
1434 auto width
= style
->StyleUIReset()->mScrollbarWidth
;
1435 auto sizes
= GetScrollbarSizes(aPresContext
, width
, Overlay::No
);
1436 MOZ_ASSERT(sizes
.mHorizontal
== sizes
.mVertical
);
1437 aResult
->SizeTo(sizes
.mHorizontal
, sizes
.mHorizontal
);
1438 if (width
!= StyleScrollbarWidth::Thin
) {
1439 // If the scrollbar has any buttons, then we increase the minimum
1440 // size so that they fit too.
1442 // FIXME(heycam): We should probably ensure that the thumb disappears
1443 // if a scrollbar is big enough to fit the buttons but not the thumb,
1444 // which is what the Windows native theme does. If we do that, then
1445 // the minimum size here needs to be reduced accordingly.
1446 switch (aAppearance
) {
1447 case StyleAppearance::ScrollbarHorizontal
:
1448 aResult
->width
*= GetScrollbarButtonCount() + 1;
1450 case StyleAppearance::ScrollbarVertical
:
1451 aResult
->height
*= GetScrollbarButtonCount() + 1;
1463 *aIsOverridable
= true;
1467 nsITheme::Transparency
nsNativeBasicTheme::GetWidgetTransparency(
1468 nsIFrame
* aFrame
, StyleAppearance aAppearance
) {
1469 return eUnknownTransparency
;
1473 nsNativeBasicTheme::WidgetStateChanged(nsIFrame
* aFrame
,
1474 StyleAppearance aAppearance
,
1475 nsAtom
* aAttribute
, bool* aShouldRepaint
,
1476 const nsAttrValue
* aOldValue
) {
1478 // Hover/focus/active changed. Always repaint.
1479 *aShouldRepaint
= true;
1481 // Check the attribute to see if it's relevant.
1482 // disabled, checked, dlgtype, default, etc.
1483 *aShouldRepaint
= false;
1484 if ((aAttribute
== nsGkAtoms::disabled
) ||
1485 (aAttribute
== nsGkAtoms::checked
) ||
1486 (aAttribute
== nsGkAtoms::selected
) ||
1487 (aAttribute
== nsGkAtoms::visuallyselected
) ||
1488 (aAttribute
== nsGkAtoms::menuactive
) ||
1489 (aAttribute
== nsGkAtoms::sortDirection
) ||
1490 (aAttribute
== nsGkAtoms::focused
) ||
1491 (aAttribute
== nsGkAtoms::_default
) ||
1492 (aAttribute
== nsGkAtoms::open
) || (aAttribute
== nsGkAtoms::hover
)) {
1493 *aShouldRepaint
= true;
1501 nsNativeBasicTheme::ThemeChanged() { return NS_OK
; }
1503 bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(
1504 StyleAppearance aAppearance
) {
1505 return IsWidgetScrollbarPart(aAppearance
);
1508 nsITheme::ThemeGeometryType
nsNativeBasicTheme::ThemeGeometryTypeForWidget(
1509 nsIFrame
* aFrame
, StyleAppearance aAppearance
) {
1510 return eThemeGeometryTypeUnknown
;
1513 bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext
* aPresContext
,
1515 StyleAppearance aAppearance
) {
1516 switch (aAppearance
) {
1517 case StyleAppearance::Radio
:
1518 case StyleAppearance::Checkbox
:
1519 case StyleAppearance::FocusOutline
:
1520 case StyleAppearance::Textarea
:
1521 case StyleAppearance::Textfield
:
1522 case StyleAppearance::Range
:
1523 case StyleAppearance::RangeThumb
:
1524 case StyleAppearance::ProgressBar
:
1525 case StyleAppearance::Progresschunk
:
1526 case StyleAppearance::Meter
:
1527 case StyleAppearance::Meterchunk
:
1528 case StyleAppearance::ScrollbarbuttonUp
:
1529 case StyleAppearance::ScrollbarbuttonDown
:
1530 case StyleAppearance::ScrollbarbuttonLeft
:
1531 case StyleAppearance::ScrollbarbuttonRight
:
1532 case StyleAppearance::ScrollbarthumbHorizontal
:
1533 case StyleAppearance::ScrollbarthumbVertical
:
1534 case StyleAppearance::ScrollbartrackHorizontal
:
1535 case StyleAppearance::ScrollbartrackVertical
:
1536 case StyleAppearance::ScrollbarHorizontal
:
1537 case StyleAppearance::ScrollbarVertical
:
1538 case StyleAppearance::Scrollcorner
:
1539 case StyleAppearance::Button
:
1540 case StyleAppearance::Listbox
:
1541 case StyleAppearance::Menulist
:
1542 case StyleAppearance::Menuitem
:
1543 case StyleAppearance::Menuitemtext
:
1544 case StyleAppearance::MenulistText
:
1545 case StyleAppearance::MenulistButton
:
1546 case StyleAppearance::NumberInput
:
1547 case StyleAppearance::MozMenulistArrowButton
:
1548 case StyleAppearance::SpinnerUpbutton
:
1549 case StyleAppearance::SpinnerDownbutton
:
1550 return !IsWidgetStyled(aPresContext
, aFrame
, aAppearance
);
1556 bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance
) {
1557 switch (aAppearance
) {
1558 case StyleAppearance::MozMenulistArrowButton
:
1559 case StyleAppearance::Radio
:
1560 case StyleAppearance::Checkbox
:
1567 bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance
) {
1571 bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }