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"
8 #include "mozilla/StaticPrefs_layout.h"
9 #include "mozilla/ClearOnShutdown.h"
10 #include "nsComboboxControlFrame.h"
11 #include "nsCSSRendering.h"
12 #include "nsDateTimeControlFrame.h"
13 #include "nsDeviceContext.h"
14 #include "PathHelpers.h"
16 using namespace mozilla
;
17 using namespace mozilla::gfx
;
18 using namespace mozilla::widget
;
23 static const sRGBColor
sBackgroundColor(sRGBColor(1.0f
, 1.0f
, 1.0f
));
24 static const sRGBColor
sBackgroundActiveColor(sRGBColor(0.88f
, 0.88f
, 0.9f
));
25 static const sRGBColor
sBackgroundActiveColorDisabled(sRGBColor(0.88f
, 0.88f
,
27 static const sRGBColor
sBorderColor(sRGBColor(0.62f
, 0.62f
, 0.68f
));
28 static const sRGBColor
sBorderColorDisabled(sRGBColor(0.44f
, 0.44f
, 0.44f
,
30 static const sRGBColor
sBorderHoverColor(sRGBColor(0.5f
, 0.5f
, 0.56f
));
31 static const sRGBColor
sBorderHoverColorDisabled(sRGBColor(0.5f
, 0.5f
, 0.56f
,
33 static const sRGBColor
sBorderFocusColor(sRGBColor(0.04f
, 0.52f
, 1.0f
));
34 static const sRGBColor
sCheckBackgroundColor(sRGBColor(0.18f
, 0.39f
, 0.89f
));
35 static const sRGBColor
sCheckBackgroundColorDisabled(sRGBColor(0.18f
, 0.39f
,
37 static const sRGBColor
sCheckBackgroundHoverColor(sRGBColor(0.02f
, 0.24f
,
39 static const sRGBColor
sCheckBackgroundHoverColorDisabled(
40 sRGBColor(0.02f
, 0.24f
, 0.58f
, 0.4f
));
41 static const sRGBColor
sCheckBackgroundActiveColor(sRGBColor(0.03f
, 0.19f
,
43 static const sRGBColor
sCheckBackgroundActiveColorDisabled(
44 sRGBColor(0.03f
, 0.19f
, 0.45f
, 0.4f
));
45 static const sRGBColor
sDisabledColor(sRGBColor(0.89f
, 0.89f
, 0.89f
));
46 static const sRGBColor
sActiveColor(sRGBColor(0.47f
, 0.47f
, 0.48f
));
47 static const sRGBColor
sInputHoverColor(sRGBColor(0.05f
, 0.05f
, 0.05f
, 0.5f
));
48 static const sRGBColor
sRangeInputBackgroundColor(sRGBColor(0.89f
, 0.89f
,
50 static const sRGBColor
sScrollbarColor(sRGBColor(0.94f
, 0.94f
, 0.94f
));
51 static const sRGBColor
sScrollbarBorderColor(sRGBColor(1.0f
, 1.0f
, 1.0f
));
52 static const sRGBColor
sScrollbarThumbColor(sRGBColor(0.8f
, 0.8f
, 0.8f
));
53 static const sRGBColor
sScrollbarThumbColorActive(sRGBColor(0.375f
, 0.375f
,
55 static const sRGBColor
sScrollbarThumbColorHover(sRGBColor(0.65f
, 0.65f
,
57 static const sRGBColor
sScrollbarArrowColor(sRGBColor(0.375f
, 0.375f
, 0.375f
));
58 static const sRGBColor
sScrollbarArrowColorActive(sRGBColor(1.0f
, 1.0f
, 1.0f
));
59 static const sRGBColor
sScrollbarArrowColorHover(sRGBColor(0.0f
, 0.0f
, 0.0f
));
60 static const sRGBColor
sScrollbarButtonColor(sScrollbarColor
);
61 static const sRGBColor
sScrollbarButtonActiveColor(sRGBColor(0.375f
, 0.375f
,
63 static const sRGBColor
sScrollbarButtonHoverColor(sRGBColor(0.86f
, 0.86f
,
65 static const sRGBColor
sButtonColor(sRGBColor(0.98f
, 0.98f
, 0.98f
));
66 static const sRGBColor
sButtonHoverColor(sRGBColor(0.94f
, 0.94f
, 0.96f
));
67 static const sRGBColor
sButtonActiveColor(sRGBColor(0.88f
, 0.88f
, 0.90f
));
68 static const sRGBColor
sWhiteColor(sRGBColor(1.0f
, 1.0f
, 1.0f
, 0.0f
));
70 static const CSSIntCoord kMinimumWidgetSize
= 17;
71 static const CSSCoord kButtonBorderWidth
= 1.0f
;
72 static const CSSCoord kMenulistBorderWidth
= 1.0f
;
73 static const CSSCoord kTextFieldBorderWidth
= 1.0f
;
76 } // namespace mozilla
78 NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme
, nsNativeTheme
, nsITheme
)
80 static uint32_t GetDPIRatio(nsIFrame
* aFrame
) {
81 return AppUnitsPerCSSPixel() / aFrame
->PresContext()
83 ->AppUnitsPerDevPixelAtUnitFullZoom();
86 static bool IsDateTimeResetButton(nsIFrame
* aFrame
) {
87 nsIFrame
* parent
= aFrame
->GetParent();
88 if (parent
&& (parent
= parent
->GetParent()) &&
89 (parent
= parent
->GetParent())) {
90 nsDateTimeControlFrame
* dateTimeFrame
= do_QueryFrame(parent
);
98 static bool IsDateTimeTextField(nsIFrame
* aFrame
) {
99 nsDateTimeControlFrame
* dateTimeFrame
= do_QueryFrame(aFrame
);
100 return dateTimeFrame
;
103 static void ComputeCheckColors(const EventStates
& aState
,
104 sRGBColor
& aBackgroundColor
,
105 sRGBColor
& aBorderColor
) {
106 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
107 bool isPressed
= !isDisabled
&& aState
.HasAllStates(NS_EVENT_STATE_HOVER
|
108 NS_EVENT_STATE_ACTIVE
);
109 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
110 bool isFocused
= aState
.HasState(NS_EVENT_STATE_FOCUS
);
111 bool isChecked
= aState
.HasState(NS_EVENT_STATE_CHECKED
);
113 sRGBColor fillColor
= sBackgroundColor
;
114 sRGBColor borderColor
= sBorderColor
;
117 fillColor
= borderColor
= sCheckBackgroundColorDisabled
;
119 fillColor
= sBackgroundColor
;
120 borderColor
= sBorderColorDisabled
;
125 fillColor
= borderColor
= sCheckBackgroundActiveColor
;
126 } else if (isHovered
) {
127 fillColor
= borderColor
= sCheckBackgroundHoverColor
;
129 fillColor
= borderColor
= sCheckBackgroundColor
;
131 } else if (isPressed
) {
132 fillColor
= sBackgroundActiveColor
;
133 borderColor
= sBorderHoverColor
;
134 } else if (isFocused
) {
135 fillColor
= sBackgroundActiveColor
;
136 borderColor
= sBorderFocusColor
;
137 } else if (isHovered
) {
138 fillColor
= sBackgroundColor
;
139 borderColor
= sBorderHoverColor
;
141 fillColor
= sBackgroundColor
;
142 borderColor
= sBorderColor
;
146 aBackgroundColor
= fillColor
;
147 aBorderColor
= borderColor
;
150 // Checkbox and radio need to preserve aspect-ratio for compat.
151 static Rect
FixAspectRatio(const Rect
& aRect
) {
153 if (rect
.width
== rect
.height
) {
157 if (rect
.width
> rect
.height
) {
158 auto diff
= rect
.width
- rect
.height
;
159 rect
.width
= rect
.height
;
162 auto diff
= rect
.height
- rect
.width
;
163 rect
.height
= rect
.width
;
170 // This pushes and pops a clip rect to the draw target.
172 // This is done to reduce fuzz in places where we may have antialiasing, because
173 // skia is not clip-invariant: given different clips, it does not guarantee the
174 // same result, even if the painted content doesn't intersect the clips.
176 // This is a bit sad, overall, but...
177 struct MOZ_RAII AutoClipRect
{
178 AutoClipRect(DrawTarget
& aDt
, const Rect
& aRect
) : mDt(aDt
) {
179 mDt
.PushClipRect(aRect
);
182 ~AutoClipRect() { mDt
.PopClip(); }
188 static void PaintRoundedRectWithBorder(DrawTarget
* aDrawTarget
,
190 const sRGBColor
& aBackgroundColor
,
191 const sRGBColor
& aBorderColor
,
192 CSSCoord aBorderWidth
, CSSCoord aRadius
,
194 const LayoutDeviceCoord
borderWidth(aBorderWidth
* aDpi
);
195 const LayoutDeviceCoord
radius(aRadius
* aDpi
);
198 // Deflate the rect by half the border width, so that the middle of the stroke
199 // fills exactly the area we want to fill and not more.
200 rect
.Deflate(borderWidth
* 0.5f
);
202 RectCornerRadii
radii(radius
, radius
, radius
, radius
);
203 RefPtr
<Path
> roundedRect
= MakePathForRoundedRect(*aDrawTarget
, rect
, radii
);
205 aDrawTarget
->Fill(roundedRect
, ColorPattern(ToDeviceColor(aBackgroundColor
)));
206 aDrawTarget
->Stroke(roundedRect
, ColorPattern(ToDeviceColor(aBorderColor
)),
207 StrokeOptions(borderWidth
));
210 static void PaintCheckboxControl(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
211 const EventStates
& aState
, uint32_t aDpi
) {
212 const CSSCoord kBorderWidth
= 2.0f
;
213 const CSSCoord kRadius
= 4.0f
;
215 sRGBColor backgroundColor
;
216 sRGBColor borderColor
;
217 ComputeCheckColors(aState
, backgroundColor
, borderColor
);
218 PaintRoundedRectWithBorder(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
219 kBorderWidth
, kRadius
, aDpi
);
222 static void PaintCheckMark(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
223 const EventStates
& aState
, uint32_t aDpi
) {
224 // Points come from the coordinates on a 7X7 unit box centered at 0,0
225 const float checkPolygonX
[] = {-2.5, -0.7, 2.5};
226 const float checkPolygonY
[] = {-0.3, 1.7, -1.5};
227 const int32_t checkNumPoints
= sizeof(checkPolygonX
) / sizeof(float);
228 const int32_t checkSize
= 8;
230 auto center
= aRect
.Center();
232 // Scale the checkmark based on the smallest dimension
233 nscoord paintScale
= std::min(aRect
.width
, aRect
.height
) / checkSize
;
234 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
236 Point(checkPolygonX
[0] * paintScale
, checkPolygonY
[0] * paintScale
);
238 for (int32_t polyIndex
= 1; polyIndex
< checkNumPoints
; polyIndex
++) {
239 p
= center
+ Point(checkPolygonX
[polyIndex
] * paintScale
,
240 checkPolygonY
[polyIndex
] * paintScale
);
243 RefPtr
<Path
> path
= builder
->Finish();
244 aDrawTarget
->Stroke(path
, ColorPattern(ToDeviceColor(sBackgroundColor
)),
245 StrokeOptions(2.0f
* aDpi
));
248 static void PaintIndeterminateMark(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
249 const EventStates
& aState
) {
250 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
253 rect
.y
+= (rect
.height
- rect
.height
/ 4) / 2;
256 aDrawTarget
->FillRect(
258 ToDeviceColor(isDisabled
? sDisabledColor
: sBackgroundColor
)));
261 static void PaintStrokedEllipse(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
262 const sRGBColor
& aBackgroundColor
,
263 const sRGBColor
& aBorderColor
,
264 const CSSCoord aBorderWidth
, uint32_t aDpi
) {
265 const LayoutDeviceCoord
borderWidth(aBorderWidth
* aDpi
);
266 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
268 // Deflate for the same reason as PaintRoundedRectWithBorder. Note that the
269 // size is the diameter, so we just shrink by the border width once.
270 Size
size(aRect
.Size() - Size(borderWidth
, borderWidth
));
271 AppendEllipseToPath(builder
, aRect
.Center(), size
);
272 RefPtr
<Path
> ellipse
= builder
->Finish();
274 aDrawTarget
->Fill(ellipse
, ColorPattern(ToDeviceColor(aBackgroundColor
)));
275 aDrawTarget
->Stroke(ellipse
, ColorPattern(ToDeviceColor(aBorderColor
)),
276 StrokeOptions(borderWidth
));
279 static void PaintRadioControl(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
280 const EventStates
& aState
, uint32_t aDpi
) {
281 const CSSCoord kBorderWidth
= 2.0f
;
283 sRGBColor backgroundColor
;
284 sRGBColor borderColor
;
285 ComputeCheckColors(aState
, backgroundColor
, borderColor
);
287 PaintStrokedEllipse(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
291 static void PaintCheckedRadioButton(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
294 rect
.x
+= 4.5f
* aDpi
;
295 rect
.width
-= 9.0f
* aDpi
;
296 rect
.y
+= 4.5f
* aDpi
;
297 rect
.height
-= 9.0f
* aDpi
;
299 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
300 AppendEllipseToPath(builder
, rect
.Center(), rect
.Size());
301 RefPtr
<Path
> ellipse
= builder
->Finish();
302 aDrawTarget
->Fill(ellipse
, ColorPattern(ToDeviceColor(sBackgroundColor
)));
305 static sRGBColor
ComputeBorderColor(const EventStates
& aState
) {
306 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
307 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
308 bool isFocused
= aState
.HasState(NS_EVENT_STATE_FOCUS
);
310 return sBorderFocusColor
;
313 return sBorderHoverColor
;
318 static void PaintTextField(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
319 const EventStates
& aState
, uint32_t aDpi
) {
320 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
321 const sRGBColor
& backgroundColor
=
322 isDisabled
? sDisabledColor
: sBackgroundColor
;
323 const sRGBColor borderColor
= ComputeBorderColor(aState
);
325 const CSSCoord kRadius
= 4.0f
;
327 PaintRoundedRectWithBorder(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
328 kTextFieldBorderWidth
, kRadius
, aDpi
);
331 std::pair
<sRGBColor
, sRGBColor
> ComputeButtonColors(
332 const EventStates
& aState
, bool aIsDatetimeResetButton
= false) {
334 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
335 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
336 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
338 const sRGBColor
& backgroundColor
= [&] {
340 return sDisabledColor
;
342 if (aIsDatetimeResetButton
) {
346 return sButtonActiveColor
;
349 return sButtonHoverColor
;
354 const sRGBColor borderColor
= ComputeBorderColor(aState
);
356 return std::make_pair(backgroundColor
, borderColor
);
359 static void PaintMenulist(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
360 const EventStates
& aState
, uint32_t aDpi
) {
361 const CSSCoord kRadius
= 4.0f
;
363 sRGBColor backgroundColor
, borderColor
;
364 std::tie(backgroundColor
, borderColor
) = ComputeButtonColors(aState
);
366 PaintRoundedRectWithBorder(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
367 kMenulistBorderWidth
, kRadius
, aDpi
);
370 static void PaintArrow(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
371 const int32_t aArrowPolygonX
[],
372 const int32_t aArrowPolygonY
[],
373 const int32_t aArrowNumPoints
, const int32_t aArrowSize
,
374 const sRGBColor aFillColor
, uint32_t aDpi
) {
375 nscoord paintScale
= std::min(aRect
.width
, aRect
.height
) / aArrowSize
;
376 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
377 Point p
= aRect
.Center() + Point(aArrowPolygonX
[0] * paintScale
,
378 aArrowPolygonY
[0] * paintScale
);
381 for (int32_t polyIndex
= 1; polyIndex
< aArrowNumPoints
; polyIndex
++) {
382 p
= aRect
.Center() + Point(aArrowPolygonX
[polyIndex
] * paintScale
,
383 aArrowPolygonY
[polyIndex
] * paintScale
);
386 RefPtr
<Path
> path
= builder
->Finish();
388 aDrawTarget
->Stroke(path
, ColorPattern(ToDeviceColor(aFillColor
)),
389 StrokeOptions(2.0f
* aDpi
));
392 static void PaintMenulistArrowButton(nsIFrame
* aFrame
, DrawTarget
* aDrawTarget
,
394 const EventStates
& aState
, uint32_t aDpi
) {
395 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
396 bool isPressed
= !isDisabled
&& aState
.HasAllStates(NS_EVENT_STATE_HOVER
|
397 NS_EVENT_STATE_ACTIVE
);
398 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
399 bool isHTML
= nsNativeTheme::IsHTMLContent(aFrame
);
401 if (!isHTML
&& nsNativeTheme::CheckBooleanAttr(aFrame
, nsGkAtoms::open
)) {
405 const int32_t arrowSize
= 8;
406 int32_t arrowPolygonX
[] = {-4, -2, 0};
407 int32_t arrowPolygonY
[] = {-1, 1, -1};
408 const int32_t arrowNumPoints
= sizeof(arrowPolygonX
) / sizeof(int32_t);
411 aDrawTarget
, aRect
, arrowPolygonX
, arrowPolygonY
, arrowNumPoints
,
413 isPressed
? sActiveColor
: isHovered
? sBorderHoverColor
: sBorderColor
,
417 static void PaintSpinnerButton(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
418 const EventStates
& aState
,
419 StyleAppearance aAppearance
, uint32_t aDpi
) {
420 bool isDisabled
= aState
.HasState(NS_EVENT_STATE_DISABLED
);
421 bool isPressed
= !isDisabled
&& aState
.HasAllStates(NS_EVENT_STATE_HOVER
|
422 NS_EVENT_STATE_ACTIVE
);
423 bool isHovered
= !isDisabled
&& aState
.HasState(NS_EVENT_STATE_HOVER
);
425 const int32_t arrowSize
= 8;
426 int32_t arrowPolygonX
[] = {0, 2, 4};
427 int32_t arrowPolygonY
[] = {-3, -1, -3};
428 const int32_t arrowNumPoints
= sizeof(arrowPolygonX
) / sizeof(int32_t);
430 if (aAppearance
== StyleAppearance::SpinnerUpbutton
) {
431 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
432 arrowPolygonY
[i
] *= -1;
437 aDrawTarget
, aRect
, arrowPolygonX
, arrowPolygonY
, arrowNumPoints
,
439 isPressed
? sActiveColor
: isHovered
? sBorderHoverColor
: sBorderColor
,
443 static void PaintRangeInputBackground(DrawTarget
* aDrawTarget
,
445 const EventStates
& aState
, uint32_t aDpi
,
448 const LayoutDeviceCoord kVerticalSize
= kMinimumWidgetSize
* 0.25f
* aDpi
;
451 rect
.y
+= (rect
.height
- kVerticalSize
) / 2;
452 rect
.height
= kVerticalSize
;
454 rect
.x
+= (rect
.width
- kVerticalSize
) / 2;
455 rect
.width
= kVerticalSize
;
458 aDrawTarget
->FillRect(
459 rect
, ColorPattern(ToDeviceColor(sRangeInputBackgroundColor
)));
460 aDrawTarget
->StrokeRect(rect
,
461 ColorPattern(ToDeviceColor(sButtonActiveColor
)));
464 static void PaintScrollbarthumbHorizontal(DrawTarget
* aDrawTarget
,
466 const EventStates
& aState
) {
467 sRGBColor thumbColor
= sScrollbarThumbColor
;
468 if (aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
)) {
469 thumbColor
= sScrollbarThumbColorActive
;
470 } else if (aState
.HasState(NS_EVENT_STATE_HOVER
)) {
471 thumbColor
= sScrollbarThumbColorHover
;
473 aDrawTarget
->FillRect(aRect
, ColorPattern(ToDeviceColor(thumbColor
)));
476 static void PaintScrollbarthumbVertical(DrawTarget
* aDrawTarget
,
478 const EventStates
& aState
) {
479 sRGBColor thumbColor
= sScrollbarThumbColor
;
480 if (aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
)) {
481 thumbColor
= sScrollbarThumbColorActive
;
482 } else if (aState
.HasState(NS_EVENT_STATE_HOVER
)) {
483 thumbColor
= sScrollbarThumbColorHover
;
485 aDrawTarget
->FillRect(aRect
, ColorPattern(ToDeviceColor(thumbColor
)));
488 static void PaintScrollbarHorizontal(DrawTarget
* aDrawTarget
,
490 aDrawTarget
->FillRect(aRect
, ColorPattern(ToDeviceColor(sScrollbarColor
)));
491 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
492 builder
->MoveTo(Point(aRect
.x
, aRect
.y
));
493 builder
->LineTo(Point(aRect
.x
+ aRect
.width
, aRect
.y
));
494 RefPtr
<Path
> path
= builder
->Finish();
495 aDrawTarget
->Stroke(path
, ColorPattern(ToDeviceColor(sScrollbarBorderColor
)));
498 static void PaintScrollbarVerticalAndCorner(DrawTarget
* aDrawTarget
,
499 const Rect
& aRect
, uint32_t aDpi
) {
500 aDrawTarget
->FillRect(aRect
, ColorPattern(ToDeviceColor(sScrollbarColor
)));
501 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
502 builder
->MoveTo(Point(aRect
.x
, aRect
.y
));
503 builder
->LineTo(Point(aRect
.x
, aRect
.y
+ aRect
.height
));
504 RefPtr
<Path
> path
= builder
->Finish();
505 aDrawTarget
->Stroke(path
, ColorPattern(ToDeviceColor(sScrollbarBorderColor
)),
506 StrokeOptions(1.0f
* aDpi
));
509 static void PaintScrollbarbutton(DrawTarget
* aDrawTarget
,
510 StyleAppearance aAppearance
, const Rect
& aRect
,
511 const EventStates
& aState
, uint32_t aDpi
) {
513 aState
.HasAllStates(NS_EVENT_STATE_HOVER
| NS_EVENT_STATE_ACTIVE
);
514 bool isHovered
= aState
.HasState(NS_EVENT_STATE_HOVER
);
516 aDrawTarget
->FillRect(
518 ToDeviceColor(isActive
? sScrollbarButtonActiveColor
519 : isHovered
? sScrollbarButtonHoverColor
520 : sScrollbarColor
)));
522 // Start with Up arrow.
523 int32_t arrowPolygonX
[] = {3, 0, -3};
524 int32_t arrowPolygonY
[] = {2, -1, 2};
525 const int32_t arrowNumPoints
= sizeof(arrowPolygonX
) / sizeof(int32_t);
526 const int32_t arrowSize
= 14;
528 switch (aAppearance
) {
529 case StyleAppearance::ScrollbarbuttonUp
:
531 case StyleAppearance::ScrollbarbuttonDown
:
532 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
533 arrowPolygonY
[i
] *= -1;
536 case StyleAppearance::ScrollbarbuttonLeft
:
537 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
538 int32_t temp
= arrowPolygonX
[i
];
539 arrowPolygonX
[i
] = arrowPolygonY
[i
];
540 arrowPolygonY
[i
] = temp
;
543 case StyleAppearance::ScrollbarbuttonRight
:
544 for (int32_t i
= 0; i
< arrowNumPoints
; i
++) {
545 int32_t temp
= arrowPolygonX
[i
];
546 arrowPolygonX
[i
] = arrowPolygonY
[i
] * -1;
547 arrowPolygonY
[i
] = temp
;
554 PaintArrow(aDrawTarget
, aRect
, arrowPolygonX
, arrowPolygonY
, arrowNumPoints
,
557 ? sScrollbarArrowColorActive
558 : isHovered
? sScrollbarArrowColorHover
: sScrollbarArrowColor
,
561 RefPtr
<PathBuilder
> builder
= aDrawTarget
->CreatePathBuilder();
562 builder
->MoveTo(Point(aRect
.x
, aRect
.y
));
563 if (aAppearance
== StyleAppearance::ScrollbarbuttonUp
||
564 aAppearance
== StyleAppearance::ScrollbarbuttonDown
) {
565 builder
->LineTo(Point(aRect
.x
, aRect
.y
+ aRect
.height
));
567 builder
->LineTo(Point(aRect
.x
+ aRect
.width
, aRect
.y
));
570 RefPtr
<Path
> path
= builder
->Finish();
571 aDrawTarget
->Stroke(path
, ColorPattern(ToDeviceColor(sScrollbarBorderColor
)),
572 StrokeOptions(1.0f
* aDpi
));
575 static void PaintButton(nsIFrame
* aFrame
, DrawTarget
* aDrawTarget
,
576 const Rect
& aRect
, const EventStates
& aState
,
578 const CSSCoord kRadius
= 4.0f
;
580 // FIXME: The DateTimeResetButton bit feels like a bit of a hack.
581 sRGBColor backgroundColor
, borderColor
;
582 std::tie(backgroundColor
, borderColor
) =
583 ComputeButtonColors(aState
, IsDateTimeResetButton(aFrame
));
585 PaintRoundedRectWithBorder(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
586 kButtonBorderWidth
, kRadius
, aDpi
);
589 static void PaintRangeThumb(DrawTarget
* aDrawTarget
, const Rect
& aRect
,
590 const EventStates
& aState
, uint32_t aDpi
) {
591 const CSSCoord kBorderWidth
= 2.0f
;
593 sRGBColor backgroundColor
, borderColor
;
594 std::tie(backgroundColor
, borderColor
) = ComputeButtonColors(aState
);
596 PaintStrokedEllipse(aDrawTarget
, aRect
, backgroundColor
, borderColor
,
601 nsNativeBasicTheme::DrawWidgetBackground(gfxContext
* aContext
, nsIFrame
* aFrame
,
602 StyleAppearance aAppearance
,
604 const nsRect
& /* aDirtyRect */) {
605 DrawTarget
* dt
= aContext
->GetDrawTarget();
606 const nscoord twipsPerPixel
= aFrame
->PresContext()->AppUnitsPerDevPixel();
607 EventStates eventState
= GetContentState(aFrame
, aAppearance
);
609 Rect devPxRect
= NSRectToSnappedRect(aRect
, twipsPerPixel
, *dt
);
610 AutoClipRect
clip(*dt
, devPxRect
);
612 if (aAppearance
== StyleAppearance::MozMenulistArrowButton
) {
613 bool isHTML
= IsHTMLContent(aFrame
);
614 nsIFrame
* parentFrame
= aFrame
->GetParent();
615 bool isMenulist
= !isHTML
&& parentFrame
->IsMenuFrame();
616 // HTML select and XUL menulist dropdown buttons get state from the
618 if (isHTML
|| isMenulist
) {
619 aFrame
= parentFrame
;
620 eventState
= GetContentState(parentFrame
, aAppearance
);
624 uint32_t dpi
= GetDPIRatio(aFrame
);
626 switch (aAppearance
) {
627 case StyleAppearance::Radio
: {
628 auto rect
= FixAspectRatio(devPxRect
);
629 PaintRadioControl(dt
, rect
, eventState
, dpi
);
630 if (IsSelected(aFrame
)) {
631 PaintCheckedRadioButton(dt
, rect
, dpi
);
635 case StyleAppearance::Checkbox
: {
636 auto rect
= FixAspectRatio(devPxRect
);
637 PaintCheckboxControl(dt
, rect
, eventState
, dpi
);
638 if (IsChecked(aFrame
)) {
639 PaintCheckMark(dt
, rect
, eventState
, dpi
);
641 if (GetIndeterminate(aFrame
)) {
642 PaintIndeterminateMark(dt
, rect
, eventState
);
646 case StyleAppearance::Textarea
:
647 case StyleAppearance::Textfield
:
648 case StyleAppearance::NumberInput
:
649 PaintTextField(dt
, devPxRect
, eventState
, dpi
);
651 case StyleAppearance::Listbox
:
652 case StyleAppearance::Menulist
:
653 case StyleAppearance::MenulistButton
:
654 PaintMenulist(dt
, devPxRect
, eventState
, dpi
);
656 case StyleAppearance::MozMenulistArrowButton
:
657 PaintMenulistArrowButton(aFrame
, dt
, devPxRect
, eventState
, dpi
);
659 case StyleAppearance::SpinnerUpbutton
:
660 case StyleAppearance::SpinnerDownbutton
:
661 PaintSpinnerButton(dt
, devPxRect
, eventState
, aAppearance
, dpi
);
663 case StyleAppearance::Range
:
664 PaintRangeInputBackground(dt
, devPxRect
, eventState
, dpi
,
665 IsRangeHorizontal(aFrame
));
667 case StyleAppearance::RangeThumb
:
668 // TODO(emilio): Do we want to enforce it being a circle using
669 // FixAspectRatio here? For now let authors tweak, it's a custom pseudo so
670 // it doesn't probably have much compat impact if at all.
671 PaintRangeThumb(dt
, devPxRect
, eventState
, dpi
);
673 case StyleAppearance::ScrollbarthumbHorizontal
:
674 PaintScrollbarthumbHorizontal(dt
, devPxRect
, eventState
);
676 case StyleAppearance::ScrollbarthumbVertical
:
677 PaintScrollbarthumbVertical(dt
, devPxRect
, eventState
);
679 case StyleAppearance::ScrollbarHorizontal
:
680 PaintScrollbarHorizontal(dt
, devPxRect
);
682 case StyleAppearance::ScrollbarVertical
:
683 case StyleAppearance::Scrollcorner
:
684 PaintScrollbarVerticalAndCorner(dt
, devPxRect
, dpi
);
686 case StyleAppearance::ScrollbarbuttonUp
:
687 case StyleAppearance::ScrollbarbuttonDown
:
688 case StyleAppearance::ScrollbarbuttonLeft
:
689 case StyleAppearance::ScrollbarbuttonRight
:
690 PaintScrollbarbutton(dt
, aAppearance
, devPxRect
, eventState
, dpi
);
692 case StyleAppearance::Button
:
693 PaintButton(aFrame
, dt
, devPxRect
, eventState
, dpi
);
696 MOZ_ASSERT_UNREACHABLE(
697 "Should not get here with a widget type we don't support.");
698 return NS_ERROR_NOT_IMPLEMENTED
;
705 nsNativeBasicTheme::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
706 aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
707 mozilla::layers::StackingContextHelper& aSc,
708 mozilla::layers::RenderRootStateManager*
709 aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
712 LayoutDeviceIntMargin
nsNativeBasicTheme::GetWidgetBorder(
713 nsDeviceContext
* aContext
, nsIFrame
* aFrame
, StyleAppearance aAppearance
) {
714 uint32_t dpi
= GetDPIRatio(aFrame
);
715 switch (aAppearance
) {
716 case StyleAppearance::Textfield
:
717 case StyleAppearance::Textarea
:
718 case StyleAppearance::NumberInput
: {
719 const LayoutDeviceIntCoord w
= kTextFieldBorderWidth
* dpi
;
720 return LayoutDeviceIntMargin(w
, w
, w
, w
);
722 case StyleAppearance::Listbox
:
723 case StyleAppearance::Menulist
:
724 case StyleAppearance::MenulistButton
: {
725 const LayoutDeviceIntCoord w
= kMenulistBorderWidth
* dpi
;
726 return LayoutDeviceIntMargin(w
, w
, w
, w
);
728 case StyleAppearance::Button
: {
729 const LayoutDeviceIntCoord w
= kButtonBorderWidth
* dpi
;
730 return LayoutDeviceIntMargin(w
, w
, w
, w
);
733 return LayoutDeviceIntMargin();
737 bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext
* aContext
,
739 StyleAppearance aAppearance
,
740 LayoutDeviceIntMargin
* aResult
) {
741 switch (aAppearance
) {
742 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
743 // and have a meaningful baseline, so they can't have
744 // author-specified padding.
745 case StyleAppearance::Radio
:
746 case StyleAppearance::Checkbox
:
747 case StyleAppearance::MozMenulistArrowButton
:
748 aResult
->SizeTo(0, 0, 0, 0);
754 // Respect author padding.
756 // TODO(emilio): Consider just unconditionally returning false, so that the
757 // default size of all elements matches other platforms and the UA stylesheet.
758 if (aFrame
->PresContext()->HasAuthorSpecifiedRules(
759 aFrame
, NS_AUTHOR_SPECIFIED_PADDING
)) {
763 uint32_t dpi
= GetDPIRatio(aFrame
);
764 switch (aAppearance
) {
765 case StyleAppearance::Textarea
:
766 case StyleAppearance::Listbox
:
767 case StyleAppearance::Menulist
:
768 case StyleAppearance::MenulistButton
:
769 case StyleAppearance::NumberInput
:
770 aResult
->SizeTo(6 * dpi
, 7 * dpi
, 6 * dpi
, 7 * dpi
);
772 case StyleAppearance::Button
:
773 aResult
->SizeTo(6 * dpi
, 21 * dpi
, 6 * dpi
, 21 * dpi
);
775 case StyleAppearance::Textfield
:
776 if (IsDateTimeTextField(aFrame
)) {
777 aResult
->SizeTo(7 * dpi
, 7 * dpi
, 5 * dpi
, 7 * dpi
);
780 aResult
->SizeTo(6 * dpi
, 7 * dpi
, 6 * dpi
, 7 * dpi
);
787 bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext
* aContext
,
789 StyleAppearance aAppearance
,
790 nsRect
* aOverflowRect
) {
791 // TODO(bug 1620360): This should return non-zero for
792 // StyleAppearance::FocusOutline, if we implement outline-style: auto.
797 nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext
* aPresContext
,
799 StyleAppearance aAppearance
,
800 LayoutDeviceIntSize
* aResult
,
801 bool* aIsOverridable
) {
802 aResult
->width
= aResult
->height
=
803 static_cast<uint32_t>(kMinimumWidgetSize
) * GetDPIRatio(aFrame
);
804 *aIsOverridable
= true;
808 nsITheme::Transparency
nsNativeBasicTheme::GetWidgetTransparency(
809 nsIFrame
* aFrame
, StyleAppearance aAppearance
) {
810 return eUnknownTransparency
;
814 nsNativeBasicTheme::WidgetStateChanged(nsIFrame
* aFrame
,
815 StyleAppearance aAppearance
,
816 nsAtom
* aAttribute
, bool* aShouldRepaint
,
817 const nsAttrValue
* aOldValue
) {
819 // Hover/focus/active changed. Always repaint.
820 *aShouldRepaint
= true;
822 // Check the attribute to see if it's relevant.
823 // disabled, checked, dlgtype, default, etc.
824 *aShouldRepaint
= false;
825 if ((aAttribute
== nsGkAtoms::disabled
) ||
826 (aAttribute
== nsGkAtoms::checked
) ||
827 (aAttribute
== nsGkAtoms::selected
) ||
828 (aAttribute
== nsGkAtoms::visuallyselected
) ||
829 (aAttribute
== nsGkAtoms::menuactive
) ||
830 (aAttribute
== nsGkAtoms::sortDirection
) ||
831 (aAttribute
== nsGkAtoms::focused
) ||
832 (aAttribute
== nsGkAtoms::_default
) ||
833 (aAttribute
== nsGkAtoms::open
) || (aAttribute
== nsGkAtoms::hover
)) {
834 *aShouldRepaint
= true;
842 nsNativeBasicTheme::ThemeChanged() { return NS_OK
; }
844 bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(StyleAppearance
) {
848 nsITheme::ThemeGeometryType
nsNativeBasicTheme::ThemeGeometryTypeForWidget(
849 nsIFrame
* aFrame
, StyleAppearance aAppearance
) {
850 return eThemeGeometryTypeUnknown
;
853 bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext
* aPresContext
,
855 StyleAppearance aAppearance
) {
856 if (IsWidgetScrollbarPart(aAppearance
)) {
857 const auto* style
= nsLayoutUtils::StyleForScrollbar(aFrame
);
858 // We don't currently handle custom scrollbars on nsNativeBasicTheme. We
859 // could, potentially.
860 if (style
->StyleUI()->HasCustomScrollbars() ||
861 style
->StyleUIReset()->mScrollbarWidth
== StyleScrollbarWidth::Thin
) {
866 switch (aAppearance
) {
867 case StyleAppearance::Radio
:
868 case StyleAppearance::Checkbox
:
869 case StyleAppearance::Textarea
:
870 case StyleAppearance::Textfield
:
871 case StyleAppearance::Range
:
872 case StyleAppearance::RangeThumb
:
873 case StyleAppearance::ScrollbarbuttonUp
:
874 case StyleAppearance::ScrollbarbuttonDown
:
875 case StyleAppearance::ScrollbarbuttonLeft
:
876 case StyleAppearance::ScrollbarbuttonRight
:
877 case StyleAppearance::ScrollbarthumbHorizontal
:
878 case StyleAppearance::ScrollbarthumbVertical
:
879 case StyleAppearance::ScrollbarHorizontal
:
880 case StyleAppearance::ScrollbarNonDisappearing
:
881 case StyleAppearance::ScrollbarVertical
:
882 case StyleAppearance::Scrollcorner
:
883 case StyleAppearance::Button
:
884 case StyleAppearance::Listbox
:
885 case StyleAppearance::Menulist
:
886 case StyleAppearance::MenulistButton
:
887 case StyleAppearance::NumberInput
:
888 case StyleAppearance::MozMenulistArrowButton
:
889 case StyleAppearance::SpinnerUpbutton
:
890 case StyleAppearance::SpinnerDownbutton
:
891 return !IsWidgetStyled(aPresContext
, aFrame
, aAppearance
);
897 bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance
) {
898 switch (aAppearance
) {
899 case StyleAppearance::MozMenulistArrowButton
:
900 case StyleAppearance::Radio
:
901 case StyleAppearance::Checkbox
:
908 bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance
) {
909 switch (aAppearance
) {
910 case StyleAppearance::Range
:
911 // TODO(emilio): Checkbox / Radio don't have focus indicators when checked.
912 // If they did, we could just return true here unconditionally.
913 case StyleAppearance::Checkbox
:
914 case StyleAppearance::Radio
:
921 bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }
923 already_AddRefed
<nsITheme
> do_GetBasicNativeThemeDoNotUseDirectly() {
924 static StaticRefPtr
<nsITheme
> gInstance
;
925 if (MOZ_UNLIKELY(!gInstance
)) {
926 gInstance
= new nsNativeBasicTheme();
927 ClearOnShutdown(&gInstance
);
929 return do_AddRef(gInstance
);
932 // On Android there's no native theme.
934 already_AddRefed
<nsITheme
> do_GetNativeThemeDoNotUseDirectly() {
935 return do_GetBasicNativeThemeDoNotUseDirectly();