зеркало из https://github.com/mozilla/gecko-dev.git
311 строки
11 KiB
C++
311 строки
11 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nsNativeThemeAndroid.h"
|
|
|
|
#include "nsIFrame.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "AndroidColors.h"
|
|
#include "nsCSSRendering.h"
|
|
#include "PathHelpers.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/StaticPrefs_widget.h"
|
|
#include "nsNativeBasicTheme.h"
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeAndroid, nsNativeTheme, nsITheme)
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gfx;
|
|
|
|
static void ClampRectAndMoveToCenter(nsRect& aRect) {
|
|
if (aRect.width < aRect.height) {
|
|
aRect.y += (aRect.height - aRect.width) / 2;
|
|
aRect.height = aRect.width;
|
|
return;
|
|
}
|
|
|
|
if (aRect.height < aRect.width) {
|
|
aRect.x += (aRect.width - aRect.height) / 2;
|
|
aRect.width = aRect.height;
|
|
}
|
|
}
|
|
|
|
static void PaintCheckboxControl(nsIFrame* aFrame, DrawTarget* aDrawTarget,
|
|
const nsRect& aRect,
|
|
const EventStates& aState) {
|
|
// 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.
|
|
RectCornerRadii innerRadii(2, 2, 2, 2);
|
|
nsRect paddingRect =
|
|
nsCSSRendering::GetBoxShadowInnerPaddingRect(aFrame, aRect);
|
|
const nscoord twipsPerPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
|
|
Rect shadowGfxRect = NSRectToRect(paddingRect, twipsPerPixel);
|
|
shadowGfxRect.Round();
|
|
RefPtr<Path> roundedRect =
|
|
MakePathForRoundedRect(*aDrawTarget, shadowGfxRect, innerRadii);
|
|
aDrawTarget->Stroke(
|
|
roundedRect,
|
|
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBorderColor)));
|
|
aDrawTarget->Fill(
|
|
roundedRect,
|
|
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBackgroundColor)));
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
|
|
aDrawTarget->Fill(
|
|
roundedRect,
|
|
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidDisabledColor)));
|
|
return;
|
|
}
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_ACTIVE)) {
|
|
aDrawTarget->Fill(
|
|
roundedRect,
|
|
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidActiveColor)));
|
|
}
|
|
}
|
|
|
|
static void PaintCheckMark(nsIFrame* aFrame, DrawTarget* aDrawTarget,
|
|
const nsRect& aRect) {
|
|
// 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(aRect.width, aRect.height) / checkSize;
|
|
nsPoint paintCenter(aRect.x + aRect.width / 2, aRect.y + aRect.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& aRect) {
|
|
int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
|
|
|
|
nsRect rect(aRect);
|
|
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)));
|
|
}
|
|
|
|
static void PaintRadioControl(nsIFrame* aFrame, DrawTarget* aDrawTarget,
|
|
const nsRect& aRect, const EventStates& aState) {
|
|
// 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.
|
|
const nscoord twipsPerPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
|
|
Rect devPxRect = NSRectToRect(aRect, twipsPerPixel);
|
|
RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
|
|
AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
|
|
RefPtr<Path> ellipse = builder->Finish();
|
|
aDrawTarget->Stroke(
|
|
ellipse,
|
|
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBorderColor)));
|
|
aDrawTarget->Fill(
|
|
ellipse,
|
|
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidBackgroundColor)));
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
|
|
aDrawTarget->Fill(
|
|
ellipse,
|
|
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidDisabledColor)));
|
|
return;
|
|
}
|
|
|
|
if (aState.HasState(NS_EVENT_STATE_ACTIVE)) {
|
|
aDrawTarget->Fill(
|
|
ellipse,
|
|
ColorPattern(ToDeviceColor(mozilla::widget::sAndroidActiveColor)));
|
|
}
|
|
}
|
|
|
|
static void PaintCheckedRadioButton(nsIFrame* aFrame, DrawTarget* aDrawTarget,
|
|
const nsRect& aRect) {
|
|
// The dot is an ellipse 2px on all sides smaller than the content-box,
|
|
// drawn in the foreground color.
|
|
nsRect rect(aRect);
|
|
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)));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeThemeAndroid::DrawWidgetBackground(gfxContext* aContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
const nsRect& aRect,
|
|
const nsRect& aDirtyRect) {
|
|
EventStates eventState = GetContentState(aFrame, aAppearance);
|
|
nsRect rect(aRect);
|
|
ClampRectAndMoveToCenter(rect);
|
|
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Radio:
|
|
PaintRadioControl(aFrame, aContext->GetDrawTarget(), rect, eventState);
|
|
if (IsSelected(aFrame)) {
|
|
PaintCheckedRadioButton(aFrame, aContext->GetDrawTarget(), rect);
|
|
}
|
|
break;
|
|
case StyleAppearance::Checkbox:
|
|
PaintCheckboxControl(aFrame, aContext->GetDrawTarget(), rect, eventState);
|
|
if (IsChecked(aFrame)) {
|
|
PaintCheckMark(aFrame, aContext->GetDrawTarget(), rect);
|
|
}
|
|
if (GetIndeterminate(aFrame)) {
|
|
PaintIndeterminateMark(aFrame, aContext->GetDrawTarget(), rect);
|
|
}
|
|
break;
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"Should not get here with a widget type we don't support.");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
LayoutDeviceIntMargin nsNativeThemeAndroid::GetWidgetBorder(
|
|
nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
|
|
return LayoutDeviceIntMargin();
|
|
}
|
|
|
|
bool nsNativeThemeAndroid::GetWidgetPadding(nsDeviceContext* aContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
LayoutDeviceIntMargin* aResult) {
|
|
switch (aAppearance) {
|
|
// Radios and checkboxes return a fixed size in GetMinimumWidgetSize
|
|
// and have a meaningful baseline, so they can't have
|
|
// author-specified padding.
|
|
case StyleAppearance::Checkbox:
|
|
case StyleAppearance::Radio:
|
|
aResult->SizeTo(0, 0, 0, 0);
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool nsNativeThemeAndroid::GetWidgetOverflow(nsDeviceContext* aContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
nsRect* aOverflowRect) {
|
|
return false;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeThemeAndroid::GetMinimumWidgetSize(nsPresContext* aPresContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
LayoutDeviceIntSize* aResult,
|
|
bool* aIsOverridable) {
|
|
if (aAppearance == StyleAppearance::Radio ||
|
|
aAppearance == StyleAppearance::Checkbox) {
|
|
// 9px + (1px padding + 1px border) * 2
|
|
aResult->width = aPresContext->CSSPixelsToDevPixels(13);
|
|
aResult->height = aPresContext->CSSPixelsToDevPixels(13);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeThemeAndroid::WidgetStateChanged(nsIFrame* aFrame,
|
|
StyleAppearance aAppearance,
|
|
nsAtom* aAttribute,
|
|
bool* aShouldRepaint,
|
|
const nsAttrValue* aOldValue) {
|
|
if (aAppearance == StyleAppearance::Radio ||
|
|
aAppearance == StyleAppearance::Checkbox) {
|
|
if (aAttribute == nsGkAtoms::active || aAttribute == nsGkAtoms::disabled ||
|
|
aAttribute == nsGkAtoms::hover) {
|
|
*aShouldRepaint = true;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
*aShouldRepaint = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNativeThemeAndroid::ThemeChanged() { return NS_OK; }
|
|
|
|
NS_IMETHODIMP_(bool)
|
|
nsNativeThemeAndroid::ThemeSupportsWidget(nsPresContext* aPresContext,
|
|
nsIFrame* aFrame,
|
|
StyleAppearance aAppearance) {
|
|
switch (aAppearance) {
|
|
case StyleAppearance::Radio:
|
|
case StyleAppearance::Checkbox:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP_(bool)
|
|
nsNativeThemeAndroid::WidgetIsContainer(StyleAppearance aAppearance) {
|
|
return false;
|
|
}
|
|
|
|
bool nsNativeThemeAndroid::ThemeDrawsFocusForWidget(
|
|
StyleAppearance aAppearance) {
|
|
return false;
|
|
}
|
|
|
|
bool nsNativeThemeAndroid::ThemeNeedsComboboxDropmarker() { return false; }
|
|
|
|
nsITheme::Transparency nsNativeThemeAndroid::GetWidgetTransparency(
|
|
nsIFrame* aFrame, StyleAppearance aAppearance) {
|
|
return eUnknownTransparency;
|
|
}
|
|
|
|
already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
|
|
static nsCOMPtr<nsITheme> inst;
|
|
|
|
if (!inst) {
|
|
if (StaticPrefs::widget_disable_native_theme_for_content()) {
|
|
inst = new nsNativeBasicTheme();
|
|
} else {
|
|
inst = new nsNativeThemeAndroid();
|
|
}
|
|
ClearOnShutdown(&inst);
|
|
}
|
|
|
|
return do_AddRef(inst);
|
|
}
|