Bug 1340661 - Manually draw checkbox and radio frames on Android. r=snorp,tnikkel

MozReview-Commit-ID: 8IiaRZNJs16

--HG--
extra : rebase_source : 8844292c6bbfec709752a51d293fc3b9bdfdced8
This commit is contained in:
Mike Conley 2017-03-03 18:36:12 -05:00
parent 538d0a5aad
commit 9036c3c896
8 changed files with 260 additions and 1 deletions

View File

@ -47,3 +47,133 @@ 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

View File

@ -22,9 +22,24 @@ public:
}
#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

View File

@ -40,3 +40,90 @@ 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

View File

@ -23,6 +23,15 @@ public:
#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

View File

@ -18,6 +18,7 @@ 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)
@ -41,6 +42,7 @@ 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)

View File

@ -98,7 +98,7 @@ select[size="1"] xul|scrollbarbutton {
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 */
}

View File

@ -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

View File

@ -25,6 +25,7 @@ EXPORTS += [
]
EXPORTS.mozilla.widget += [
'AndroidColors.h',
'AndroidCompositorWidget.h',
'AndroidUiThread.h',
]