Bug 1352238 Part 3 - Implement a fake native theme for checkbox/radio form controls on Android. r=mats,snorp
authorLouis Chang <lochang@mozilla.com>
Tue, 05 Sep 2017 22:44:42 +0800
changeset 378926 d0e60b3b1b582811fa9b2c1a2baff0090e5d7c62
parent 378925 c60cee53a36321b62fc6c915719ba3806ab15e00
child 378927 68cd9f54d2b14ebab0ae4b0c1552983584b640c7
push id32445
push userarchaeopteryx@coole-files.de
push dateTue, 05 Sep 2017 21:54:33 +0000
treeherdermozilla-central@978d2539a8d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats, snorp
bugs1352238
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1352238 Part 3 - Implement a fake native theme for checkbox/radio form controls on Android. r=mats,snorp MozReview-Commit-ID: 5g6VJzfZv4Z
widget/android/AndroidColors.h
widget/android/moz.build
widget/android/nsNativeThemeAndroid.cpp
widget/android/nsNativeThemeAndroid.h
widget/android/nsWidgetFactory.cpp
widget/moz.build
new file mode 100644
--- /dev/null
+++ b/widget/android/AndroidColors.h
@@ -0,0 +1,20 @@
+/* -*- Mode: c++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
+/* 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/. */
+
+#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 // mozilla_widget_AndroidColors_h
--- a/widget/android/moz.build
+++ b/widget/android/moz.build
@@ -50,16 +50,17 @@ UNIFIED_SOURCES += [
     'GeneratedJNIWrappers.cpp',
     'GfxInfo.cpp',
     'nsAndroidProtocolHandler.cpp',
     'nsAppShell.cpp',
     'nsClipboard.cpp',
     'nsDeviceContextAndroid.cpp',
     'nsIdleServiceAndroid.cpp',
     'nsLookAndFeel.cpp',
+    'nsNativeThemeAndroid.cpp',
     'nsPrintOptionsAndroid.cpp',
     'nsScreenManagerAndroid.cpp',
     'nsWidgetFactory.cpp',
     'nsWindow.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
new file mode 100644
--- /dev/null
+++ b/widget/android/nsNativeThemeAndroid.cpp
@@ -0,0 +1,227 @@
+/* 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 "nsIDOMHTMLInputElement.h"
+#include "nsIFrame.h"
+#include "nsThemeConstants.h"
+#include "AndroidColors.h"
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeAndroid, nsNativeTheme, nsITheme)
+
+using namespace mozilla::gfx;
+
+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(aFrame->StyleColor()->mColor)));
+}
+
+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(aFrame->StyleColor()->mColor)));
+}
+
+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()));
+
+  ColorPattern color(ToDeviceColor(aFrame->StyleColor()->mColor));
+
+  RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+  AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
+  RefPtr<Path> ellipse = builder->Finish();
+  aDrawTarget->Fill(ellipse, color);
+}
+
+NS_IMETHODIMP
+nsNativeThemeAndroid::DrawWidgetBackground(gfxContext* aContext,
+                                           nsIFrame* aFrame,
+                                           uint8_t aWidgetType,
+                                           const nsRect& aRect,
+                                           const nsRect& aDirtyRect)
+{
+  switch (aWidgetType) {
+    case NS_THEME_RADIO:
+      PaintRadioControl(aFrame, aContext->GetDrawTarget(), aDirtyRect);
+      if (IsSelected(aFrame)) {
+        PaintCheckedRadioButton(aFrame, aContext->GetDrawTarget(), aDirtyRect);
+      }
+      break;
+    case NS_THEME_CHECKBOX:
+      PaintCheckboxControl(aFrame, aContext->GetDrawTarget(), aDirtyRect);
+      if (IsChecked(aFrame)) {
+        PaintCheckMark(aFrame, aContext->GetDrawTarget(), aDirtyRect);
+      }
+      if (GetIndeterminate(aFrame)) {
+        PaintIndeterminateMark(aFrame, aContext->GetDrawTarget(), aDirtyRect);
+      }
+      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;
+}
+
+NS_IMETHODIMP
+nsNativeThemeAndroid::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
+                                      uint8_t aWidgetType, nsIntMargin* aResult)
+{
+  *aResult = nsIntMargin();
+  return NS_OK;
+}
+
+bool
+nsNativeThemeAndroid::GetWidgetPadding(nsDeviceContext* aContext,
+                                       nsIFrame* aFrame, uint8_t aWidgetType,
+                                       nsIntMargin* aResult)
+{
+  return false;
+}
+
+bool
+nsNativeThemeAndroid::GetWidgetOverflow(nsDeviceContext* aContext,
+                                        nsIFrame* aFrame, uint8_t aWidgetType,
+                                        nsRect* aOverflowRect)
+{
+  return false;
+}
+
+NS_IMETHODIMP
+nsNativeThemeAndroid::GetMinimumWidgetSize(nsPresContext* aPresContext,
+                                       nsIFrame* aFrame, uint8_t aWidgetType,
+                                       LayoutDeviceIntSize* aResult,
+                                       bool* aIsOverridable)
+{
+  if (aWidgetType == NS_THEME_RADIO || aWidgetType == NS_THEME_CHECKBOX) {
+    // The fixed size of checkmark used in PaintCheckMark
+    aResult->width = 9;
+    aResult->height = 9;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeAndroid::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+                                     nsIAtom* aAttribute, bool* aShouldRepaint,
+                                     const nsAttrValue* aOldValue)
+{
+  if (aWidgetType == NS_THEME_RADIO || aWidgetType == NS_THEME_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,
+                                          uint8_t aWidgetType)
+{
+  switch (aWidgetType) {
+    case NS_THEME_RADIO:
+    case NS_THEME_CHECKBOX:
+      return true;
+  }
+
+  return false;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeAndroid::WidgetIsContainer(uint8_t aWidgetType)
+{
+  return false;
+}
+
+bool
+nsNativeThemeAndroid::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+  return false;
+}
+
+bool
+nsNativeThemeAndroid::ThemeNeedsComboboxDropmarker()
+{
+  return false;
+}
+
+nsITheme::Transparency
+nsNativeThemeAndroid::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+  return eUnknownTransparency;
+}
new file mode 100644
--- /dev/null
+++ b/widget/android/nsNativeThemeAndroid.h
@@ -0,0 +1,68 @@
+/* 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/. */
+
+#ifndef nsNativeThemeAndroid_h_
+#define nsNativeThemeAndroid_h_
+
+#include "nsITheme.h"
+#include "nsNativeTheme.h"
+
+class nsNativeThemeAndroid final: private nsNativeTheme,
+                                  public nsITheme
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  // The nsITheme interface.
+  NS_IMETHOD DrawWidgetBackground(gfxContext* aContext,
+                                  nsIFrame* aFrame, uint8_t aWidgetType,
+                                  const nsRect& aRect,
+                                  const nsRect& aDirtyRect) override;
+
+  NS_IMETHOD GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
+                             uint8_t aWidgetType,
+                             nsIntMargin* aResult) override;
+
+  bool GetWidgetPadding(nsDeviceContext* aContext,
+                        nsIFrame* aFrame,
+                        uint8_t aWidgetType,
+                        nsIntMargin* aResult) override;
+
+  bool GetWidgetOverflow(nsDeviceContext* aContext,
+                         nsIFrame* aFrame,
+                         uint8_t aWidgetType,
+                         nsRect* aOverflowRect) override;
+
+  NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext,
+                                  nsIFrame* aFrame, uint8_t aWidgetType,
+                                  mozilla::LayoutDeviceIntSize* aResult,
+                                  bool* aIsOverridable) override;
+
+  NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+                                nsIAtom* aAttribute,
+                                bool* aShouldRepaint,
+                                const nsAttrValue* aOldValue) override;
+
+  NS_IMETHOD ThemeChanged() override;
+
+  NS_IMETHOD_(bool) ThemeSupportsWidget(nsPresContext* aPresContext,
+                                        nsIFrame* aFrame,
+                                        uint8_t aWidgetType) override;
+
+  NS_IMETHOD_(bool) WidgetIsContainer(uint8_t aWidgetType) override;
+
+  NS_IMETHOD_(bool) ThemeDrawsFocusForWidget(uint8_t aWidgetType) override;
+
+  bool ThemeNeedsComboboxDropmarker() override;
+
+  Transparency GetWidgetTransparency(nsIFrame* aFrame,
+                                     uint8_t aWidgetType) override;
+
+  nsNativeThemeAndroid() {}
+
+protected:
+  virtual ~nsNativeThemeAndroid() {}
+};
+
+#endif // nsNativeThemeAndroid_h_
--- a/widget/android/nsWidgetFactory.cpp
+++ b/widget/android/nsWidgetFactory.cpp
@@ -24,16 +24,17 @@
 #include "nsPrintSession.h"
 #include "nsDeviceContextAndroid.h"
 #include "nsHTMLFormatConverter.h"
 #include "nsXULAppAPI.h"
 #include "nsAndroidProtocolHandler.h"
 
 #include "nsToolkitCompsCID.h"
 #include "AndroidAlerts.h"
