Bug 1352238 Part 3 - Implement a fake native theme for checkbox/radio form controls on Android. r?mats draft
authorLouis Chang <lochang@mozilla.com>
Tue, 05 Sep 2017 22:44:42 +0800
changeset 659109 eec976c6e58fc347b2c26d7a7aedef5d0011bf17
parent 659108 84914dfd4bd2a524727dd5a9e69cc4e6d1d97c72
child 659110 efbd7c948027c0ec85d133d538b6ac17a2abbaf4
push id78020
push userlochang@mozilla.com
push dateTue, 05 Sep 2017 14:49:24 +0000
reviewersmats
bugs1352238
milestone57.0a1
Bug 1352238 Part 3 - Implement a fake native theme for checkbox/radio form controls on Android. r?mats 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',
     ]