Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include "nsNativeThemeAndroid.h"
6
7
#include "nsIFrame.h"
8
#include "nsStyleConsts.h"
9
#include "AndroidColors.h"
10
#include "nsCSSRendering.h"
11
#include "PathHelpers.h"
12
#include "mozilla/ClearOnShutdown.h"
13
14
NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeAndroid, nsNativeTheme, nsITheme)
15
16
using namespace mozilla;
17
using namespace mozilla::gfx;
18
19
static void ClampRectAndMoveToCenter(nsRect& aRect) {
20
if (aRect.width < aRect.height) {
21
aRect.y += (aRect.height - aRect.width) / 2;
22
aRect.height = aRect.width;
23
return;
24
}
25
26
if (aRect.height < aRect.width) {
27
aRect.x += (aRect.width - aRect.height) / 2;
28
aRect.width = aRect.height;
29
}
30
}
31
32
static void PaintCheckboxControl(nsIFrame* aFrame, DrawTarget* aDrawTarget,
33
const nsRect& aRect,
34
const EventStates& aState) {
35
// Checkbox controls aren't something that we can render on Android
36
// natively. We fake native drawing of appearance: checkbox items
37
// out here, and use hardcoded colours from AndroidColors.h to
38
// simulate native theming.
39
RectCornerRadii innerRadii(2, 2, 2, 2);
40
nsRect paddingRect =
41
nsCSSRendering::GetBoxShadowInnerPaddingRect(aFrame, aRect);
42
const nscoord twipsPerPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
43
Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
44
shadowGfxRect.Round();
45
RefPtr<Path> roundedRect =
46
MakePathForRoundedRect(*aDrawTarget, shadowGfxRect, innerRadii);
47
aDrawTarget->Stroke(
48
roundedRect,
49
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBorderColor)));
50
aDrawTarget->Fill(
51
roundedRect,
52
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBackgroundColor)));
53
54
if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
55
aDrawTarget->Fill(
56
roundedRect,
57
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidDisabledColor)));
58
return;
59
}
60
61
if (aState.HasState(NS_EVENT_STATE_ACTIVE)) {
62
aDrawTarget->Fill(
63
roundedRect,
64
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidActiveColor)));
65
}
66
}
67
68
static void PaintCheckMark(nsIFrame* aFrame, DrawTarget* aDrawTarget,
69
const nsRect& aRect) {
70
// Points come from the coordinates on a 7X7 unit box centered at 0,0
71
const int32_t checkPolygonX[] = {-3, -1, 3, 3, -1, -3};
72
const int32_t checkPolygonY[] = {-1, 1, -3, -1, 3, 1};
73
const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(int32_t);
74
const int32_t checkSize = 9; // 2 units of padding on either side
75
// of the 7x7 unit checkmark
76
77
// Scale the checkmark based on the smallest dimension
78
nscoord paintScale = std::min(aRect.width, aRect.height) / checkSize;
79
nsPoint paintCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2);
80
81
RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
82
nsPoint p = paintCenter + nsPoint(checkPolygonX[0] * paintScale,
83
checkPolygonY[0] * paintScale);
84
85
int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
86
builder->MoveTo(NSPointToPoint(p, appUnitsPerDevPixel));
87
for (int32_t polyIndex = 1; polyIndex < checkNumPoints; polyIndex++) {
88
p = paintCenter + nsPoint(checkPolygonX[polyIndex] * paintScale,
89
checkPolygonY[polyIndex] * paintScale);
90
builder->LineTo(NSPointToPoint(p, appUnitsPerDevPixel));
91
}
92
RefPtr<Path> path = builder->Finish();
93
aDrawTarget->Fill(
94
path, ColorPattern(ToDeviceColor(mozilla::widget::sAndroidCheckColor)));
95
}
96
97
static void PaintIndeterminateMark(nsIFrame* aFrame, DrawTarget* aDrawTarget,
98
const nsRect& aRect) {
99
int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
100
101
nsRect rect(aRect);
102
rect.y += (rect.height - rect.height / 4) / 2;
103
rect.height /= 4;
104
105
Rect devPxRect = NSRectToSnappedRect(rect, appUnitsPerDevPixel, *aDrawTarget);
106
107
aDrawTarget->FillRect(
108
devPxRect,
109
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidCheckColor)));
110
}
111
112
static void PaintRadioControl(nsIFrame* aFrame, DrawTarget* aDrawTarget,
113
const nsRect& aRect, const EventStates& aState) {
114
// Radio controls aren't something that we can render on Android
115
// natively. We fake native drawing of appearance: radio items
116
// out here, and use hardcoded colours to simulate native
117
// theming.
118
const nscoord twipsPerPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
119
Rect devPxRect = NSRectToRect(aRect, twipsPerPixel);
120
RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
121
AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
122
RefPtr<Path> ellipse = builder->Finish();
123
aDrawTarget->Stroke(
124
ellipse,
125
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBorderColor)));
126
aDrawTarget->Fill(
127
ellipse,
128
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBackgroundColor)));
129
130
if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
131
aDrawTarget->Fill(
132
ellipse,
133
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidDisabledColor)));
134
return;
135
}
136
137
if (aState.HasState(NS_EVENT_STATE_ACTIVE)) {
138
aDrawTarget->Fill(
139
ellipse,
140
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidActiveColor)));
141
}
142
}
143
144
static void PaintCheckedRadioButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
145
const nsRect& aRect) {
146
// The dot is an ellipse 2px on all sides smaller than the content-box,
147
// drawn in the foreground color.
148
nsRect rect(aRect);
149
rect.Deflate(nsPresContext::CSSPixelsToAppUnits(2),
150
nsPresContext::CSSPixelsToAppUnits(2));
151
152
Rect devPxRect = ToRect(nsLayoutUtils::RectToGfxRect(
153
rect, aFrame->PresContext()->AppUnitsPerDevPixel()));
154
155
RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
156
AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
157
RefPtr<Path> ellipse = builder->Finish();
158
aDrawTarget->Fill(
159
ellipse,
160
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidCheckColor)));
161
}
162
163
NS_IMETHODIMP
164
nsNativeThemeAndroid::DrawWidgetBackground(gfxContext* aContext,
165
nsIFrame* aFrame,
166
StyleAppearance aAppearance,
167
const nsRect& aRect,
168
const nsRect& aDirtyRect) {
169
EventStates eventState = GetContentState(aFrame, aAppearance);
170
nsRect rect(aRect);
171
ClampRectAndMoveToCenter(rect);
172
173
switch (aAppearance) {
174
case StyleAppearance::Radio:
175
PaintRadioControl(aFrame, aContext->GetDrawTarget(), rect, eventState);
176
if (IsSelected(aFrame)) {
177
PaintCheckedRadioButton(aFrame, aContext->GetDrawTarget(), rect);
178
}
179
break;
180
case StyleAppearance::Checkbox:
181
PaintCheckboxControl(aFrame, aContext->GetDrawTarget(), rect, eventState);
182
if (IsChecked(aFrame)) {
183
PaintCheckMark(aFrame, aContext->GetDrawTarget(), rect);
184
}
185
if (GetIndeterminate(aFrame)) {
186
PaintIndeterminateMark(aFrame, aContext->GetDrawTarget(), rect);
187
}
188
break;
189
default:
190
MOZ_ASSERT_UNREACHABLE(
191
"Should not get here with a widget type we don't support.");
192
return NS_ERROR_NOT_IMPLEMENTED;
193
}
194
195
return NS_OK;
196
}
197
198
LayoutDeviceIntMargin nsNativeThemeAndroid::GetWidgetBorder(
199
nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
200
return LayoutDeviceIntMargin();
201
}
202
203
bool nsNativeThemeAndroid::GetWidgetPadding(nsDeviceContext* aContext,
204
nsIFrame* aFrame,
205
StyleAppearance aAppearance,
206
LayoutDeviceIntMargin* aResult) {
207
switch (aAppearance) {
208
// Radios and checkboxes return a fixed size in GetMinimumWidgetSize
209
// and have a meaningful baseline, so they can't have
210
// author-specified padding.
211
case StyleAppearance::Checkbox:
212
case StyleAppearance::Radio:
213
aResult->SizeTo(0, 0, 0, 0);
214
return true;
215
default:
216
return false;
217
}
218
}
219
220
bool nsNativeThemeAndroid::GetWidgetOverflow(nsDeviceContext* aContext,
221
nsIFrame* aFrame,
222
StyleAppearance aAppearance,
223
nsRect* aOverflowRect) {
224
return false;
225
}
226
227
NS_IMETHODIMP
228
nsNativeThemeAndroid::GetMinimumWidgetSize(nsPresContext* aPresContext,
229
nsIFrame* aFrame,
230
StyleAppearance aAppearance,
231
LayoutDeviceIntSize* aResult,
232
bool* aIsOverridable) {
233
if (aAppearance == StyleAppearance::Radio ||
234
aAppearance == StyleAppearance::Checkbox) {
235
// 9px + (1px padding + 1px border) * 2
236
aResult->width = aPresContext->CSSPixelsToDevPixels(13);
237
aResult->height = aPresContext->CSSPixelsToDevPixels(13);
238
}
239
240
return NS_OK;
241
}
242
243
NS_IMETHODIMP
244
nsNativeThemeAndroid::WidgetStateChanged(nsIFrame* aFrame,
245
StyleAppearance aAppearance,
246
nsAtom* aAttribute,
247
bool* aShouldRepaint,
248
const nsAttrValue* aOldValue) {
249
if (aAppearance == StyleAppearance::Radio ||
250
aAppearance == StyleAppearance::Checkbox) {
251
if (aAttribute == nsGkAtoms::active || aAttribute == nsGkAtoms::disabled ||
252
aAttribute == nsGkAtoms::hover) {
253
*aShouldRepaint = true;
254
return NS_OK;
255
}
256
}
257
258
*aShouldRepaint = false;
259
return NS_OK;
260
}
261
262
NS_IMETHODIMP
263
nsNativeThemeAndroid::ThemeChanged() { return NS_OK; }
264
265
NS_IMETHODIMP_(bool)
266
nsNativeThemeAndroid::ThemeSupportsWidget(nsPresContext* aPresContext,
267
nsIFrame* aFrame,
268
StyleAppearance aAppearance) {
269
switch (aAppearance) {
270
case StyleAppearance::Radio:
271
case StyleAppearance::Checkbox:
272
return true;
273
default:
274
return false;
275
}
276
}
277
278
NS_IMETHODIMP_(bool)
279
nsNativeThemeAndroid::WidgetIsContainer(StyleAppearance aAppearance) {
280
return false;
281
}
282
283
bool nsNativeThemeAndroid::ThemeDrawsFocusForWidget(
284
StyleAppearance aAppearance) {
285
return false;
286
}
287
288
bool nsNativeThemeAndroid::ThemeNeedsComboboxDropmarker() { return false; }
289
290
nsITheme::Transparency nsNativeThemeAndroid::GetWidgetTransparency(
291
nsIFrame* aFrame, StyleAppearance aAppearance) {
292
return eUnknownTransparency;
293
}
294
295
already_AddRefed<nsITheme> do_GetNativeTheme() {
296
static nsCOMPtr<nsITheme> inst;
297
298
if (!inst) {
299
inst = new nsNativeThemeAndroid();
300
ClearOnShutdown(&inst);
301
}
302
303
return do_AddRef(inst);
304
}