+#include "nsNativeThemeAndroid.h"
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsScreenManagerAndroid)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceAndroid, nsIdleServiceAndroid::GetInstance)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsAndroid, Init)
@@ -47,20 +48,41 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsAndroid
 namespace mozilla {
 namespace widget {
 // This constructor should really be shared with all platforms.
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(AndroidAlerts)
 }
 }
 
+static nsresult
+nsNativeThemeAndroidConstructor(nsISupports *aOuter, REFNSIID aIID,
+                                void **aResult)
+{
+  nsresult rv;
+
+  if (aOuter) {
+    rv = NS_ERROR_NO_AGGREGATION;
+    return rv;
+  }
+
+  *aResult = nullptr;
+  nsNativeThemeAndroid* inst = new nsNativeThemeAndroid();
+  NS_ADDREF(inst);
+  rv = inst->QueryInterface(aIID, aResult);
+  NS_RELEASE(inst);
+
+  return rv;
+}
+
 NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
 NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
 NS_DEFINE_NAMED_CID(NS_CHILD_CID);
 NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID);
 NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
 NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
 NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID);
 NS_DEFINE_NAMED_CID(NS_DEVICE_CONTEXT_SPEC_CID);
 NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
@@ -69,16 +91,17 @@ NS_DEFINE_NAMED_CID(NS_ANDROIDBRIDGE_CID
 NS_DEFINE_NAMED_CID(NS_ANDROIDPROTOCOLHANDLER_CID);
 NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID);
 
 static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
   { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
   { &kNS_CHILD_CID, false, nullptr, nsWindowConstructor },
   { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor },
   { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerAndroidConstructor },
+  { &kNS_THEMERENDERER_CID, false, nullptr, nsNativeThemeAndroidConstructor },
   { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceAndroidConstructor },
   { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
   { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor },
   { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
   { &kNS_PRINTSETTINGSSERVICE_CID, false, nullptr, nsPrintOptionsAndroidConstructor },
   { &kNS_PRINTSESSION_CID, false, nullptr, nsPrintSessionConstructor },
   { &kNS_DEVICE_CONTEXT_SPEC_CID, false, nullptr, nsDeviceContextSpecAndroidConstructor },
   { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
@@ -89,16 +112,17 @@ static const mozilla::Module::CIDEntry k
   { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
   { "@mozilla.org/widgets/window/android;1", &kNS_WINDOW_CID },
   { "@mozilla.org/widgets/child_window/android;1", &kNS_CHILD_CID },
   { "@mozilla.org/widget/appshell/android;1", &kNS_APPSHELL_CID },
   { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID },
+  { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID },
   { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
   { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
   { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID },
   { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
   { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID },
   { "@mozilla.org/gfx/printsession;1", &kNS_PRINTSESSION_CID },
   { "@mozilla.org/gfx/devicecontextspec;1", &kNS_DEVICE_CONTEXT_SPEC_CID },
   { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -258,17 +258,17 @@ if toolkit in ('cocoa', 'windows'):
     ]
 
 if toolkit in {'gtk2', 'gtk3', 'cocoa', 'windows',
                'android', 'uikit'}:
     UNIFIED_SOURCES += [
         'nsBaseFilePicker.cpp',
     ]
 
-if toolkit in ('gtk2', 'gtk3', 'windows', 'cocoa'):
+if toolkit in ('gtk2', 'gtk3', 'windows', 'cocoa', 'android'):
     UNIFIED_SOURCES += [
         'nsNativeTheme.cpp',
     ]
 if toolkit == 'gtk3':
     XPIDL_SOURCES += [
         'nsIApplicationChooser.idl',
     ]