Bug 1340661 - Manually draw checkbox and radio frames on Android. r?tn,snorp
MozReview-Commit-ID: 8IiaRZNJs16
--- a/layout/forms/nsGfxCheckboxControlFrame.cpp
+++ b/layout/forms/nsGfxCheckboxControlFrame.cpp
@@ -42,8 +42,138 @@ nsGfxCheckboxControlFrame::~nsGfxCheckbo
#ifdef ACCESSIBILITY
a11y::AccType
nsGfxCheckboxControlFrame::AccessibleType()
{
return a11y::eHTMLCheckboxType;
}
#endif
+
+#ifdef ANDROID
+
+#include "mozilla/widget/AndroidColors.h"
+
+static void
+PaintCheckboxBorder(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(aFrame->GetUsedBorderAndPadding());
+
+ // Checkbox controls aren't something that we can render on Android
+ // natively. We fake native drawing of appearance: checkbox items
+ // out here, and use hardcoded colours from AndroidColors.h to
+ // simulate native theming.
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ Rect devPxRect = NSRectToSnappedRect(rect, appUnitsPerDevPixel, *aDrawTarget);
+ aDrawTarget->StrokeRect(devPxRect,
+ ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBorderColor)));
+}
+
+static void
+PaintCheckMark(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(aFrame->GetUsedBorderAndPadding());
+
+ // Points come from the coordinates on a 7X7 unit box centered at 0,0
+ const int32_t checkPolygonX[] = { -3, -1, 3, 3, -1, -3 };
+ const int32_t checkPolygonY[] = { -1, 1, -3, -1, 3, 1 };
+ const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(int32_t);
+ const int32_t checkSize = 9; // 2 units of padding on either side
+ // of the 7x7 unit checkmark
+
+ // Scale the checkmark based on the smallest dimension
+ nscoord paintScale = std::min(rect.width, rect.height) / checkSize;
+ nsPoint paintCenter(rect.x + rect.width / 2,
+ rect.y + rect.height / 2);
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ nsPoint p = paintCenter + nsPoint(checkPolygonX[0] * paintScale,
+ checkPolygonY[0] * paintScale);
+
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ builder->MoveTo(NSPointToPoint(p, appUnitsPerDevPixel));
+ for (int32_t polyIndex = 1; polyIndex < checkNumPoints; polyIndex++) {
+ p = paintCenter + nsPoint(checkPolygonX[polyIndex] * paintScale,
+ checkPolygonY[polyIndex] * paintScale);
+ builder->LineTo(NSPointToPoint(p, appUnitsPerDevPixel));
+ }
+ RefPtr<Path> path = builder->Finish();
+ aDrawTarget->Fill(path,
+ ColorPattern(ToDeviceColor(mozilla::widget::sAndroidCheckColor)));
+}
+
+static void
+PaintIndeterminateMark(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(aFrame->GetUsedBorderAndPadding());
+ rect.y += (rect.height - rect.height/4) / 2;
+ rect.height /= 4;
+
+ Rect devPxRect = NSRectToSnappedRect(rect, appUnitsPerDevPixel, *aDrawTarget);
+ aDrawTarget->FillRect(devPxRect,
+ ColorPattern(ToDeviceColor(mozilla::widget::sAndroidCheckColor)));
+}
+
+void
+nsGfxCheckboxControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsFormControlFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ if (!IsVisibleForPainting(aBuilder)) {
+ return; // nothing to paint.
+ }
+
+ if (IsThemed()) {
+ return; // No need to paint the checkmark. The theme will do it.
+ }
+
+ if (StyleDisplay()->mAppearance != NS_THEME_CHECKBOX) {
+ return;
+ }
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayGeneric(aBuilder, this, PaintCheckboxBorder,
+ "CheckboxBorder", nsDisplayItem::TYPE_CHECKBOX_BORDER));
+
+ if (IsChecked() || IsIndeterminate()) {
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayGeneric(aBuilder, this,
+ IsIndeterminate()
+ ? PaintIndeterminateMark : PaintCheckMark,
+ "CheckedCheckbox",
+ nsDisplayItem::TYPE_CHECKED_CHECKBOX));
+ }
+}
+
+bool
+nsGfxCheckboxControlFrame::IsChecked()
+{
+ nsCOMPtr<nsIDOMHTMLInputElement> elem(do_QueryInterface(mContent));
+ bool retval = false;
+ elem->GetChecked(&retval);
+ return retval;
+}
+
+bool
+nsGfxCheckboxControlFrame::IsIndeterminate()
+{
+ nsCOMPtr<nsIDOMHTMLInputElement> elem(do_QueryInterface(mContent));
+ bool retval = false;
+ elem->GetIndeterminate(&retval);
+ return retval;
+}
+#endif
--- a/layout/forms/nsGfxCheckboxControlFrame.h
+++ b/layout/forms/nsGfxCheckboxControlFrame.h
@@ -17,15 +17,30 @@ public:
virtual ~nsGfxCheckboxControlFrame();
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const override {
return MakeFrameName(NS_LITERAL_STRING("CheckboxControl"), aResult);
}
#endif
+#ifdef ANDROID
+ // On Android, there's no native theme or native widget support for
+ // checkbox or radio buttons. We draw them ourselves here using
+ // hardcoded colour values in order to simulate native drawing.
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+#endif
+
#ifdef ACCESSIBILITY
virtual mozilla::a11y::AccType AccessibleType() override;
#endif
+
+#ifdef ANDROID
+protected:
+ bool IsChecked();
+ bool IsIndeterminate();
+#endif
};
#endif
--- a/layout/forms/nsGfxRadioControlFrame.cpp
+++ b/layout/forms/nsGfxRadioControlFrame.cpp
@@ -35,8 +35,95 @@ nsGfxRadioControlFrame::~nsGfxRadioContr
#ifdef ACCESSIBILITY
a11y::AccType
nsGfxRadioControlFrame::AccessibleType()
{
return a11y::eHTMLRadioButtonType;
}
#endif
+
+#ifdef ANDROID
+
+#include "mozilla/widget/AndroidColors.h"
+
+static void
+PaintRadioBorder(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(aFrame->GetUsedBorderAndPadding());
+
+ Rect devPxRect =
+ ToRect(nsLayoutUtils::RectToGfxRect(rect,
+ aFrame->PresContext()->AppUnitsPerDevPixel()));
+ // Radio controls aren't something that we can render on Android
+ // natively. We fake native drawing of appearance: radio items
+ // out here, and use hardcoded colours to simulate native
+ // theming.
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
+ RefPtr<Path> ellipse = builder->Finish();
+ aDrawTarget->Stroke(ellipse,
+ ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBorderColor)));
+}
+
+//--------------------------------------------------------------
+// Draw the dot for a non-native radio button in the checked state.
+static void
+PaintCheckedRadioButton(nsIFrame* aFrame,
+ DrawTarget* aDrawTarget,
+ const nsRect& aDirtyRect,
+ nsPoint aPt)
+{
+ // The dot is an ellipse 2px on all sides smaller than the content-box,
+ // drawn in the foreground color.
+ nsRect rect(aPt, aFrame->GetSize());
+ rect.Deflate(aFrame->GetUsedBorderAndPadding());
+ rect.Deflate(nsPresContext::CSSPixelsToAppUnits(2),
+ nsPresContext::CSSPixelsToAppUnits(2));
+
+ Rect devPxRect =
+ ToRect(nsLayoutUtils::RectToGfxRect(rect,
+ aFrame->PresContext()->AppUnitsPerDevPixel()));
+
+ RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+ AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
+ RefPtr<Path> ellipse = builder->Finish();
+ aDrawTarget->Fill(ellipse,
+ ColorPattern(ToDeviceColor(mozilla::widget::sAndroidCheckColor)));
+}
+
+void
+nsGfxRadioControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists)
+{
+ nsFormControlFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+ if (!IsVisibleForPainting(aBuilder)) {
+ return;
+ }
+
+ if (IsThemed()) {
+ return; // The theme will paint the check, if any.
+ }
+
+ if (StyleDisplay()->mAppearance != NS_THEME_RADIO) {
+ return;
+ }
+
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayGeneric(aBuilder, this, PaintRadioBorder,
+ "RadioBorder", nsDisplayItem::TYPE_RADIOBUTTON_BORDER));
+
+ bool checked = true;
+ GetCurrentCheckState(&checked); // Get check state from the content model
+ if (checked) {
+ aLists.Content()->AppendNewToTop(new (aBuilder)
+ nsDisplayGeneric(aBuilder, this, PaintCheckedRadioButton,
+ "CheckedRadioButton",
+ nsDisplayItem::TYPE_CHECKED_RADIOBUTTON));
+ }
+}
+#endif
--- a/layout/forms/nsGfxRadioControlFrame.h
+++ b/layout/forms/nsGfxRadioControlFrame.h
@@ -18,11 +18,20 @@ public:
explicit nsGfxRadioControlFrame(nsStyleContext* aContext);
~nsGfxRadioControlFrame();
NS_DECL_FRAMEARENA_HELPERS
#ifdef ACCESSIBILITY
virtual mozilla::a11y::AccType AccessibleType() override;
#endif
+
+#ifdef ANDROID
+ // On Android, there's no native theme or native widget support for
+ // checkbox or radio buttons. We draw them ourselves here using
+ // hardcoded colour values in order to simulate native drawing.
+ virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsRect& aDirtyRect,
+ const nsDisplayListSet& aLists) override;
+#endif
};
#endif
--- a/layout/painting/nsDisplayItemTypesList.h
+++ b/layout/painting/nsDisplayItemTypesList.h
@@ -13,16 +13,17 @@ DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BORDER_
DECLARE_DISPLAY_ITEM_TYPE(BUTTON_BOX_SHADOW_OUTER)
DECLARE_DISPLAY_ITEM_TYPE(BUTTON_FOREGROUND)
DECLARE_DISPLAY_ITEM_TYPE(CANVAS)
DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_COLOR)
DECLARE_DISPLAY_ITEM_TYPE(CANVAS_THEMED_BACKGROUND)
DECLARE_DISPLAY_ITEM_TYPE(CANVAS_BACKGROUND_IMAGE)
DECLARE_DISPLAY_ITEM_TYPE(CANVAS_FOCUS)
DECLARE_DISPLAY_ITEM_TYPE(CARET)
+DECLARE_DISPLAY_ITEM_TYPE(CHECKBOX_BORDER)
DECLARE_DISPLAY_ITEM_TYPE(CHECKED_CHECKBOX)
DECLARE_DISPLAY_ITEM_TYPE(CHECKED_RADIOBUTTON)
DECLARE_DISPLAY_ITEM_TYPE(CLEAR_BACKGROUND)
DECLARE_DISPLAY_ITEM_TYPE(COLUMN_RULE)
DECLARE_DISPLAY_ITEM_TYPE(COMBOBOX_FOCUS)
DECLARE_DISPLAY_ITEM_TYPE(EVENT_RECEIVER)
DECLARE_DISPLAY_ITEM_TYPE(LAYER_EVENT_REGIONS)
DECLARE_DISPLAY_ITEM_TYPE(FIELDSET_BORDER_BACKGROUND)
@@ -36,16 +37,17 @@ DECLARE_DISPLAY_ITEM_TYPE(LIST_FOCUS)
DECLARE_DISPLAY_ITEM_TYPE(OPACITY)
DECLARE_DISPLAY_ITEM_TYPE(OPTION_EVENT_GRABBER)
DECLARE_DISPLAY_ITEM_TYPE(OUTLINE)
DECLARE_DISPLAY_ITEM_TYPE(OWN_LAYER)
DECLARE_DISPLAY_ITEM_TYPE(PLUGIN)
DECLARE_DISPLAY_ITEM_TYPE(PLUGIN_READBACK)
DECLARE_DISPLAY_ITEM_TYPE(PLUGIN_VIDEO)
DECLARE_DISPLAY_ITEM_TYPE(PRINT_PLUGIN)
+DECLARE_DISPLAY_ITEM_TYPE(RADIOBUTTON_BORDER)
DECLARE_DISPLAY_ITEM_TYPE(RANGE_FOCUS_RING)
DECLARE_DISPLAY_ITEM_TYPE(REMOTE)
DECLARE_DISPLAY_ITEM_TYPE(RESOLUTION)
DECLARE_DISPLAY_ITEM_TYPE(SCROLL_INFO_LAYER)
DECLARE_DISPLAY_ITEM_TYPE(SELECTION_OVERLAY)
DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR)
DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR_REGION)
DECLARE_DISPLAY_ITEM_TYPE(SUBDOCUMENT)
--- a/mobile/android/themes/core/content.css
+++ b/mobile/android/themes/core/content.css
@@ -93,17 +93,17 @@ select[size="1"] xul|scrollbarbutton {
margin-left: 0;
min-width: 16px;
}
/* Override inverse OS themes */
textarea,
button,
xul|button,
-* > input:not([type="image"]) {
+* > input:not(:-moz-any([type="image"], [type="checkbox"], [type="radio"])) {
-moz-appearance: none !important; /* See bug 598421 for fixing the platform */
}
textarea,
button,
xul|button,
* > input:not(:-moz-any([type="image"], [type="checkbox"], [type="radio"])) {
border-radius: var(--form_border_radius);
new file mode 100644
--- /dev/null
+++ b/widget/android/AndroidColors.h
@@ -0,0 +1,15 @@
+#ifndef mozilla_widget_AndroidColors_h
+#define mozilla_widget_AndroidColors_h
+
+#include "mozilla/gfx/2D.h"
+
+namespace mozilla {
+namespace widget {
+
+static const Color sAndroidBorderColor(Color(0.73f, 0.73f, 0.73f));
+static const Color sAndroidCheckColor(Color(0.19f, 0.21f, 0.23f));
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -20,16 +20,17 @@ EXPORTS += [
'AndroidBridge.h',
'AndroidJavaWrappers.h',
'AndroidJNIWrapper.h',
'GeneratedJNINatives.h',
'GeneratedJNIWrappers.h',
]
EXPORTS.mozilla.widget += [
+ 'AndroidColors.h',
'AndroidCompositorWidget.h',
'AndroidUiThread.h',
]
UNIFIED_SOURCES += [
'AndroidAlerts.cpp',
'AndroidBridge.cpp',
'AndroidCompositorWidget.cpp',