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/. */
5
6 #include "nsNativeBasicTheme.h"
7
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"
15
16 using namespace mozilla;
17 using namespace mozilla::gfx;
18 using namespace mozilla::widget;
19
20 namespace mozilla {
21 namespace widget {
22
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));
69
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;
74
75 } // namespace widget
76 } // namespace mozilla
77
NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme,nsNativeTheme,nsITheme)78 NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme, nsNativeTheme, nsITheme)
79
80 static uint32_t GetDPIRatio(nsIFrame* aFrame) {
81 return AppUnitsPerCSSPixel() / aFrame->PresContext()
82 ->DeviceContext()
83 ->AppUnitsPerDevPixelAtUnitFullZoom();
84 }
85
IsDateTimeResetButton(nsIFrame * aFrame)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;
93 }
94 }
95 return false;
96 }
97
IsDateTimeTextField(nsIFrame * aFrame)98 static bool IsDateTimeTextField(nsIFrame* aFrame) {
99 nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(aFrame);
100 return dateTimeFrame;
101 }
102
ComputeCheckColors(const EventStates & aState,sRGBColor & aBackgroundColor,sRGBColor & aBorderColor)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);
112
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;
121 }
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;
130 }
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;
143 }
144 }
145
146 aBackgroundColor = fillColor;
147 aBorderColor = borderColor;
148 }
149
150 // Checkbox and radio need to preserve aspect-ratio for compat.
FixAspectRatio(const Rect & aRect)151 static Rect FixAspectRatio(const Rect& aRect) {
152 Rect rect(aRect);
153 if (rect.width == rect.height) {
154 return rect;
155 }
156
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;
165 }
166
167 return rect;
168 }
169
170 // This pushes and pops a clip rect to the draw target.
171 //
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.
175 //
176 // This is a bit sad, overall, but...
177 struct MOZ_RAII AutoClipRect {
AutoClipRectAutoClipRect178 AutoClipRect(DrawTarget& aDt, const Rect& aRect) : mDt(aDt) {
179 mDt.PushClipRect(aRect);
180 }
181
~AutoClipRectAutoClipRect182 ~AutoClipRect() { mDt.PopClip(); }
183
184 private:
185 DrawTarget& mDt;
186 };
187
PaintRoundedRectWithBorder(DrawTarget * aDrawTarget,const Rect & aRect,const sRGBColor & aBackgroundColor,const sRGBColor & aBorderColor,CSSCoord aBorderWidth,CSSCoord aRadius,uint32_t aDpi)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);
196
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);
201
202 RectCornerRadii radii(radius, radius, radius, radius);
203 RefPtr<Path> roundedRect = MakePathForRoundedRect(*aDrawTarget, rect, radii);
204
205 aDrawTarget->Fill(roundedRect, ColorPattern(ToDeviceColor(aBackgroundColor)));
206 aDrawTarget->Stroke(roundedRect, ColorPattern(ToDeviceColor(aBorderColor)),
207 StrokeOptions(borderWidth));
208 }
209
PaintCheckboxControl(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t aDpi)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;
214
215 sRGBColor backgroundColor;
216 sRGBColor borderColor;
217 ComputeCheckColors(aState, backgroundColor, borderColor);
218 PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor,
219 kBorderWidth, kRadius, aDpi);
220 }
221
PaintCheckMark(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t 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;
229
230 auto center = aRect.Center();
231
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);
242 }
243 RefPtr<Path> path = builder->Finish();
244 aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sBackgroundColor)),
245 StrokeOptions(2.0f * aDpi));
246 }
247
PaintIndeterminateMark(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState)248 static void PaintIndeterminateMark(DrawTarget* aDrawTarget, const Rect& aRect,
249 const EventStates& aState) {
250 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
251
252 Rect rect(aRect);
253 rect.y += (rect.height - rect.height / 4) / 2;
254 rect.height /= 4;
255
256 aDrawTarget->FillRect(
257 rect, ColorPattern(
258 ToDeviceColor(isDisabled ? sDisabledColor : sBackgroundColor)));
259 }
260
PaintStrokedEllipse(DrawTarget * aDrawTarget,const Rect & aRect,const sRGBColor & aBackgroundColor,const sRGBColor & aBorderColor,const CSSCoord aBorderWidth,uint32_t aDpi)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();
267
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();
273
274 aDrawTarget->Fill(ellipse, ColorPattern(ToDeviceColor(aBackgroundColor)));
275 aDrawTarget->Stroke(ellipse, ColorPattern(ToDeviceColor(aBorderColor)),
276 StrokeOptions(borderWidth));
277 }
278
PaintRadioControl(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t aDpi)279 static void PaintRadioControl(DrawTarget* aDrawTarget, const Rect& aRect,
280 const EventStates& aState, uint32_t aDpi) {
281 const CSSCoord kBorderWidth = 2.0f;
282
283 sRGBColor backgroundColor;
284 sRGBColor borderColor;
285 ComputeCheckColors(aState, backgroundColor, borderColor);
286
287 PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor,
288 kBorderWidth, aDpi);
289 }
290
PaintCheckedRadioButton(DrawTarget * aDrawTarget,const Rect & aRect,uint32_t 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;
298
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)));
303 }
304
ComputeBorderColor(const EventStates & aState)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;
311 }
312 if (isHovered) {
313 return sBorderHoverColor;
314 }
315 return sBorderColor;
316 }
317
PaintTextField(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t aDpi)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);
324
325 const CSSCoord kRadius = 4.0f;
326
327 PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor,
328 kTextFieldBorderWidth, kRadius, aDpi);
329 }
330
ComputeButtonColors(const EventStates & aState,bool aIsDatetimeResetButton=false)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);
337
338 const sRGBColor& backgroundColor = [&] {
339 if (isDisabled) {
340 return sDisabledColor;
341 }
342 if (aIsDatetimeResetButton) {
343 return sWhiteColor;
344 }
345 if (isActive) {
346 return sButtonActiveColor;
347 }
348 if (isHovered) {
349 return sButtonHoverColor;
350 }
351 return sButtonColor;
352 }();
353
354 const sRGBColor borderColor = ComputeBorderColor(aState);
355
356 return std::make_pair(backgroundColor, borderColor);
357 }
358
PaintMenulist(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t aDpi)359 static void PaintMenulist(DrawTarget* aDrawTarget, const Rect& aRect,
360 const EventStates& aState, uint32_t aDpi) {
361 const CSSCoord kRadius = 4.0f;
362
363 sRGBColor backgroundColor, borderColor;
364 std::tie(backgroundColor, borderColor) = ComputeButtonColors(aState);
365
366 PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor,
367 kMenulistBorderWidth, kRadius, aDpi);
368 }
369
PaintArrow(DrawTarget * aDrawTarget,const Rect & aRect,const int32_t aArrowPolygonX[],const int32_t aArrowPolygonY[],const int32_t aArrowNumPoints,const int32_t aArrowSize,const sRGBColor aFillColor,uint32_t 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);
379
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);
385 }
386 RefPtr<Path> path = builder->Finish();
387
388 aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(aFillColor)),
389 StrokeOptions(2.0f * aDpi));
390 }
391
PaintMenulistArrowButton(nsIFrame * aFrame,DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t 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);
400
401 if (!isHTML && nsNativeTheme::CheckBooleanAttr(aFrame, nsGkAtoms::open)) {
402 isHovered = false;
403 }
404
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);
409
410 PaintArrow(
411 aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
412 arrowSize,
413 isPressed ? sActiveColor : isHovered ? sBorderHoverColor : sBorderColor,
414 aDpi);
415 }
416
PaintSpinnerButton(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,StyleAppearance aAppearance,uint32_t 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);
424
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);
429
430 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
431 for (int32_t i = 0; i < arrowNumPoints; i++) {
432 arrowPolygonY[i] *= -1;
433 }
434 }
435
436 PaintArrow(
437 aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
438 arrowSize,
439 isPressed ? sActiveColor : isHovered ? sBorderHoverColor : sBorderColor,
440 aDpi);
441 }
442
PaintRangeInputBackground(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t aDpi,bool aHorizontal)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;
449
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;
456 }
457
458 aDrawTarget->FillRect(
459 rect, ColorPattern(ToDeviceColor(sRangeInputBackgroundColor)));
460 aDrawTarget->StrokeRect(rect,
461 ColorPattern(ToDeviceColor(sButtonActiveColor)));
462 }
463
PaintScrollbarthumbHorizontal(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState)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;
472 }
473 aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(thumbColor)));
474 }
475
PaintScrollbarthumbVertical(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState)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;
484 }
485 aDrawTarget->FillRect(aRect, ColorPattern(ToDeviceColor(thumbColor)));
486 }
487
PaintScrollbarHorizontal(DrawTarget * aDrawTarget,const Rect & aRect)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)));
496 }
497
PaintScrollbarVerticalAndCorner(DrawTarget * aDrawTarget,const Rect & aRect,uint32_t aDpi)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));
507 }
508
PaintScrollbarbutton(DrawTarget * aDrawTarget,StyleAppearance aAppearance,const Rect & aRect,const EventStates & aState,uint32_t 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);
515
516 aDrawTarget->FillRect(
517 aRect, ColorPattern(
518 ToDeviceColor(isActive ? sScrollbarButtonActiveColor
519 : isHovered ? sScrollbarButtonHoverColor
520 : sScrollbarColor)));
521
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;
527
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;
534 }
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;
541 }
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;
548 }
549 break;
550 default:
551 return;
552 }
553
554 PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, arrowNumPoints,
555 arrowSize,
556 isActive
557 ? sScrollbarArrowColorActive
558 : isHovered ? sScrollbarArrowColorHover : sScrollbarArrowColor,
559 aDpi);
560
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));
568 }
569
570 RefPtr<Path> path = builder->Finish();
571 aDrawTarget->Stroke(path, ColorPattern(ToDeviceColor(sScrollbarBorderColor)),
572 StrokeOptions(1.0f * aDpi));
573 }
574
PaintButton(nsIFrame * aFrame,DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t 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;
579
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));
584
585 PaintRoundedRectWithBorder(aDrawTarget, aRect, backgroundColor, borderColor,
586 kButtonBorderWidth, kRadius, aDpi);
587 }
588
PaintRangeThumb(DrawTarget * aDrawTarget,const Rect & aRect,const EventStates & aState,uint32_t aDpi)589 static void PaintRangeThumb(DrawTarget* aDrawTarget, const Rect& aRect,
590 const EventStates& aState, uint32_t aDpi) {
591 const CSSCoord kBorderWidth = 2.0f;
592
593 sRGBColor backgroundColor, borderColor;
594 std::tie(backgroundColor, borderColor) = ComputeButtonColors(aState);
595
596 PaintStrokedEllipse(aDrawTarget, aRect, backgroundColor, borderColor,
597 kBorderWidth, aDpi);
598 }
599
600 NS_IMETHODIMP
DrawWidgetBackground(gfxContext * aContext,nsIFrame * aFrame,StyleAppearance aAppearance,const nsRect & aRect,const nsRect &)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);
608
609 Rect devPxRect = NSRectToSnappedRect(aRect, twipsPerPixel, *dt);
610 AutoClipRect clip(*dt, devPxRect);
611
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);
621 }
622 }
623
624 uint32_t dpi = GetDPIRatio(aFrame);
625
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);
632 }
633 break;
634 }
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);
640 }
641 if (GetIndeterminate(aFrame)) {
642 PaintIndeterminateMark(dt, rect, eventState);
643 }
644 break;
645 }
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;
700 }
701
702 return NS_OK;
703 }
704
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) {
711 }*/
712
GetWidgetBorder(nsDeviceContext * aContext,nsIFrame * aFrame,StyleAppearance aAppearance)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);
722 }
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);
729 }
730 case StyleAppearance::Button: {
731 const LayoutDeviceIntCoord w = kButtonBorderWidth * dpi;
732 return LayoutDeviceIntMargin(w, w, w, w);
733 }
734 default:
735 return LayoutDeviceIntMargin();
736 }
737 }
738
GetWidgetPadding(nsDeviceContext * aContext,nsIFrame * aFrame,StyleAppearance aAppearance,LayoutDeviceIntMargin * aResult)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;
754 }
755
756 // Respect author padding.
757 //
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;
763 }
764
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;
782 }
783 aResult->SizeTo(6 * dpi, 7 * dpi, 6 * dpi, 7 * dpi);
784 return true;
785 default:
786 return false;
787 }
788 }
789
GetWidgetOverflow(nsDeviceContext * aContext,nsIFrame * aFrame,StyleAppearance aAppearance,nsRect * aOverflowRect)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;
797 }
798
799 NS_IMETHODIMP
GetMinimumWidgetSize(nsPresContext * aPresContext,nsIFrame * aFrame,StyleAppearance aAppearance,LayoutDeviceIntSize * aResult,bool * aIsOverridable)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;
809 }
810
GetWidgetTransparency(nsIFrame * aFrame,StyleAppearance aAppearance)811 nsITheme::Transparency nsNativeBasicTheme::GetWidgetTransparency(
812 nsIFrame* aFrame, StyleAppearance aAppearance) {
813 return eUnknownTransparency;
814 }
815
816 NS_IMETHODIMP
WidgetStateChanged(nsIFrame * aFrame,StyleAppearance aAppearance,nsAtom * aAttribute,bool * aShouldRepaint,const nsAttrValue * aOldValue)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;
838 }
839 }
840
841 return NS_OK;
842 }
843
844 NS_IMETHODIMP
ThemeChanged()845 nsNativeBasicTheme::ThemeChanged() { return NS_OK; }
846
WidgetAppearanceDependsOnWindowFocus(StyleAppearance)847 bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(StyleAppearance) {
848 return false;
849 }
850
ThemeGeometryTypeForWidget(nsIFrame * aFrame,StyleAppearance aAppearance)851 nsITheme::ThemeGeometryType nsNativeBasicTheme::ThemeGeometryTypeForWidget(
852 nsIFrame* aFrame, StyleAppearance aAppearance) {
853 return eThemeGeometryTypeUnknown;
854 }
855
ThemeSupportsWidget(nsPresContext * aPresContext,nsIFrame * aFrame,StyleAppearance aAppearance)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;
866 }
867 }
868
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::ScrollbarNonDisappearing:
884 case StyleAppearance::ScrollbarVertical:
885 case StyleAppearance::Scrollcorner:
886 case StyleAppearance::Button:
887 case StyleAppearance::Listbox:
888 case StyleAppearance::Menulist:
889 case StyleAppearance::MenulistButton:
890 case StyleAppearance::MenulistTextfield:
891 case StyleAppearance::NumberInput:
892 case StyleAppearance::MozMenulistArrowButton:
893 case StyleAppearance::SpinnerUpbutton:
894 case StyleAppearance::SpinnerDownbutton:
895 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
896 default:
897 return false;
898 }
899 }
900
WidgetIsContainer(StyleAppearance aAppearance)901 bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance) {
902 switch (aAppearance) {
903 case StyleAppearance::MozMenulistArrowButton:
904 case StyleAppearance::Radio:
905 case StyleAppearance::Checkbox:
906 return false;
907 default:
908 return true;
909 }
910 }
911
ThemeDrawsFocusForWidget(StyleAppearance aAppearance)912 bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
913 switch (aAppearance) {
914 case StyleAppearance::Range:
915 // TODO(emilio): Checkbox / Radio don't have focus indicators when checked.
916 // If they did, we could just return true here unconditionally.
917 case StyleAppearance::Checkbox:
918 case StyleAppearance::Radio:
919 return false;
920 default:
921 return true;
922 }
923 }
924
ThemeNeedsComboboxDropmarker()925 bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }
926
do_GetBasicNativeThemeDoNotUseDirectly()927 already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
928 static StaticRefPtr<nsITheme> gInstance;
929 if (MOZ_UNLIKELY(!gInstance)) {
930 gInstance = new nsNativeBasicTheme();
931 ClearOnShutdown(&gInstance);
932 }
933 return do_AddRef(gInstance);
934 }
935