Bug 1627646 - Avoid creating a Port object when there are no listeners r=mixedpuppy
[gecko.git] / widget / nsNativeBasicTheme.cpp
blob9c86ffa03f6c024e48604e13409ceb4cb76a75cb
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;
20 namespace mozilla {
21 namespace 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,
26 0.9f, 0.4f));
27 static const sRGBColor sBorderColor(sRGBColor(0.62f, 0.62f, 0.68f));
28 static const sRGBColor sBorderColorDisabled(sRGBColor(0.44f, 0.44f, 0.44f,
29 0.4f));
30 static const sRGBColor sBorderHoverColor(sRGBColor(0.5f, 0.5f, 0.56f));
31 static const sRGBColor sBorderHoverColorDisabled(sRGBColor(0.5f, 0.5f, 0.56f,
32 0.4f));
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,
36 0.89f, 0.4f));
37 static const sRGBColor sCheckBackgroundHoverColor(sRGBColor(0.02f, 0.24f,
38 0.58f));
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,
42 0.45f));
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,
49 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,
54 0.375f));
55 static const sRGBColor sScrollbarThumbColorHover(sRGBColor(0.65f, 0.65f,
56 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,
62 0.375f));
63 static const sRGBColor sScrollbarButtonHoverColor(sRGBColor(0.86f, 0.86f,
64 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;
75 } // namespace widget
76 } // namespace mozilla
78 NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme, nsNativeTheme, nsITheme)
80 static uint32_t GetDPIRatio(nsIFrame* aFrame) {
81 return AppUnitsPerCSSPixel() / aFrame->PresContext()
82 ->DeviceContext()
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);
91 if (dateTimeFrame) {
92 return true;
95 return false;
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;
115 if (isDisabled) {
116 if (isChecked) {
117 fillColor = borderColor = sCheckBackgroundColorDisabled;
118 } else {
119 fillColor = sBackgroundColor;
120 borderColor = sBorderColorDisabled;
122 } else {
123 if (isChecked) {
124 if (isPressed) {
125 fillColor = borderColor = sCheckBackgroundActiveColor;
126 } else if (isHovered) {
127 fillColor = borderColor = sCheckBackgroundHoverColor;
128 } else {
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;
140 } else {
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) {
152 Rect rect(aRect);
153 if (rect.width == rect.height) {
154 return rect;
157 if (rect.width > rect.height) {
158 auto diff = rect.width - rect.height;
159 rect.width = rect.height;
160 rect.x += diff / 2;
161 } else {
162 auto diff = rect.height - rect.width;
163 rect.height = rect.width;
164 rect.y += diff / 2;
167 return rect;
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(); }
184 private:
185 DrawTarget& mDt;
188 static void PaintRoundedRectWithBorder(DrawTarget* aDrawTarget,
189 const Rect& aRect,
190 const sRGBColor& aBackgroundColor,
191 const sRGBColor& aBorderColor,
192 CSSCoord aBorderWidth, CSSCoord aRadius,
193 uint32_t aDpi) {
194 const LayoutDeviceCoord borderWidth(aBorderWidth * aDpi);
195 const LayoutDeviceCoord radius(aRadius * aDpi);
197 Rect rect(aRect);
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();
235 Point p = center +
236 Point(checkPolygonX[0] * paintScale, checkPolygonY[0] * paintScale);
237 builder->MoveTo(p);
238 for (int32_t polyIndex = 1; polyIndex < checkNumPoints; polyIndex++) {
239 p = center + Point(checkPolygonX[polyIndex] * paintScale,
240 checkPolygonY[polyIndex] * paintScale);
241 builder->LineTo(p);
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);
252 Rect rect(aRect);
253 rect.y += (rect.height - rect.height / 4) / 2;
254 rect.height /= 4;
256 aDrawTarget->FillRect(
257 rect, ColorPattern(
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,
288 kBorderWidth, aDpi);
291 static void PaintCheckedRadioButton(DrawTarget* aDrawTarget, const Rect& aRect,
292 uint32_t aDpi) {
293 Rect 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);
309 if (isFocused) {
310 return sBorderFocusColor;
312 if (isHovered) {
313 return sBorderHoverColor;
315 return sBorderColor;
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) {
333 bool isActive =
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 = [&] {
339 if (isDisabled) {
340 return sDisabledColor;
342 if (aIsDatetimeResetButton) {
343 return sWhiteColor;
345 if (isActive) {
346 return sButtonActiveColor;
348 if (isHovered) {
349 return sButtonHoverColor;
351 return sButtonColor;
352 }();
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);
380 builder->MoveTo(p);
381 for (int32_t polyIndex = 1; polyIndex < aArrowNumPoints; polyIndex++) {
382 p = aRect.Center() + Point(aArrowPolygonX[polyIndex] * paintScale,
383 aArrowPolygonY[polyIndex] * paintScale);
384 builder->LineTo(p);
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,
393 const Rect& aRect,
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)) {
402 isHovered = false;
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);
410 PaintArrow(
411 aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
412 arrowSize,
413 isPressed ? sActiveColor : isHovered ? sBorderHoverColor : sBorderColor,
414 aDpi);
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;
436 PaintArrow(
437 aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
438 arrowSize,
439 isPressed ? sActiveColor : isHovered ? sBorderHoverColor : sBorderColor,
440 aDpi);
443 static void PaintRangeInputBackground(DrawTarget* aDrawTarget,
444 const Rect& aRect,
445 const EventStates& aState, uint32_t aDpi,
446 bool aHorizontal) {
447 Rect rect(aRect);
448 const LayoutDeviceCoord kVerticalSize = kMinimumWidgetSize * 0.25f * aDpi;
450 if (aHorizontal) {
451 rect.y += (rect.height - kVerticalSize) / 2;
452 rect.height = kVerticalSize;
453 } else {
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,
465 const Rect& aRect,
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,
477 const Rect& aRect,
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,
489 const Rect& aRect) {
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) {
512 bool isActive =
513 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
514 bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
516 aDrawTarget->FillRect(
517 aRect, ColorPattern(
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:
530 break;
531 case StyleAppearance::ScrollbarbuttonDown:
532 for (int32_t i = 0; i < arrowNumPoints; i++) {
533 arrowPolygonY[i] *= -1;
535 break;
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;
542 break;
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;
549 break;
550 default:
551 return;
554 PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
555 arrowSize,
556 isActive
557 ? sScrollbarArrowColorActive
558 : isHovered ? sScrollbarArrowColorHover : sScrollbarArrowColor,
559 aDpi);
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));
566 } else {
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,
577 uint32_t aDpi) {
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,
597 kBorderWidth, aDpi);
600 NS_IMETHODIMP
601 nsNativeBasicTheme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
602 StyleAppearance aAppearance,
603 const nsRect& aRect,
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
617 // parent.
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);
633 break;
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);
644 break;
646 case StyleAppearance::Textarea:
647 case StyleAppearance::Textfield:
648 case StyleAppearance::NumberInput:
649 PaintTextField(dt, devPxRect, eventState, dpi);
650 break;
651 case StyleAppearance::Listbox:
652 case StyleAppearance::Menulist:
653 case StyleAppearance::MenulistButton:
654 case StyleAppearance::MenulistTextfield:
655 PaintMenulist(dt, devPxRect, eventState, dpi);
656 break;
657 case StyleAppearance::MozMenulistArrowButton:
658 PaintMenulistArrowButton(aFrame, dt, devPxRect, eventState, dpi);
659 break;
660 case StyleAppearance::SpinnerUpbutton:
661 case StyleAppearance::SpinnerDownbutton:
662 PaintSpinnerButton(dt, devPxRect, eventState, aAppearance, dpi);
663 break;
664 case StyleAppearance::Range:
665 PaintRangeInputBackground(dt, devPxRect, eventState, dpi,
666 IsRangeHorizontal(aFrame));
667 break;
668 case StyleAppearance::RangeThumb:
669 // TODO(emilio): Do we want to enforce it being a circle using
670 // FixAspectRatio here? For now let authors tweak, it's a custom pseudo so
671 // it doesn't probably have much compat impact if at all.
672 PaintRangeThumb(dt, devPxRect, eventState, dpi);
673 break;
674 case StyleAppearance::ScrollbarthumbHorizontal:
675 PaintScrollbarthumbHorizontal(dt, devPxRect, eventState);
676 break;
677 case StyleAppearance::ScrollbarthumbVertical:
678 PaintScrollbarthumbVertical(dt, devPxRect, eventState);
679 break;
680 case StyleAppearance::ScrollbarHorizontal:
681 PaintScrollbarHorizontal(dt, devPxRect);
682 break;
683 case StyleAppearance::ScrollbarVertical:
684 case StyleAppearance::Scrollcorner:
685 PaintScrollbarVerticalAndCorner(dt, devPxRect, dpi);
686 break;
687 case StyleAppearance::ScrollbarbuttonUp:
688 case StyleAppearance::ScrollbarbuttonDown:
689 case StyleAppearance::ScrollbarbuttonLeft:
690 case StyleAppearance::ScrollbarbuttonRight:
691 PaintScrollbarbutton(dt, aAppearance, devPxRect, eventState, dpi);
692 break;
693 case StyleAppearance::Button:
694 PaintButton(aFrame, dt, devPxRect, eventState, dpi);
695 break;
696 default:
697 MOZ_ASSERT_UNREACHABLE(
698 "Should not get here with a widget type we don't support.");
699 return NS_ERROR_NOT_IMPLEMENTED;
702 return NS_OK;
705 /*bool
706 nsNativeBasicTheme::CreateWebRenderCommandsForWidget(mozilla::wr::DisplayListBuilder&
707 aBuilder, mozilla::wr::IpcResourceUpdateQueue& aResources, const
708 mozilla::layers::StackingContextHelper& aSc,
709 mozilla::layers::RenderRootStateManager*
710 aManager, nsIFrame* aFrame, StyleAppearance aAppearance, const nsRect& aRect) {
713 LayoutDeviceIntMargin nsNativeBasicTheme::GetWidgetBorder(
714 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
715 uint32_t dpi = GetDPIRatio(aFrame);
716 switch (aAppearance) {
717 case StyleAppearance::Textfield:
718 case StyleAppearance::Textarea:
719 case StyleAppearance::NumberInput: {
720 const LayoutDeviceIntCoord w = kTextFieldBorderWidth * dpi;
721 return LayoutDeviceIntMargin(w, w, w, w);
723 case StyleAppearance::Listbox:
724 case StyleAppearance::Menulist:
725 case StyleAppearance::MenulistButton:
726 case StyleAppearance::MenulistTextfield: {
727 const LayoutDeviceIntCoord w = kMenulistBorderWidth * dpi;
728 return LayoutDeviceIntMargin(w, w, w, w);
730 case StyleAppearance::Button: {
731 const LayoutDeviceIntCoord w = kButtonBorderWidth * dpi;
732 return LayoutDeviceIntMargin(w, w, w, w);
734 default:
735 return LayoutDeviceIntMargin();
739 bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext* aContext,
740 nsIFrame* aFrame,
741 StyleAppearance aAppearance,
742 LayoutDeviceIntMargin* aResult) {
743 switch (aAppearance) {
744 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
745 // and have a meaningful baseline, so they can't have
746 // author-specified padding.
747 case StyleAppearance::Radio:
748 case StyleAppearance::Checkbox:
749 case StyleAppearance::MozMenulistArrowButton:
750 aResult->SizeTo(0, 0, 0, 0);
751 return true;
752 default:
753 break;
756 // Respect author padding.
758 // TODO(emilio): Consider just unconditionally returning false, so that the
759 // default size of all elements matches other platforms and the UA stylesheet.
760 if (aFrame->PresContext()->HasAuthorSpecifiedRules(
761 aFrame, NS_AUTHOR_SPECIFIED_PADDING)) {
762 return false;
765 uint32_t dpi = GetDPIRatio(aFrame);
766 switch (aAppearance) {
767 case StyleAppearance::Textarea:
768 case StyleAppearance::Listbox:
769 case StyleAppearance::Menulist:
770 case StyleAppearance::MenulistButton:
771 case StyleAppearance::MenulistTextfield:
772 case StyleAppearance::NumberInput:
773 aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi);
774 return true;
775 case StyleAppearance::Button:
776 aResult->SizeTo(6 * dpi, 21 * dpi, 6 * dpi, 21 * dpi);
777 return true;
778 case StyleAppearance::Textfield:
779 if (IsDateTimeTextField(aFrame)) {
780 aResult->SizeTo(7 * dpi, 7 * dpi, 5 * dpi, 7 * dpi);
781 return true;
783 aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi);
784 return true;
785 default:
786 return false;
790 bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext* aContext,
791 nsIFrame* aFrame,
792 StyleAppearance aAppearance,
793 nsRect* aOverflowRect) {
794 // TODO(bug 1620360): This should return non-zero for
795 // StyleAppearance::FocusOutline, if we implement outline-style: auto.
796 return false;
799 NS_IMETHODIMP
800 nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext* aPresContext,
801 nsIFrame* aFrame,
802 StyleAppearance aAppearance,
803 LayoutDeviceIntSize* aResult,
804 bool* aIsOverridable) {
805 aResult->width = aResult->height =
806 static_cast<uint32_t>(kMinimumWidgetSize) * GetDPIRatio(aFrame);
807 *aIsOverridable = true;
808 return NS_OK;
811 nsITheme::Transparency nsNativeBasicTheme::GetWidgetTransparency(
812 nsIFrame* aFrame, StyleAppearance aAppearance) {
813 return eUnknownTransparency;
816 NS_IMETHODIMP
817 nsNativeBasicTheme::WidgetStateChanged(nsIFrame* aFrame,
818 StyleAppearance aAppearance,
819 nsAtom* aAttribute, bool* aShouldRepaint,
820 const nsAttrValue* aOldValue) {
821 if (!aAttribute) {
822 // Hover/focus/active changed. Always repaint.
823 *aShouldRepaint = true;
824 } else {
825 // Check the attribute to see if it's relevant.
826 // disabled, checked, dlgtype, default, etc.
827 *aShouldRepaint = false;
828 if ((aAttribute == nsGkAtoms::disabled) ||
829 (aAttribute == nsGkAtoms::checked) ||
830 (aAttribute == nsGkAtoms::selected) ||
831 (aAttribute == nsGkAtoms::visuallyselected) ||
832 (aAttribute == nsGkAtoms::menuactive) ||
833 (aAttribute == nsGkAtoms::sortDirection) ||
834 (aAttribute == nsGkAtoms::focused) ||
835 (aAttribute == nsGkAtoms::_default) ||
836 (aAttribute == nsGkAtoms::open) || (aAttribute == nsGkAtoms::hover)) {
837 *aShouldRepaint = true;
841 return NS_OK;
844 NS_IMETHODIMP
845 nsNativeBasicTheme::ThemeChanged() { return NS_OK; }
847 bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(StyleAppearance) {
848 return false;
851 nsITheme::ThemeGeometryType nsNativeBasicTheme::ThemeGeometryTypeForWidget(
852 nsIFrame* aFrame, StyleAppearance aAppearance) {
853 return eThemeGeometryTypeUnknown;
856 bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext* aPresContext,
857 nsIFrame* aFrame,
858 StyleAppearance aAppearance) {
859 if (IsWidgetScrollbarPart(aAppearance)) {
860 const auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
861 // We don't currently handle custom scrollbars on nsNativeBasicTheme. We
862 // could, potentially.
863 if (style->StyleUI()->HasCustomScrollbars() ||
864 style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
865 return false;
869 switch (aAppearance) {
870 case StyleAppearance::Radio:
871 case StyleAppearance::Checkbox:
872 case StyleAppearance::Textarea:
873 case StyleAppearance::Textfield:
874 case StyleAppearance::Range:
875 case StyleAppearance::RangeThumb:
876 case StyleAppearance::ScrollbarbuttonUp:
877 case StyleAppearance::ScrollbarbuttonDown:
878 case StyleAppearance::ScrollbarbuttonLeft:
879 case StyleAppearance::ScrollbarbuttonRight:
880 case StyleAppearance::ScrollbarthumbHorizontal:
881 case StyleAppearance::ScrollbarthumbVertical:
882 case StyleAppearance::ScrollbarHorizontal:
883 case StyleAppearance::ScrollbarVertical:
884 case StyleAppearance::Scrollcorner:
885 case StyleAppearance::Button:
886 case StyleAppearance::Listbox:
887 case StyleAppearance::Menulist:
888 case StyleAppearance::MenulistButton:
889 case StyleAppearance::MenulistTextfield:
890 case StyleAppearance::NumberInput:
891 case StyleAppearance::MozMenulistArrowButton:
892 case StyleAppearance::SpinnerUpbutton:
893 case StyleAppearance::SpinnerDownbutton:
894 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
895 default:
896 return false;
900 bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance) {
901 switch (aAppearance) {
902 case StyleAppearance::MozMenulistArrowButton:
903 case StyleAppearance::Radio:
904 case StyleAppearance::Checkbox:
905 return false;
906 default:
907 return true;
911 bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
912 return true;
915 bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }
917 already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
918 static StaticRefPtr<nsITheme> gInstance;
919 if (MOZ_UNLIKELY(!gInstance)) {
920 gInstance = new nsNativeBasicTheme();
921 ClearOnShutdown(&gInstance);
923 return do_AddRef(gInstance);