summaryrefslogtreecommitdiffstats
path: root/widget/cocoa/nsNativeThemeCocoa.mm
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /widget/cocoa/nsNativeThemeCocoa.mm
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'widget/cocoa/nsNativeThemeCocoa.mm')
-rw-r--r--widget/cocoa/nsNativeThemeCocoa.mm3931
1 files changed, 3931 insertions, 0 deletions
diff --git a/widget/cocoa/nsNativeThemeCocoa.mm b/widget/cocoa/nsNativeThemeCocoa.mm
new file mode 100644
index 000000000..3c8695442
--- /dev/null
+++ b/widget/cocoa/nsNativeThemeCocoa.mm
@@ -0,0 +1,3931 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsNativeThemeCocoa.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Helpers.h"
+#include "nsChildView.h"
+#include "nsDeviceContext.h"
+#include "nsLayoutUtils.h"
+#include "nsObjCExceptions.h"
+#include "nsNumberControlFrame.h"
+#include "nsRangeFrame.h"
+#include "nsRenderingContext.h"
+#include "nsRect.h"
+#include "nsSize.h"
+#include "nsThemeConstants.h"
+#include "nsIPresShell.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "nsIFrame.h"
+#include "nsIAtom.h"
+#include "nsNameSpaceManager.h"
+#include "nsPresContext.h"
+#include "nsGkAtoms.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaWindow.h"
+#include "nsNativeThemeColors.h"
+#include "nsIScrollableFrame.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLMeterElement.h"
+#include "nsLookAndFeel.h"
+#include "VibrancyManager.h"
+
+#include "gfxContext.h"
+#include "gfxQuartzSurface.h"
+#include "gfxQuartzNativeDrawing.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using mozilla::dom::HTMLMeterElement;
+
+#define DRAW_IN_FRAME_DEBUG 0
+#define SCROLLBARS_VISUAL_DEBUG 0
+
+// private Quartz routines needed here
+extern "C" {
+ CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
+ CG_EXTERN void CGContextSetBaseCTM(CGContextRef, CGAffineTransform);
+ typedef CFTypeRef CUIRendererRef;
+ void CUIDraw(CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options, CFDictionaryRef* result);
+}
+
+// Workaround for NSCell control tint drawing
+// Without this workaround, NSCells are always drawn with the clear control tint
+// as long as they're not attached to an NSControl which is a subview of an active window.
+// XXXmstange Why doesn't Webkit need this?
+@implementation NSCell (ControlTintWorkaround)
+- (int)_realControlTint { return [self controlTint]; }
+@end
+
+// The purpose of this class is to provide objects that can be used when drawing
+// NSCells using drawWithFrame:inView: without causing any harm. The only
+// messages that will be sent to such an object are "isFlipped" and
+// "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
+// on 10.4 (see bug 465069); currentEditor (which isn't even a method of
+// NSView) will be called when drawing search fields, and we only provide it in
+// order to prevent "unrecognized selector" exceptions.
+// There's no need to pass the actual NSView that we're drawing into to
+// drawWithFrame:inView:. What's more, doing so even causes unnecessary
+// invalidations as soon as we draw a focusring!
+@interface CellDrawView : NSView
+
+@end;
+
+@implementation CellDrawView
+
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (NSText*)currentEditor
+{
+ return nil;
+}
+
+@end
+
+// These two classes don't actually add any behavior over NSButtonCell. Their
+// purpose is to make it easy to distinguish NSCell objects that are used for
+// drawing radio buttons / checkboxes from other cell types.
+// The class names are made up, there are no classes with these names in AppKit.
+// The reason we need them is that calling [cell setButtonType:NSRadioButton]
+// doesn't leave an easy-to-check "marker" on the cell object - there is no
+// -[NSButtonCell buttonType] method.
+@interface RadioButtonCell : NSButtonCell
+@end;
+@implementation RadioButtonCell @end;
+@interface CheckboxCell : NSButtonCell
+@end;
+@implementation CheckboxCell @end;
+
+static void
+DrawFocusRingForCellIfNeeded(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
+{
+ if ([aCell showsFirstResponder]) {
+ CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+ CGContextSaveGState(cgContext);
+
+ // It's important to set the focus ring style before we enter the
+ // transparency layer so that the transparency layer only contains
+ // the normal button mask without the focus ring, and the conversion
+ // to the focus ring shape happens only when the transparency layer is
+ // ended.
+ NSSetFocusRingStyle(NSFocusRingOnly);
+
+ // We need to draw the whole button into a transparency layer because
+ // many button types are composed of multiple parts, and if these parts
+ // were drawn while the focus ring style was active, each individual part
+ // would produce a focus ring for itself. But we only want one focus ring
+ // for the whole button. The transparency layer is a way to merge the
+ // individual button parts together before the focus ring shape is
+ // calculated.
+ CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0);
+ [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView];
+ CGContextEndTransparencyLayer(cgContext);
+
+ CGContextRestoreGState(cgContext);
+ }
+}
+
+static bool
+FocusIsDrawnByDrawWithFrame(NSCell* aCell)
+{
+#if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
+ // When building with the 10.8 SDK or higher, focus rings don't draw as part
+ // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call
+ // to -[NSCell drawFocusRingMaskWithFrame:inView:]; .
+ // See the NSButtonCell section under
+ // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes
+ return false;
+#else
+ if (!nsCocoaFeatures::OnYosemiteOrLater()) {
+ // When building with the 10.7 SDK or lower, focus rings always draw as
+ // part of -[NSCell drawWithFrame:inView:] if the build is run on 10.9 or
+ // lower.
+ return true;
+ }
+
+ // On 10.10, whether the focus ring is drawn as part of
+ // -[NSCell drawWithFrame:inView:] depends on the cell type.
+ // Radio buttons and checkboxes draw their own focus rings, other cell
+ // types need -[NSCell drawFocusRingMaskWithFrame:inView:].
+ return [aCell isKindOfClass:[RadioButtonCell class]] ||
+ [aCell isKindOfClass:[CheckboxCell class]];
+#endif
+}
+
+static void
+DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView)
+{
+ [aCell drawWithFrame:aWithFrame inView:aInView];
+
+ if (!FocusIsDrawnByDrawWithFrame(aCell)) {
+ DrawFocusRingForCellIfNeeded(aCell, aWithFrame, aInView);
+ }
+}
+
+/**
+ * NSProgressBarCell is used to draw progress bars of any size.
+ */
+@interface NSProgressBarCell : NSCell
+{
+ /*All instance variables are private*/
+ double mValue;
+ double mMax;
+ bool mIsIndeterminate;
+ bool mIsHorizontal;
+}
+
+- (void)setValue:(double)value;
+- (double)value;
+- (void)setMax:(double)max;
+- (double)max;
+- (void)setIndeterminate:(bool)aIndeterminate;
+- (bool)isIndeterminate;
+- (void)setHorizontal:(bool)aIsHorizontal;
+- (bool)isHorizontal;
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
+@end
+
+@implementation NSProgressBarCell
+
+- (void)setMax:(double)aMax
+{
+ mMax = aMax;
+}
+
+- (double)max
+{
+ return mMax;
+}
+
+- (void)setValue:(double)aValue
+{
+ mValue = aValue;
+}
+
+- (double)value
+{
+ return mValue;
+}
+
+- (void)setIndeterminate:(bool)aIndeterminate
+{
+ mIsIndeterminate = aIndeterminate;
+}
+
+- (bool)isIndeterminate
+{
+ return mIsIndeterminate;
+}
+
+- (void)setHorizontal:(bool)aIsHorizontal
+{
+ mIsHorizontal = aIsHorizontal;
+}
+
+- (bool)isHorizontal
+{
+ return mIsHorizontal;
+}
+
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
+{
+ CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+
+ HIThemeTrackDrawInfo tdi;
+
+ tdi.version = 0;
+ tdi.min = 0;
+
+ tdi.value = INT32_MAX * (mValue / mMax);
+ tdi.max = INT32_MAX;
+ tdi.bounds = NSRectToCGRect(cellFrame);
+ tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
+ tdi.enableState = [self controlTint] == NSClearControlTint ? kThemeTrackInactive
+ : kThemeTrackActive;
+
+ NSControlSize size = [self controlSize];
+ if (size == NSRegularControlSize) {
+ tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar
+ : kThemeLargeProgressBar;
+ } else {
+ NS_ASSERTION(size == NSSmallControlSize,
+ "We shouldn't have another size than small and regular for the moment");
+ tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
+ : kThemeMediumProgressBar;
+ }
+
+ int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
+ int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
+ tdi.trackInfo.progress.phase = uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) /
+ milliSecondsPerStep);
+
+ HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
+}
+
+@end
+
+@interface ContextAwareSearchFieldCell : NSSearchFieldCell
+{
+ nsIFrame* mContext;
+}
+
+// setContext: stores the searchfield nsIFrame so that it can be consulted
+// during painting. Please reset this by calling setContext:nullptr as soon as
+// you're done with painting because we don't want to keep a dangling pointer.
+- (void)setContext:(nsIFrame*)aContext;
+@end
+
+@implementation ContextAwareSearchFieldCell
+
+- (id)initTextCell:(NSString*)aString
+{
+ if ((self = [super initTextCell:aString])) {
+ mContext = nullptr;
+ }
+ return self;
+}
+
+- (void)setContext:(nsIFrame*)aContext
+{
+ mContext = aContext;
+}
+
+static BOOL IsToolbarStyleContainer(nsIFrame* aFrame)
+{
+ nsIContent* content = aFrame->GetContent();
+ if (!content)
+ return NO;
+
+ if (content->IsAnyOfXULElements(nsGkAtoms::toolbar,
+ nsGkAtoms::toolbox,
+ nsGkAtoms::statusbar))
+ return YES;
+
+ switch (aFrame->StyleDisplay()->mAppearance) {
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+- (BOOL)_isToolbarMode
+{
+ // On 10.7, searchfields have two different styles, depending on whether
+ // the searchfield is on top of of window chrome. This function is called on
+ // 10.7 during drawing in order to determine which style to use.
+ for (nsIFrame* frame = mContext; frame; frame = frame->GetParent()) {
+ if (IsToolbarStyleContainer(frame)) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+@end
+
+// Workaround for Bug 542048
+// On 64-bit, NSSearchFieldCells don't draw focus rings.
+#if defined(__x86_64__)
+
+@interface SearchFieldCellWithFocusRing : ContextAwareSearchFieldCell {} @end
+
+@implementation SearchFieldCellWithFocusRing
+
+- (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView
+{
+ [super drawWithFrame:rect inView:controlView];
+
+ if (FocusIsDrawnByDrawWithFrame(self)) {
+ // For some reason, -[NSSearchFieldCell drawWithFrame:inView] doesn't draw a
+ // focus ring in 64 bit mode, no matter what SDK is used or what OS X version
+ // we're running on. But if FocusIsDrawnByDrawWithFrame(self), then our
+ // caller expects us to draw a focus ring. So we just do that here.
+ DrawFocusRingForCellIfNeeded(self, rect, controlView);
+ }
+}
+
+- (void)drawFocusRingMaskWithFrame:(NSRect)rect inView:(NSView*)controlView
+{
+ // By default this draws nothing. I don't know why.
+ // We just draw the search field again. It's a great mask shape for its own
+ // focus ring.
+ [super drawWithFrame:rect inView:controlView];
+}
+
+@end
+
+#endif
+
+#define HITHEME_ORIENTATION kHIThemeOrientationNormal
+
+static CGFloat kMaxFocusRingWidth = 0; // initialized by the nsNativeThemeCocoa constructor
+
+// These enums are for indexing into the margin array.
+enum {
+ leopardOSorlater = 0, // 10.6 - 10.9
+ yosemiteOSorlater = 1 // 10.10+
+};
+
+enum {
+ miniControlSize,
+ smallControlSize,
+ regularControlSize
+};
+
+enum {
+ leftMargin,
+ topMargin,
+ rightMargin,
+ bottomMargin
+};
+
+static size_t EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
+ if (cocoaControlSize == NSMiniControlSize)
+ return miniControlSize;
+ else if (cocoaControlSize == NSSmallControlSize)
+ return smallControlSize;
+ else
+ return regularControlSize;
+}
+
+static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
+ if (enumControlSize == miniControlSize)
+ return NSMiniControlSize;
+ else if (enumControlSize == smallControlSize)
+ return NSSmallControlSize;
+ else
+ return NSRegularControlSize;
+}
+
+static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize)
+{
+ if (aControlSize == NSRegularControlSize)
+ return @"regular";
+ else if (aControlSize == NSSmallControlSize)
+ return @"small";
+ else
+ return @"mini";
+}
+
+static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
+{
+ if (!marginSet)
+ return;
+
+ static int osIndex = nsCocoaFeatures::OnYosemiteOrLater() ?
+ yosemiteOSorlater : leopardOSorlater;
+ size_t controlSize = EnumSizeForCocoaSize(cocoaControlSize);
+ const float* buttonMargins = marginSet[osIndex][controlSize];
+ rect->origin.x -= buttonMargins[leftMargin];
+ rect->origin.y -= buttonMargins[bottomMargin];
+ rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
+ rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
+}
+
+static ChildView* ChildViewForFrame(nsIFrame* aFrame)
+{
+ if (!aFrame)
+ return nil;
+
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ if (!widget)
+ return nil;
+
+ NSView* view = (NSView*)widget->GetNativeData(NS_NATIVE_WIDGET);
+ return [view isKindOfClass:[ChildView class]] ? (ChildView*)view : nil;
+}
+
+static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
+ nsIWidget** aTopLevelWidget = NULL)
+{
+ if (!aFrame)
+ return nil;
+
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ if (!widget)
+ return nil;
+
+ nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
+ if (aTopLevelWidget)
+ *aTopLevelWidget = topLevelWidget;
+
+ return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
+}
+
+static NSSize
+WindowButtonsSize(nsIFrame* aFrame)
+{
+ NSWindow* window = NativeWindowForFrame(aFrame);
+ if (!window) {
+ // Return fallback values.
+ return NSMakeSize(54, 16);
+ }
+
+ NSRect buttonBox = NSZeroRect;
+ NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton];
+ if (closeButton) {
+ buttonBox = NSUnionRect(buttonBox, [closeButton frame]);
+ }
+ NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton];
+ if (minimizeButton) {
+ buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]);
+ }
+ NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton];
+ if (zoomButton) {
+ buttonBox = NSUnionRect(buttonBox, [zoomButton frame]);
+ }
+ return buttonBox.size;
+}
+
+static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
+{
+ nsIWidget* topLevelWidget = NULL;
+ NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
+ if (!topLevelWidget || !win)
+ return YES;
+
+ // XUL popups, e.g. the toolbar customization popup, can't become key windows,
+ // but controls in these windows should still get the active look.
+ if (topLevelWidget->WindowType() == eWindowType_popup)
+ return YES;
+ if ([win isSheet])
+ return [win isKeyWindow];
+ return [win isMainWindow] && ![win attachedSheet];
+}
+
+// Toolbar controls and content controls respond to different window
+// activeness states.
+static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl)
+{
+ if (aIsToolbarControl)
+ return [NativeWindowForFrame(aFrame) isMainWindow];
+ return FrameIsInActiveWindow(aFrame);
+}
+
+static bool IsInSourceList(nsIFrame* aFrame) {
+ for (nsIFrame* frame = aFrame->GetParent(); frame; frame = frame->GetParent()) {
+ if (frame->StyleDisplay()->mAppearance == NS_THEME_MAC_SOURCE_LIST) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
+
+nsNativeThemeCocoa::nsNativeThemeCocoa()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ kMaxFocusRingWidth = nsCocoaFeatures::OnYosemiteOrLater() ? 7 : 4;
+
+ // provide a local autorelease pool, as this is called during startup
+ // before the main event-loop pool is in place
+ nsAutoreleasePool pool;
+
+ mDisclosureButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mDisclosureButtonCell setBezelStyle:NSRoundedDisclosureBezelStyle];
+ [mDisclosureButtonCell setButtonType:NSPushOnPushOffButton];
+ [mDisclosureButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mHelpButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle];
+ [mHelpButtonCell setButtonType:NSMomentaryPushInButton];
+ [mHelpButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mPushButtonCell = [[NSButtonCell alloc] initTextCell:@""];
+ [mPushButtonCell setButtonType:NSMomentaryPushInButton];
+ [mPushButtonCell setHighlightsBy:NSPushInCellMask];
+
+ mRadioButtonCell = [[RadioButtonCell alloc] initTextCell:@""];
+ [mRadioButtonCell setButtonType:NSRadioButton];
+
+ mCheckboxCell = [[CheckboxCell alloc] initTextCell:@""];
+ [mCheckboxCell setButtonType:NSSwitchButton];
+ [mCheckboxCell setAllowsMixedState:YES];
+
+#if defined(__x86_64__)
+ mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
+#else
+ mSearchFieldCell = [[ContextAwareSearchFieldCell alloc] initTextCell:@""];
+#endif
+ [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
+ [mSearchFieldCell setBezeled:YES];
+ [mSearchFieldCell setEditable:YES];
+ [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
+
+ mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
+
+ mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
+ [mComboBoxCell setBezeled:YES];
+ [mComboBoxCell setEditable:YES];
+ [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
+
+ mProgressBarCell = [[NSProgressBarCell alloc] init];
+
+ mMeterBarCell = [[NSLevelIndicatorCell alloc]
+ initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
+
+ mCellDrawView = [[CellDrawView alloc] init];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsNativeThemeCocoa::~nsNativeThemeCocoa()
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mMeterBarCell release];
+ [mProgressBarCell release];
+ [mDisclosureButtonCell release];
+ [mHelpButtonCell release];
+ [mPushButtonCell release];
+ [mRadioButtonCell release];
+ [mCheckboxCell release];
+ [mSearchFieldCell release];
+ [mDropdownCell release];
+ [mComboBoxCell release];
+ [mCellDrawView release];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+// Limit on the area of the target rect (in pixels^2) in
+// DrawCellWithScaling() and DrawButton() and above which we
+// don't draw the object into a bitmap buffer. This is to avoid crashes in
+// [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
+// CGContextDrawImage(), and also to avoid very poor drawing performance in
+// CGContextDrawImage() when it scales the bitmap (particularly if xscale or
+// yscale is less than but near 1 -- e.g. 0.9). This value was determined
+// by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
+// different amounts of RAM.
+#define BITMAP_MAX_AREA 500000
+
+static int
+GetBackingScaleFactorForRendering(CGContextRef cgContext)
+{
+ CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
+ CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
+ float maxScale = std::max(fabs(transformedUserSpacePixel.size.width),
+ fabs(transformedUserSpacePixel.size.height));
+ return maxScale > 1.0 ? 2 : 1;
+}
+
+/*
+ * Draw the given NSCell into the given cgContext.
+ *
+ * destRect - the size and position of the resulting control rectangle
+ * controlSize - the NSControlSize which will be given to the NSCell before
+ * asking it to render
+ * naturalSize - The natural dimensions of this control.
+ * If the control rect size is not equal to either of these, a scale
+ * will be applied to the context so that rendering the control at the
+ * natural size will result in it filling the destRect space.
+ * If a control has no natural dimensions in either/both axes, pass 0.0f.
+ * minimumSize - The minimum dimensions of this control.
+ * If the control rect size is less than the minimum for a given axis,
+ * a scale will be applied to the context so that the minimum is used
+ * for drawing. If a control has no minimum dimensions in either/both
+ * axes, pass 0.0f.
+ * marginSet - an array of margins; a multidimensional array of [2][3][4],
+ * with the first dimension being the OS version (Tiger or Leopard),
+ * the second being the control size (mini, small, regular), and the third
+ * being the 4 margin values (left, top, right, bottom).
+ * view - The NSView that we're drawing into. As far as I can tell, it doesn't
+ * matter if this is really the right view; it just has to return YES when
+ * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
+ * mirrorHorizontal - whether to mirror the cell horizontally
+ */
+static void DrawCellWithScaling(NSCell *cell,
+ CGContextRef cgContext,
+ const HIRect& destRect,
+ NSControlSize controlSize,
+ NSSize naturalSize,
+ NSSize minimumSize,
+ const float marginSet[][3][4],
+ NSView* view,
+ BOOL mirrorHorizontal)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
+
+ if (naturalSize.width != 0.0f)
+ drawRect.size.width = naturalSize.width;
+ if (naturalSize.height != 0.0f)
+ drawRect.size.height = naturalSize.height;
+
+ // Keep aspect ratio when scaling if one dimension is free.
+ if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
+ drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
+ if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
+ drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
+
+ // Honor minimum sizes.
+ if (drawRect.size.width < minimumSize.width)
+ drawRect.size.width = minimumSize.width;
+ if (drawRect.size.height < minimumSize.height)
+ drawRect.size.height = minimumSize.height;
+
+ [NSGraphicsContext saveGraphicsState];
+
+ // Only skip the buffer if the area of our cell (in pixels^2) is too large.
+ if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
+ // Inflate the rect Gecko gave us by the margin for the control.
+ InflateControlRect(&drawRect, controlSize, marginSet);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+
+ DrawCellIncludingFocusRing(cell, drawRect, view);
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+ else {
+ float w = ceil(drawRect.size.width);
+ float h = ceil(drawRect.size.height);
+ NSRect tmpRect = NSMakeRect(kMaxFocusRingWidth, kMaxFocusRingWidth, w, h);
+
+ // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
+ InflateControlRect(&tmpRect, controlSize, marginSet);
+
+ // and then, expand by kMaxFocusRingWidth size to make sure we can capture any focus ring
+ w += kMaxFocusRingWidth * 2.0;
+ h += kMaxFocusRingWidth * 2.0;
+
+ int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
+ CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+ CGContextRef ctx = CGBitmapContextCreate(NULL,
+ (int) w * backingScaleFactor, (int) h * backingScaleFactor,
+ 8, (int) w * backingScaleFactor * 4,
+ rgb, kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(rgb);
+
+ // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
+ // This is the first flip transform, applied to cgContext.
+ CGContextScaleCTM(cgContext, 1.0f, -1.0f);
+ CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
+ if (mirrorHorizontal) {
+ CGContextScaleCTM(cgContext, -1.0f, 1.0f);
+ CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
+ }
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
+
+ CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
+
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(ctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
+
+ // This is the second flip transform, applied to ctx.
+ CGContextScaleCTM(ctx, 1.0f, -1.0f);
+ CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
+
+ DrawCellIncludingFocusRing(cell, tmpRect, view);
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+
+ CGImageRef img = CGBitmapContextCreateImage(ctx);
+
+ // Drop the image into the original destination rectangle, scaling to fit
+ // Only scale kMaxFocusRingWidth by xscale/yscale when the resulting rect
+ // doesn't extend beyond the overflow rect
+ float xscale = destRect.size.width / drawRect.size.width;
+ float yscale = destRect.size.height / drawRect.size.height;
+ float scaledFocusRingX = xscale < 1.0f ? kMaxFocusRingWidth * xscale : kMaxFocusRingWidth;
+ float scaledFocusRingY = yscale < 1.0f ? kMaxFocusRingWidth * yscale : kMaxFocusRingWidth;
+ CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
+ destRect.origin.y - scaledFocusRingY,
+ destRect.size.width + scaledFocusRingX * 2,
+ destRect.size.height + scaledFocusRingY * 2),
+ img);
+
+ CGImageRelease(img);
+ CGContextRelease(ctx);
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, destRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+struct CellRenderSettings {
+ // The natural dimensions of the control.
+ // If a control has no natural dimensions in either/both axes, set to 0.0f.
+ NSSize naturalSizes[3];
+
+ // The minimum dimensions of the control.
+ // If a control has no minimum dimensions in either/both axes, set to 0.0f.
+ NSSize minimumSizes[3];
+
+ // A three-dimensional array,
+ // with the first dimension being the OS version ([0] 10.6-10.9, [1] 10.10 and above),
+ // the second being the control size (mini, small, regular), and the third
+ // being the 4 margin values (left, top, right, bottom).
+ float margins[2][3][4];
+};
+
+/*
+ * This is a helper method that returns the required NSControlSize given a size
+ * and the size of the three controls plus a tolerance.
+ * size - The width or the height of the element to draw.
+ * sizes - An array with the all the width/height of the element for its
+ * different sizes.
+ * tolerance - The tolerance as passed to DrawCellWithSnapping.
+ * NOTE: returns NSRegularControlSize if all values in 'sizes' are zero.
+ */
+static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance)
+{
+ for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
+ if (sizes[i] == 0) {
+ continue;
+ }
+
+ CGFloat next = 0;
+ // Find next value.
+ for (uint32_t j = i+1; j <= regularControlSize; ++j) {
+ if (sizes[j] != 0) {
+ next = sizes[j];
+ break;
+ }
+ }
+
+ // If it's the latest value, we pick it.
+ if (next == 0) {
+ return CocoaSizeForEnum(i);
+ }
+
+ if (size <= sizes[i] + tolerance && size < next) {
+ return CocoaSizeForEnum(i);
+ }
+ }
+
+ // If we are here, that means sizes[] was an array with only empty values
+ // or the algorithm above is wrong.
+ // The former can happen but the later would be wrong.
+ NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
+ "We found no control! We shouldn't be there!");
+ return CocoaSizeForEnum(regularControlSize);
+}
+
+/*
+ * Draw the given NSCell into the given cgContext with a nice control size.
+ *
+ * This function is similar to DrawCellWithScaling, but it decides what
+ * control size to use based on the destRect's size.
+ * Scaling is only applied when the difference between the destRect's size
+ * and the next smaller natural size is greater than snapTolerance. Otherwise
+ * it snaps to the next smaller control size without scaling because unscaled
+ * controls look nicer.
+ */
+static void DrawCellWithSnapping(NSCell *cell,
+ CGContextRef cgContext,
+ const HIRect& destRect,
+ const CellRenderSettings settings,
+ float verticalAlignFactor,
+ NSView* view,
+ BOOL mirrorHorizontal,
+ float snapTolerance = 2.0f)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
+ const NSSize *sizes = settings.naturalSizes;
+ const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
+ const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
+ const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
+
+ HIRect drawRect = destRect;
+
+ CGFloat controlWidths[3] = { miniSize.width, smallSize.width, regularSize.width };
+ NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
+ CGFloat controlHeights[3] = { miniSize.height, smallSize.height, regularSize.height };
+ NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
+
+ NSControlSize controlSize = NSRegularControlSize;
+ size_t sizeIndex = 0;
+
+ // At some sizes, don't scale but snap.
+ const NSControlSize smallerControlSize =
+ EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
+ controlSizeX : controlSizeY;
+ const size_t smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
+ const NSSize size = sizes[smallerControlSizeIndex];
+ float diffWidth = size.width ? rectWidth - size.width : 0.0f;
+ float diffHeight = size.height ? rectHeight - size.height : 0.0f;
+ if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
+ diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
+ // Snap to the smaller control size.
+ controlSize = smallerControlSize;
+ sizeIndex = smallerControlSizeIndex;
+ MOZ_ASSERT(sizeIndex < ArrayLength(settings.naturalSizes));
+
+ // Resize and center the drawRect.
+ if (sizes[sizeIndex].width) {
+ drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
+ drawRect.size.width = sizes[sizeIndex].width;
+ }
+ if (sizes[sizeIndex].height) {
+ drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
+ drawRect.size.height = sizes[sizeIndex].height;
+ }
+ } else {
+ // Use the larger control size.
+ controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
+ controlSizeX : controlSizeY;
+ sizeIndex = EnumSizeForCocoaSize(controlSize);
+ }
+
+ [cell setControlSize:controlSize];
+
+ MOZ_ASSERT(sizeIndex < ArrayLength(settings.minimumSizes));
+ const NSSize minimumSize = settings.minimumSizes[sizeIndex];
+ DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
+ minimumSize, settings.margins, view, mirrorHorizontal);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+@interface NSWindow(CoreUIRendererPrivate)
++ (CUIRendererRef)coreUIRenderer;
+@end
+
+static id
+GetAquaAppearance()
+{
+ // We only need NSAppearance on 10.10 and up.
+ if (nsCocoaFeatures::OnYosemiteOrLater()) {
+ Class NSAppearanceClass = NSClassFromString(@"NSAppearance");
+ if (NSAppearanceClass &&
+ [NSAppearanceClass respondsToSelector:@selector(appearanceNamed:)]) {
+ return [NSAppearanceClass performSelector:@selector(appearanceNamed:)
+ withObject:@"NSAppearanceNameAqua"];
+ }
+ }
+ return nil;
+}
+
+@interface NSObject(NSAppearanceCoreUIRendering)
+- (void)_drawInRect:(CGRect)rect context:(CGContextRef)cgContext options:(id)options;
+@end
+
+static void
+RenderWithCoreUI(CGRect aRect, CGContextRef cgContext, NSDictionary* aOptions, bool aSkipAreaCheck = false)
+{
+ id appearance = GetAquaAppearance();
+
+ if (!aSkipAreaCheck && aRect.size.width * aRect.size.height > BITMAP_MAX_AREA) {
+ return;
+ }
+
+ if (appearance && [appearance respondsToSelector:@selector(_drawInRect:context:options:)]) {
+ // Render through NSAppearance on Mac OS 10.10 and up. This will call
+ // CUIDraw with a CoreUI renderer that will give us the correct 10.10
+ // style. Calling CUIDraw directly with [NSWindow coreUIRenderer] still
+ // renders 10.9-style widgets on 10.10.
+ [appearance _drawInRect:aRect context:cgContext options:aOptions];
+ } else {
+ // 10.9 and below
+ CUIRendererRef renderer = [NSWindow respondsToSelector:@selector(coreUIRenderer)]
+ ? [NSWindow coreUIRenderer] : nil;
+ CUIDraw(renderer, aRect, cgContext, (CFDictionaryRef)aOptions, NULL);
+ }
+}
+
+static float VerticalAlignFactor(nsIFrame *aFrame)
+{
+ if (!aFrame)
+ return 0.5f; // default: center
+
+ const nsStyleCoord& va = aFrame->StyleDisplay()->mVerticalAlign;
+ uint8_t intval = (va.GetUnit() == eStyleUnit_Enumerated)
+ ? va.GetIntValue()
+ : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
+ switch (intval) {
+ case NS_STYLE_VERTICAL_ALIGN_TOP:
+ case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
+ return 0.0f;
+
+ case NS_STYLE_VERTICAL_ALIGN_SUB:
+ case NS_STYLE_VERTICAL_ALIGN_SUPER:
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
+ case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
+ return 0.5f;
+
+ case NS_STYLE_VERTICAL_ALIGN_BASELINE:
+ case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
+ case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
+ return 1.0f;
+
+ default:
+ NS_NOTREACHED("invalid vertical-align");
+ return 0.5f;
+ }
+}
+
+// These are the sizes that Gecko needs to request to draw if it wants
+// to get a standard-sized Aqua radio button drawn. Note that the rects
+// that draw these are actually a little bigger.
+static const CellRenderSettings radioSettings = {
+ {
+ NSMakeSize(11, 11), // mini
+ NSMakeSize(13, 13), // small
+ NSMakeSize(16, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 1, 1, 1}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 2}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+static const CellRenderSettings checkboxSettings = {
+ {
+ NSMakeSize(11, 11), // mini
+ NSMakeSize(13, 13), // small
+ NSMakeSize(16, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 1, 0, 0}, // mini
+ {0, 1, 0, 1}, // small
+ {0, 1, 0, 1} // regular
+ },
+ { // Yosemite
+ {0, 1, 0, 0}, // mini
+ {0, 1, 0, 1}, // small
+ {0, 1, 0, 1} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
+ const HIRect& inBoxRect, bool inSelected,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
+ NSCellStateValue state = inSelected ? NSOnState : NSOffState;
+
+ // Check if we have an indeterminate checkbox
+ if (inCheckbox && GetIndeterminate(aFrame))
+ state = NSMixedState;
+
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)];
+ [cell setState:state];
+ [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
+
+ // Ensure that the control is square.
+ float length = std::min(inBoxRect.size.width, inBoxRect.size.height);
+ HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
+ inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
+ length, length);
+
+ DrawCellWithSnapping(cell, cgContext, drawRect,
+ inCheckbox ? checkboxSettings : radioSettings,
+ VerticalAlignFactor(aFrame), mCellDrawView, NO);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings searchFieldSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(32, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame, EventStates inState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ ContextAwareSearchFieldCell* cell = mSearchFieldCell;
+ [cell setContext:aFrame];
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ // NOTE: this could probably use inState
+ [cell setShowsFirstResponder:IsFocused(aFrame)];
+
+ // When using the 10.11 SDK, the default string will be shown if we don't
+ // set the placeholder string.
+ [cell setPlaceholderString:@""];
+
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
+ VerticalAlignFactor(aFrame), mCellDrawView,
+ IsFrameRTL(aFrame));
+
+ [cell setContext:nullptr];
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const NSSize kCheckmarkSize = NSMakeSize(11, 11);
+static const NSSize kMenuarrowSize = NSMakeSize(9, 10);
+static const NSSize kMenuScrollArrowSize = NSMakeSize(10, 8);
+static NSString* kCheckmarkImage = @"MenuOnState";
+static NSString* kMenuarrowRightImage = @"MenuSubmenu";
+static NSString* kMenuarrowLeftImage = @"MenuSubmenuLeft";
+static NSString* kMenuDownScrollArrowImage = @"MenuScrollDown";
+static NSString* kMenuUpScrollArrowImage = @"MenuScrollUp";
+static const CGFloat kMenuIconIndent = 6.0f;
+
+void
+nsNativeThemeCocoa::DrawMenuIcon(CGContextRef cgContext, const CGRect& aRect,
+ EventStates inState, nsIFrame* aFrame,
+ const NSSize& aIconSize, NSString* aImageName,
+ bool aCenterHorizontally)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ // Adjust size and position of our drawRect.
+ CGFloat paddingX = std::max(CGFloat(0.0), aRect.size.width - aIconSize.width);
+ CGFloat paddingY = std::max(CGFloat(0.0), aRect.size.height - aIconSize.height);
+ CGFloat paddingStartX = std::min(paddingX, kMenuIconIndent);
+ CGFloat paddingEndX = std::max(CGFloat(0.0), paddingX - kMenuIconIndent);
+ CGRect drawRect = CGRectMake(
+ aRect.origin.x + (aCenterHorizontally ? ceil(paddingX / 2) :
+ IsFrameRTL(aFrame) ? paddingEndX : paddingStartX),
+ aRect.origin.y + ceil(paddingY / 2),
+ aIconSize.width, aIconSize.height);
+
+ NSString* state = IsDisabled(aFrame, inState) ? @"disabled" :
+ (CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ? @"pressed" : @"normal");
+
+ NSString* imageName = aImageName;
+ if (!nsCocoaFeatures::OnElCapitanOrLater()) {
+ // Pre-10.11, image names are prefixed with "image."
+ imageName = [@"image." stringByAppendingString:aImageName];
+ }
+
+ RenderWithCoreUI(drawRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIBackgroundTypeMenu", @"backgroundTypeKey",
+ imageName, @"imageNameKey",
+ state, @"state",
+ @"image", @"widget",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil]);
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, drawRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const NSSize kHelpButtonSize = NSMakeSize(20, 20);
+static const NSSize kDisclosureButtonSize = NSMakeSize(21, 21);
+
+static const CellRenderSettings pushButtonSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(26, 0), // small
+ NSMakeSize(30, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {4, 0, 4, 1}, // small
+ {5, 0, 5, 2} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {4, 0, 4, 1}, // small
+ {5, 0, 5, 2} // regular
+ }
+ }
+};
+
+// The height at which we start doing square buttons instead of rounded buttons
+// Rounded buttons look bad if drawn at a height greater than 26, so at that point
+// we switch over to doing square buttons which looks fine at any size.
+#define DO_SQUARE_BUTTON_HEIGHT 26
+
+void
+nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame, float aOriginalHeight)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ BOOL isDisabled = IsDisabled(aFrame, inState);
+
+ NSButtonCell* cell = (aWidgetType == NS_THEME_BUTTON) ? mPushButtonCell :
+ (aWidgetType == NS_THEME_MAC_HELP_BUTTON) ? mHelpButtonCell : mDisclosureButtonCell;
+ [cell setEnabled:!isDisabled];
+ [cell setHighlighted:isActive &&
+ inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
+ [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive];
+
+ if (aWidgetType != NS_THEME_BUTTON) { // Help button or disclosure button.
+ NSSize buttonSize = NSMakeSize(0, 0);
+ if (aWidgetType == NS_THEME_MAC_HELP_BUTTON) {
+ buttonSize = kHelpButtonSize;
+ } else { // Disclosure button.
+ buttonSize = kDisclosureButtonSize;
+ [cell setState:(aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED) ? NSOffState : NSOnState];
+ }
+
+ DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
+ NSZeroSize, buttonSize, NULL, mCellDrawView,
+ false); // Don't mirror icon in RTL.
+ } else {
+ // If the button is tall enough, draw the square button style so that
+ // buttons with non-standard content look good. Otherwise draw normal
+ // rounded aqua buttons.
+ // This comparison is done based on the height that is calculated without
+ // the top, because the snapped height can be affected by the top of the
+ // rect and that may result in different height depending on the top value.
+ if (aOriginalHeight > DO_SQUARE_BUTTON_HEIGHT) {
+ [cell setBezelStyle:NSShadowlessSquareBezelStyle];
+ DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize,
+ NSZeroSize, NSMakeSize(14, 0), NULL, mCellDrawView,
+ IsFrameRTL(aFrame));
+ } else {
+ [cell setBezelStyle:NSRoundedBezelStyle];
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, pushButtonSettings, 0.5f,
+ mCellDrawView, IsFrameRTL(aFrame), 1.0f);
+ }
+ }
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawFocusOutline(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeFrameDrawInfo fdi;
+ fdi.version = 0;
+ fdi.kind = kHIThemeFrameTextFieldSquare;
+ fdi.state = kThemeStateActive;
+ fdi.isFocused = TRUE;
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ HIThemeDrawFrame(&inBoxRect, &fdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
+
+static void
+RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
+ RenderHIThemeControlFunction aFunc, void* aData,
+ BOOL mirrorHorizontally = NO)
+{
+ CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
+ CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
+
+ bool drawDirect;
+ HIRect drawRect = aRect;
+ drawRect.origin = CGPointZero;
+
+ if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
+ savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
+ drawDirect = TRUE;
+ } else {
+ drawDirect = FALSE;
+ }
+
+ // Fall back to no bitmap buffer if the area of our control (in pixels^2)
+ // is too large.
+ if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
+ aFunc(aCGContext, drawRect, aData);
+ } else {
+ // Inflate the buffer to capture focus rings.
+ int w = ceil(drawRect.size.width) + 2 * kMaxFocusRingWidth;
+ int h = ceil(drawRect.size.height) + 2 * kMaxFocusRingWidth;
+
+ int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
+ w * backingScaleFactor,
+ h * backingScaleFactor,
+ 8,
+ w * backingScaleFactor * 4,
+ colorSpace,
+ kCGImageAlphaPremultipliedFirst);
+ CGColorSpaceRelease(colorSpace);
+
+ CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
+ CGContextTranslateCTM(bitmapctx, kMaxFocusRingWidth, kMaxFocusRingWidth);
+
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(bitmapctx, CGAffineTransformMakeScale(backingScaleFactor, backingScaleFactor));
+
+ // HITheme always wants to draw into a flipped context, or things
+ // get confused.
+ CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
+ CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
+
+ aFunc(bitmapctx, drawRect, aData);
+
+ CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
+
+ CGAffineTransform ctm = CGContextGetCTM(aCGContext);
+
+ // We need to unflip, so that we can do a DrawImage without getting a flipped image.
+ CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
+ CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
+
+ if (mirrorHorizontally) {
+ CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
+ CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
+ }
+
+ HIRect inflatedDrawRect = CGRectMake(-kMaxFocusRingWidth, -kMaxFocusRingWidth, w, h);
+ CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
+
+ CGContextSetCTM(aCGContext, ctm);
+
+ CGImageRelease(bitmap);
+ CGContextRelease(bitmapctx);
+ }
+
+ CGContextSetCTM(aCGContext, savedCTM);
+}
+
+static void
+RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
+{
+ HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
+ HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
+}
+
+void
+nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, bool inIsDefault,
+ ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ BOOL isDisabled = IsDisabled(aFrame, inState);
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = inValue;
+ bdi.adornment = inAdornment;
+
+ if (isDisabled) {
+ bdi.state = kThemeStateUnavailable;
+ }
+ else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
+ bdi.state = kThemeStatePressed;
+ }
+ else {
+ if (inKind == kThemeArrowButton)
+ bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
+ else if (!isActive && inKind == kThemeListHeaderButton)
+ bdi.state = kThemeStateInactive;
+ else
+ bdi.state = kThemeStateActive;
+ }
+
+ if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive)
+ bdi.adornment |= kThemeAdornmentFocus;
+
+ if (inIsDefault && !isDisabled &&
+ !inState.HasState(NS_EVENT_STATE_ACTIVE)) {
+ bdi.adornment |= kThemeAdornmentDefault;
+ bdi.animation.time.start = 0;
+ bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
+ }
+
+ HIRect drawFrame = inBoxRect;
+
+ if (inKind == kThemePushButton) {
+ drawFrame.size.height -= 2;
+ if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
+ bdi.kind = kThemePushButtonMini;
+ }
+ else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
+ bdi.kind = kThemePushButtonSmall;
+ drawFrame.origin.y -= 1;
+ drawFrame.origin.x += 1;
+ drawFrame.size.width -= 2;
+ }
+ }
+ else if (inKind == kThemeListHeaderButton) {
+ CGContextClipToRect(cgContext, inBoxRect);
+ // Always remove the top border.
+ drawFrame.origin.y -= 1;
+ drawFrame.size.height += 1;
+ // Remove the left border in LTR mode and the right border in RTL mode.
+ drawFrame.size.width += 1;
+ bool isLast = IsLastTreeHeaderCell(aFrame);
+ if (isLast)
+ drawFrame.size.width += 1; // Also remove the other border.
+ if (!IsFrameRTL(aFrame) || isLast)
+ drawFrame.origin.x -= 1;
+ }
+
+ RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
+ IsFrameRTL(aFrame));
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings dropdownSettings = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 19), // small
+ NSMakeSize(0, 22) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {1, 1, 2, 1}, // mini
+ {3, 0, 3, 1}, // small
+ {3, 0, 3, 0} // regular
+ },
+ { // Yosemite
+ {1, 1, 2, 1}, // mini
+ {3, 0, 3, 1}, // small
+ {3, 0, 3, 0} // regular
+ }
+ }
+};
+
+static const CellRenderSettings editableMenulistSettings = {
+ {
+ NSMakeSize(0, 15), // mini
+ NSMakeSize(0, 18), // small
+ NSMakeSize(0, 21) // regular
+ },
+ {
+ NSMakeSize(18, 0), // mini
+ NSMakeSize(38, 0), // small
+ NSMakeSize(44, 0) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 2, 2}, // mini
+ {0, 0, 3, 2}, // small
+ {0, 1, 3, 3} // regular
+ },
+ { // Yosemite
+ {0, 0, 2, 2}, // mini
+ {0, 0, 3, 2}, // small
+ {0, 1, 3, 3} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, uint8_t aWidgetType,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
+
+ BOOL isEditable = (aWidgetType == NS_THEME_MENULIST_TEXTFIELD);
+ NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
+
+ [cell setEnabled:!IsDisabled(aFrame, inState)];
+ [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))];
+ [cell setHighlighted:IsOpenButton(aFrame)];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
+
+ const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
+ DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
+ 0.5f, mCellDrawView, IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings spinnerSettings = {
+ {
+ NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
+ NSMakeSize(15, 22), // small
+ NSMakeSize(19, 27) // regular
+ },
+ {
+ NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border)
+ NSMakeSize(15, 22), // small
+ NSMakeSize(19, 27) // regular
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {0, 0, 0, 0}, // small
+ {0, 0, 0, 0} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
+ const HIRect& inBoxRect, ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ EventStates inState, nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = kThemeButtonOff;
+ bdi.adornment = inAdornment;
+
+ if (IsDisabled(aFrame, inState))
+ bdi.state = kThemeStateUnavailable;
+ else
+ bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
+
+ HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext,
+ ThemeButtonKind inKind,
+ const HIRect& inBoxRect,
+ ThemeDrawState inDrawState,
+ ThemeButtonAdornment inAdornment,
+ EventStates inState,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UPBUTTON ||
+ aWidgetType == NS_THEME_SPINNER_DOWNBUTTON);
+
+ HIThemeButtonDrawInfo bdi;
+ bdi.version = 0;
+ bdi.kind = inKind;
+ bdi.value = kThemeButtonOff;
+ bdi.adornment = inAdornment;
+
+ if (IsDisabled(aFrame, inState))
+ bdi.state = kThemeStateUnavailable;
+ else
+ bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
+
+ // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons
+ // together as a single unit (presumably because when one button is active,
+ // the appearance of both changes (in different ways)). Here we have to paint
+ // both buttons, using clip to hide the one we don't want to paint.
+ HIRect drawRect = inBoxRect;
+ drawRect.size.height *= 2;
+ if (aWidgetType == NS_THEME_SPINNER_DOWNBUTTON) {
+ drawRect.origin.y -= inBoxRect.size.height;
+ }
+
+ // Shift the drawing a little to the left, since cocoa paints with more
+ // blank space around the visual buttons than we'd like:
+ drawRect.origin.x -= 1;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
+ const HIRect& inBoxRect, bool inDisabled,
+ EventStates inState)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeFrameDrawInfo fdi;
+ fdi.version = 0;
+ fdi.kind = inKind;
+
+ // We don't ever set an inactive state for this because it doesn't
+ // look right (see other apps).
+ fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive;
+
+ // for some reason focus rings on listboxes draw incorrectly
+ if (inKind == kHIThemeFrameListBox)
+ fdi.isFocused = 0;
+ else
+ fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
+
+ // HIThemeDrawFrame takes the rect for the content area of the frame, not
+ // the bounding rect for the frame. Here we reduce the size of the rect we
+ // will pass to make it the size of the content.
+ HIRect drawRect = inBoxRect;
+ if (inKind == kHIThemeFrameTextFieldSquare) {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
+ drawRect.origin.x += frameOutset;
+ drawRect.origin.y += frameOutset;
+ drawRect.size.width -= frameOutset * 2;
+ drawRect.size.height -= frameOutset * 2;
+ }
+ else if (inKind == kHIThemeFrameListBox) {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
+ drawRect.origin.x += frameOutset;
+ drawRect.origin.y += frameOutset;
+ drawRect.size.width -= frameOutset * 2;
+ drawRect.size.height -= frameOutset * 2;
+ }
+
+#if DRAW_IN_FRAME_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
+ CGContextFillRect(cgContext, inBoxRect);
+#endif
+
+ HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings progressSettings[2][2] = {
+ // Vertical progress bar.
+ {
+ // Determined settings.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(10, 0), // small
+ NSMakeSize(16, 0) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+ },
+ // There is no horizontal margin in regular undetermined size.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(10, 0), // small
+ NSMakeSize(16, 0) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 0, 1, 0} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 0, 1, 0} // regular
+ }
+ }
+ }
+ },
+ // Horizontal progress bar.
+ {
+ // Determined settings.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(0, 10), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+ },
+ // There is no horizontal margin in regular undetermined size.
+ {
+ {
+ NSZeroSize, // mini
+ NSMakeSize(0, 10), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {0, 1, 0, 1} // regular
+ },
+ { // Yosemite
+ {0, 0, 0, 0}, // mini
+ {1, 1, 1, 1}, // small
+ {0, 1, 0, 1} // regular
+ }
+ }
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
+ bool inIsIndeterminate, bool inIsHorizontal,
+ double inValue, double inMaxValue,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ NSProgressBarCell* cell = mProgressBarCell;
+
+ [cell setValue:inValue];
+ [cell setMax:inMaxValue];
+ [cell setIndeterminate:inIsIndeterminate];
+ [cell setHorizontal:inIsHorizontal];
+ [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint]
+ : NSClearControlTint)];
+
+ DrawCellWithSnapping(cell, cgContext, inBoxRect,
+ progressSettings[inIsHorizontal][inIsIndeterminate],
+ VerticalAlignFactor(aFrame), mCellDrawView,
+ IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static const CellRenderSettings meterSetting = {
+ {
+ NSMakeSize(0, 16), // mini
+ NSMakeSize(0, 16), // small
+ NSMakeSize(0, 16) // regular
+ },
+ {
+ NSZeroSize, NSZeroSize, NSZeroSize
+ },
+ {
+ { // Leopard
+ {1, 1, 1, 1}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ },
+ { // Yosemite
+ {1, 1, 1, 1}, // mini
+ {1, 1, 1, 1}, // small
+ {1, 1, 1, 1} // regular
+ }
+ }
+};
+
+void
+nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK
+
+ NS_PRECONDITION(aFrame, "aFrame should not be null here!");
+
+ // When using -moz-meterbar on an non meter element, we will not be able to
+ // get all the needed information so we just draw an empty meter.
+ nsIContent* content = aFrame->GetContent();
+ if (!(content && content->IsHTMLElement(nsGkAtoms::meter))) {
+ DrawCellWithSnapping(mMeterBarCell, cgContext, inBoxRect,
+ meterSetting, VerticalAlignFactor(aFrame),
+ mCellDrawView, IsFrameRTL(aFrame));
+ return;
+ }
+
+ HTMLMeterElement* meterElement = static_cast<HTMLMeterElement*>(content);
+ double value = meterElement->Value();
+ double min = meterElement->Min();
+ double max = meterElement->Max();
+
+ NSLevelIndicatorCell* cell = mMeterBarCell;
+
+ [cell setMinValue:min];
+ [cell setMaxValue:max];
+ [cell setDoubleValue:value];
+
+ /**
+ * The way HTML and Cocoa defines the meter/indicator widget are different.
+ * So, we are going to use a trick to get the Cocoa widget showing what we
+ * are expecting: we set the warningValue or criticalValue to the current
+ * value when we want to have the widget to be in the warning or critical
+ * state.
+ */
+ EventStates states = aFrame->GetContent()->AsElement()->State();
+
+ // Reset previously set warning and critical values.
+ [cell setWarningValue:max+1];
+ [cell setCriticalValue:max+1];
+
+ if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
+ [cell setWarningValue:value];
+ } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
+ [cell setCriticalValue:value];
+ }
+
+ HIRect rect = CGRectStandardize(inBoxRect);
+ BOOL vertical = IsVerticalMeter(aFrame);
+
+ CGContextSaveGState(cgContext);
+
+ if (vertical) {
+ /**
+ * Cocoa doesn't provide a vertical meter bar so to show one, we have to
+ * show a rotated horizontal meter bar.
+ * Given that we want to show a vertical meter bar, we assume that the rect
+ * has vertical dimensions but we can't correctly draw a meter widget inside
+ * such a rectangle so we need to inverse width and height (and re-position)
+ * to get a rectangle with horizontal dimensions.
+ * Finally, we want to show a vertical meter so we want to rotate the result
+ * so it is vertical. We do that by changing the context.
+ */
+ CGFloat tmp = rect.size.width;
+ rect.size.width = rect.size.height;
+ rect.size.height = tmp;
+ rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
+ rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
+
+ CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
+ CGContextRotateCTM(cgContext, -M_PI / 2.f);
+ CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
+ }
+
+ DrawCellWithSnapping(cell, cgContext, rect,
+ meterSetting, VerticalAlignFactor(aFrame),
+ mCellDrawView, !vertical && IsFrameRTL(aFrame));
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK
+}
+
+void
+nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeTabPaneDrawInfo tpdi;
+
+ tpdi.version = 1;
+ tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
+ tpdi.direction = kThemeTabNorth;
+ tpdi.size = kHIThemeTabSizeNormal;
+ tpdi.kind = kHIThemeTabKindNormal;
+
+ HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, bool inIsVertical,
+ bool inIsReverse, int32_t inCurrentValue,
+ int32_t inMinValue, int32_t inMaxValue,
+ nsIFrame* aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeTrackDrawInfo tdi;
+
+ tdi.version = 0;
+ tdi.kind = kThemeMediumSlider;
+ tdi.bounds = inBoxRect;
+ tdi.min = inMinValue;
+ tdi.max = inMaxValue;
+ tdi.value = inCurrentValue;
+ tdi.attributes = kThemeTrackShowThumb;
+ if (!inIsVertical)
+ tdi.attributes |= kThemeTrackHorizontal;
+ if (inIsReverse)
+ tdi.attributes |= kThemeTrackRightToLeft;
+ if (inState.HasState(NS_EVENT_STATE_FOCUS))
+ tdi.attributes |= kThemeTrackHasFocus;
+ if (IsDisabled(aFrame, inState))
+ tdi.enableState = kThemeTrackDisabled;
+ else
+ tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
+ tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
+ tdi.trackInfo.slider.pressState = 0;
+
+ HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+nsIFrame*
+nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter)
+{
+ // Usually a separator is drawn by the segment to the right of the
+ // separator, but pressed and selected segments have higher priority.
+ if (!aBefore || !aAfter)
+ return nullptr;
+ if (IsSelectedButton(aAfter))
+ return aAfter;
+ if (IsSelectedButton(aBefore) || IsPressedButton(aBefore))
+ return aBefore;
+ return aAfter;
+}
+
+CGRect
+nsNativeThemeCocoa::SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
+ nsIFrame* aCurrent, nsIFrame* aRight)
+{
+ // A separator between two segments should always be located in the leftmost
+ // pixel column of the segment to the right of the separator, regardless of
+ // who ends up drawing it.
+ // CoreUI draws the separators inside the drawing rect.
+ if (aLeft && SeparatorResponsibility(aLeft, aCurrent) == aLeft) {
+ // The left button draws the separator, so we need to make room for it.
+ aRect.origin.x += 1;
+ aRect.size.width -= 1;
+ }
+ if (SeparatorResponsibility(aCurrent, aRight) == aCurrent) {
+ // We draw the right separator, so we need to extend the draw rect into the
+ // segment to our right.
+ aRect.size.width += 1;
+ }
+ return aRect;
+}
+
+static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast)
+{
+ if (aIsFirst) {
+ if (aIsLast)
+ return @"kCUISegmentPositionOnly";
+ return @"kCUISegmentPositionFirst";
+ }
+ if (aIsLast)
+ return @"kCUISegmentPositionLast";
+ return @"kCUISegmentPositionMiddle";
+}
+
+struct SegmentedControlRenderSettings {
+ const CGFloat* heights;
+ const NSString* widgetName;
+ const BOOL ignoresPressedWhenSelected;
+ const BOOL isToolbarControl;
+};
+
+static const CGFloat tabHeights[3] = { 17, 20, 23 };
+
+static const SegmentedControlRenderSettings tabRenderSettings = {
+ tabHeights, @"tab", YES, NO
+};
+
+static const CGFloat toolbarButtonHeights[3] = { 15, 18, 22 };
+
+static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
+ toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve", NO, YES
+};
+
+void
+nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
+ EventStates inState, nsIFrame* aFrame,
+ const SegmentedControlRenderSettings& aSettings)
+{
+ BOOL isActive = IsActive(aFrame, aSettings.isToolbarControl);
+ BOOL isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
+ BOOL isSelected = IsSelectedButton(aFrame);
+ BOOL isPressed = IsPressedButton(aFrame);
+ if (isSelected && aSettings.ignoresPressedWhenSelected) {
+ isPressed = NO;
+ }
+
+ BOOL isRTL = IsFrameRTL(aFrame);
+ nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
+ nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
+ CGRect drawRect = SeparatorAdjustedRect(inBoxRect, left, aFrame, right);
+ BOOL drawLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
+ BOOL drawRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
+ NSControlSize controlSize = FindControlSize(drawRect.size.height, aSettings.heights, 4.0f);
+
+ RenderWithCoreUI(drawRect, cgContext, [NSDictionary dictionaryWithObjectsAndKeys:
+ aSettings.widgetName, @"widget",
+ (isActive ? @"kCUIPresentationStateActiveKey" : @"kCUIPresentationStateInactive"), @"kCUIPresentationStateKey",
+ ToolbarButtonPosition(!left, !right), @"kCUIPositionKey",
+ [NSNumber numberWithBool:drawLeftSeparator], @"kCUISegmentLeadingSeparatorKey",
+ [NSNumber numberWithBool:drawRightSeparator], @"kCUISegmentTrailingSeparatorKey",
+ [NSNumber numberWithBool:isSelected], @"value",
+ (isPressed ? @"pressed" : (isActive ? @"normal" : @"inactive")), @"state",
+ [NSNumber numberWithBool:isFocused], @"focus",
+ CUIControlSizeForCocoaSize(controlSize), @"size",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ @"up", @"direction",
+ nil]);
+}
+
+void
+nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame* aFrame,
+ EventStates aButtonStates[])
+{
+ static nsIContent::AttrValuesArray attributeValues[] = {
+ &nsGkAtoms::scrollbarUpTop,
+ &nsGkAtoms::scrollbarDownTop,
+ &nsGkAtoms::scrollbarUpBottom,
+ &nsGkAtoms::scrollbarDownBottom,
+ nullptr
+ };
+
+ // Get the state of any scrollbar buttons in our child frames
+ for (nsIFrame *childFrame : aFrame->PrincipalChildList()) {
+ nsIContent *childContent = childFrame->GetContent();
+ if (!childContent) continue;
+ int32_t attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sbattr,
+ attributeValues, eCaseMatters);
+ if (attrIndex < 0) continue;
+
+ aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON);
+ }
+}
+
+nsIFrame*
+nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
+{
+ // Walk our parents to find a scrollbar frame
+ nsIFrame *scrollbarFrame = aFrame;
+ do {
+ if (scrollbarFrame->GetType() == nsGkAtoms::scrollbarFrame) break;
+ } while ((scrollbarFrame = scrollbarFrame->GetParent()));
+
+ // We return null if we can't find a parent scrollbar frame
+ return scrollbarFrame;
+}
+
+static bool
+ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
+{
+ if (![aWindow isKindOfClass:[ToolbarWindow class]])
+ return false;
+
+ ToolbarWindow* win = (ToolbarWindow*)aWindow;
+ float unifiedToolbarHeight = [win unifiedToolbarHeight];
+ return inBoxRect.origin.x == 0 &&
+ inBoxRect.size.width >= [win frame].size.width &&
+ CGRectGetMaxY(inBoxRect) <= unifiedToolbarHeight;
+}
+
+// By default, kCUIWidgetWindowFrame drawing draws rounded corners in the
+// upper corners. Depending on the context type, it fills the background in
+// the corners with black or leaves it transparent. Unfortunately, this corner
+// rounding interacts poorly with the window corner masking we apply during
+// titlebar drawing and results in small remnants of the corner background
+// appearing at the rounded edge.
+// So we draw square corners.
+static void
+DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped)
+{
+ // We extend the draw rect horizontally and clip away the rounded corners.
+ const CGFloat extendHorizontal = 10;
+ CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0);
+ CGContextSaveGState(aContext);
+ CGContextClipToRect(aContext, aRect);
+
+ RenderWithCoreUI(drawRect, aContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIWidgetWindowFrame", @"widget",
+ @"regularwin", @"windowtype",
+ (aIsMain ? @"normal" : @"inactive"), @"state",
+ [NSNumber numberWithDouble:aUnifiedHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
+ [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
+ [NSNumber numberWithBool:aIsFlipped], @"is.flipped",
+ nil]);
+
+ CGContextRestoreGState(aContext);
+}
+
+void
+nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
+ NSWindow* aWindow)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ CGFloat unifiedHeight = std::max([(ToolbarWindow*)aWindow unifiedToolbarHeight],
+ inBoxRect.size.height);
+ BOOL isMain = [aWindow isMainWindow];
+ CGFloat titlebarHeight = unifiedHeight - inBoxRect.size.height;
+ CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight,
+ inBoxRect.size.width, inBoxRect.size.height + titlebarHeight);
+ DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, unifiedHeight, isMain, YES);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
+ nsIFrame *aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ if (inBoxRect.size.height < 2.0f)
+ return;
+
+ CGContextSaveGState(cgContext);
+ CGContextClipToRect(cgContext, inBoxRect);
+
+ // kCUIWidgetWindowFrame draws a complete window frame with both title bar
+ // and bottom bar. We only want the bottom bar, so we extend the draw rect
+ // upwards to make space for the title bar, and then we clip it away.
+ CGRect drawRect = inBoxRect;
+ const int extendUpwards = 40;
+ drawRect.origin.y -= extendUpwards;
+ drawRect.size.height += extendUpwards;
+ RenderWithCoreUI(drawRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ @"kCUIWidgetWindowFrame", @"widget",
+ @"regularwin", @"windowtype",
+ (IsActive(aFrame, YES) ? @"normal" : @"inactive"), @"state",
+ [NSNumber numberWithInt:inBoxRect.size.height], @"kCUIWindowFrameBottomBarHeightKey",
+ [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawBottomBarSeparatorKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil]);
+
+ CGContextRestoreGState(cgContext);
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+void
+nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect,
+ CGFloat aUnifiedHeight, BOOL aIsMain, BOOL aIsFlipped)
+{
+ CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height);
+ DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain, aIsFlipped);
+}
+
+static void
+RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
+{
+ HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
+ HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
+}
+
+void
+nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
+ nsIFrame *aFrame)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+ HIThemeGrowBoxDrawInfo drawInfo;
+ drawInfo.version = 0;
+ drawInfo.state = kThemeStateActive;
+ drawInfo.kind = kHIThemeGrowBoxKindNormal;
+ drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
+ drawInfo.size = kHIThemeGrowBoxSizeNormal;
+
+ RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
+ IsFrameRTL(aFrame));
+
+ NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
+static void
+DrawVibrancyBackground(CGContextRef cgContext, CGRect inBoxRect,
+ nsIFrame* aFrame, nsITheme::ThemeGeometryType aThemeGeometryType,
+ int aCornerRadiusIfOpaque = 0)
+{
+ ChildView* childView = ChildViewForFrame(aFrame);
+ if (childView) {
+ NSRect rect = NSRectFromCGRect(inBoxRect);
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+ [NSGraphicsContext saveGraphicsState];
+
+ NSColor* fillColor = [childView vibrancyFillColorForThemeGeometryType:aThemeGeometryType];
+ if ([fillColor alphaComponent] == 1.0 && aCornerRadiusIfOpaque > 0) {
+ // The fillColor being opaque means that the system-wide pref "reduce
+ // transparency" is set. In that scenario, we still go through all the
+ // vibrancy rendering paths (VibrancyManager::SystemSupportsVibrancy()
+ // will still return true), but the result just won't look "vibrant".
+ // However, there's one unfortunate change of behavior that this pref
+ // has: It stops the window server from applying window masks. We use
+ // a window mask to get rounded corners on menus. So since the mask
+ // doesn't work in "reduce vibrancy" mode, we need to do our own rounded
+ // corner clipping here.
+ [[NSBezierPath bezierPathWithRoundedRect:rect
+ xRadius:aCornerRadiusIfOpaque
+ yRadius:aCornerRadiusIfOpaque] addClip];
+ }
+
+ [fillColor set];
+ NSRectFill(rect);
+
+ [NSGraphicsContext restoreGraphicsState];
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+}
+
+bool
+nsNativeThemeCocoa::IsParentScrollbarRolledOver(nsIFrame* aFrame)
+{
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ return nsLookAndFeel::UseOverlayScrollbars()
+ ? CheckBooleanAttr(scrollbarFrame, nsGkAtoms::hover)
+ : GetContentState(scrollbarFrame, NS_THEME_NONE).HasState(NS_EVENT_STATE_HOVER);
+}
+
+static bool
+IsHiDPIContext(nsPresContext* aContext)
+{
+ return nsPresContext::AppUnitsPerCSSPixel() >=
+ 2 * aContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
+
+ // setup to draw into the correct port
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+ gfx::Rect nativeDirtyRect = NSRectToRect(aDirtyRect, p2a);
+ gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
+ nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
+ float nativeWidgetHeight = round(nativeWidgetRect.Height());
+ nativeWidgetRect.Round();
+ if (nativeWidgetRect.IsEmpty())
+ return NS_OK; // Don't attempt to draw invisible widgets.
+
+ AutoRestoreTransform autoRestoreTransform(&aDrawTarget);
+
+ bool hidpi = IsHiDPIContext(aFrame->PresContext());
+ if (hidpi) {
+ // Use high-resolution drawing.
+ nativeWidgetRect.Scale(0.5f);
+ nativeWidgetHeight *= 0.5f;
+ nativeDirtyRect.Scale(0.5f);
+ aDrawTarget.SetTransform(aDrawTarget.GetTransform().PreScale(2.0f, 2.0f));
+ }
+
+ gfxQuartzNativeDrawing nativeDrawing(aDrawTarget, nativeDirtyRect);
+
+ CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
+ if (cgContext == nullptr) {
+ // The Quartz surface handles 0x0 surfaces by internally
+ // making all operations no-ops; there's no cgcontext created for them.
+ // Unfortunately, this means that callers that want to render
+ // directly to the CGContext need to be aware of this quirk.
+ return NS_OK;
+ }
+
+ if (hidpi) {
+ // Set the context's "base transform" to in order to get correctly-sized focus rings.
+ CGContextSetBaseCTM(cgContext, CGAffineTransformMakeScale(2, 2));
+ }
+
+#if 0
+ if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
+ fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
+ aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
+ fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
+ mat._11, mat._12, mat._21, mat._22, mat._31, mat._32);
+ fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
+ mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
+ CGAffineTransform mm = CGContextGetCTM(cgContext);
+ fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
+ mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
+ }
+#endif
+
+ CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
+ nativeWidgetRect.Width(), nativeWidgetRect.Height());
+
+#if 0
+ fprintf(stderr, " --> macRect %f %f %f %f\n",
+ macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
+ CGRect bounds = CGContextGetClipBoundingBox(cgContext);
+ fprintf(stderr, " --> clip bounds: %f %f %f %f\n",
+ bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
+
+ //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
+ //CGContextFillRect(cgContext, bounds);
+#endif
+
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+
+ switch (aWidgetType) {
+ case NS_THEME_DIALOG: {
+ if (IsWindowSheet(aFrame)) {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ HIThemeSetFill(kThemeBrushSheetBackgroundTransparent, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextFillRect(cgContext, macRect);
+ }
+ } else {
+ HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextFillRect(cgContext, macRect);
+ }
+
+ }
+ break;
+
+ case NS_THEME_MENUPOPUP:
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ DrawVibrancyBackground(cgContext, macRect, aFrame, eThemeGeometryTypeMenu, 4);
+ } else {
+ HIThemeMenuDrawInfo mdi;
+ memset(&mdi, 0, sizeof(mdi));
+ mdi.version = 0;
+ mdi.menuType = IsDisabled(aFrame, eventState) ?
+ static_cast<ThemeMenuType>(kThemeMenuTypeInactive) :
+ static_cast<ThemeMenuType>(kThemeMenuTypePopUp);
+
+ bool isLeftOfParent = false;
+ if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
+ mdi.menuType = kThemeMenuTypeHierarchical;
+ }
+
+ // The rounded corners draw outside the frame.
+ CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
+ macRect.size.width, macRect.size.height - 8);
+ HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
+ }
+ break;
+
+ case NS_THEME_MENUARROW: {
+ bool isRTL = IsFrameRTL(aFrame);
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kMenuarrowSize,
+ isRTL ? kMenuarrowLeftImage : kMenuarrowRightImage, true);
+ }
+ break;
+
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM: {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ bool isDisabled = IsDisabled(aFrame, eventState);
+ bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
+ HIThemeMenuItemDrawInfo drawInfo;
+ memset(&drawInfo, 0, sizeof(drawInfo));
+ drawInfo.version = 0;
+ drawInfo.itemType = kThemeMenuItemPlain;
+ drawInfo.state = (isDisabled ?
+ static_cast<ThemeMenuState>(kThemeMenuDisabled) :
+ isSelected ?
+ static_cast<ThemeMenuState>(kThemeMenuSelected) :
+ static_cast<ThemeMenuState>(kThemeMenuActive));
+
+ // XXX pass in the menu rect instead of always using the item rect
+ HIRect ignored;
+ HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
+ }
+
+ if (aWidgetType == NS_THEME_CHECKMENUITEM) {
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kCheckmarkSize, kCheckmarkImage, false);
+ }
+ }
+ break;
+
+ case NS_THEME_MENUSEPARATOR: {
+ ThemeMenuState menuState;
+ if (IsDisabled(aFrame, eventState)) {
+ menuState = kThemeMenuDisabled;
+ }
+ else {
+ menuState = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
+ kThemeMenuSelected : kThemeMenuActive;
+ }
+
+ HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
+ HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
+ }
+ break;
+
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ DrawMenuIcon(cgContext, macRect, eventState, aFrame, kMenuScrollArrowSize,
+ aWidgetType == NS_THEME_BUTTON_ARROW_UP ?
+ kMenuUpScrollArrowImage : kMenuDownScrollArrowImage, true);
+ break;
+
+ case NS_THEME_TOOLTIP:
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ DrawVibrancyBackground(cgContext, macRect, aFrame, ThemeGeometryTypeForWidget(aFrame, aWidgetType));
+ } else {
+ CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
+ CGContextFillRect(cgContext, macRect);
+ }
+ break;
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO: {
+ bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
+ DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
+ eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_BUTTON:
+ if (IsDefaultButton(aFrame)) {
+ // Check whether the default button is in a document that does not
+ // match the :-moz-window-inactive pseudoclass. This activeness check
+ // is different from the other "active window" checks in this file
+ // because we absolutely need the button's default button appearance to
+ // be in sync with its text color, and the text color is changed by
+ // such a :-moz-window-inactive rule. (That's because on 10.10 and up,
+ // default buttons in active windows have blue background and white
+ // text, and default buttons in inactive windows have white background
+ // and black text.)
+ EventStates docState = aFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ bool isInActiveWindow = !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+ if (!IsDisabled(aFrame, eventState) && isInActiveWindow &&
+ !QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
+ NS_WARNING("Unable to animate button!");
+ }
+ DrawButton(cgContext, kThemePushButton, macRect, isInActiveWindow,
+ kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
+ } else if (IsButtonTypeMenu(aFrame)) {
+ DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
+ } else {
+ DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame,
+ nativeWidgetHeight);
+ }
+ break;
+
+ case NS_THEME_FOCUS_OUTLINE:
+ DrawFocusOutline(cgContext, macRect, eventState, aWidgetType, aFrame);
+ break;
+
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame,
+ nativeWidgetHeight);
+ break;
+
+ case NS_THEME_BUTTON_BEVEL:
+ DrawButton(cgContext, kThemeMediumBevelButton, macRect,
+ IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
+ eventState, aFrame);
+ break;
+
+ case NS_THEME_SPINNER: {
+ nsIContent* content = aFrame->GetContent();
+ if (content->IsHTMLElement()) {
+ // In HTML the theming for the spin buttons is drawn individually into
+ // their own backgrounds instead of being drawn into the background of
+ // their spinner parent as it is for XUL.
+ break;
+ }
+ ThemeDrawState state = kThemeStateActive;
+ if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("up"), eCaseMatters)) {
+ state = kThemeStatePressedUp;
+ }
+ else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
+ NS_LITERAL_STRING("down"), eCaseMatters)) {
+ state = kThemeStatePressedDown;
+ }
+
+ DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
+ kThemeAdornmentNone, eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON: {
+ nsNumberControlFrame* numberControlFrame =
+ nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
+ if (numberControlFrame) {
+ ThemeDrawState state = kThemeStateActive;
+ if (numberControlFrame->SpinnerUpButtonIsDepressed()) {
+ state = kThemeStatePressedUp;
+ } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) {
+ state = kThemeStatePressedDown;
+ }
+ DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state,
+ kThemeAdornmentNone, eventState, aFrame, aWidgetType);
+ }
+ }
+ break;
+
+ case NS_THEME_TOOLBARBUTTON:
+ DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
+ break;
+
+ case NS_THEME_SEPARATOR: {
+ HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
+ HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
+ }
+ break;
+
+ case NS_THEME_TOOLBAR: {
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ if (ToolbarCanBeUnified(cgContext, macRect, win)) {
+ DrawUnifiedToolbar(cgContext, macRect, win);
+ break;
+ }
+ BOOL isMain = [win isMainWindow];
+ CGRect drawRect = macRect;
+
+ // top border
+ drawRect.size.height = 1.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
+
+ // background
+ drawRect.origin.y += drawRect.size.height;
+ drawRect.size.height = macRect.size.height - 2.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, isMain);
+
+ // bottom border
+ drawRect.origin.y += drawRect.size.height;
+ drawRect.size.height = 1.0f;
+ DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, isMain);
+ }
+ break;
+
+ case NS_THEME_WINDOW_TITLEBAR: {
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ BOOL isMain = [win isMainWindow];
+ float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]] ?
+ [(ToolbarWindow*)win unifiedToolbarHeight] : macRect.size.height;
+ DrawNativeTitlebar(cgContext, macRect, unifiedToolbarHeight, isMain, YES);
+ }
+ break;
+
+ case NS_THEME_STATUSBAR:
+ DrawStatusBar(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
+ break;
+
+ case NS_THEME_MENULIST_BUTTON:
+ DrawButton(cgContext, kThemeArrowButton, macRect, false, kThemeButtonOn,
+ kThemeAdornmentArrowDownArrow, eventState, aFrame);
+ break;
+
+ case NS_THEME_GROUPBOX: {
+ HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
+ HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
+ break;
+ }
+
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_NUMBER_INPUT:
+ // HIThemeSetFill is not available on 10.3
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+
+ // XUL textboxes set the native appearance on the containing box, while
+ // concrete focus is set on the html:input element within it. We can
+ // though, check the focused attribute of xul textboxes in this case.
+ // On Mac, focus rings are always shown for textboxes, so we do not need
+ // to check the window's focus ring state here
+ if (aFrame->GetContent()->IsXULElement() && IsFocused(aFrame)) {
+ eventState |= NS_EVENT_STATE_FOCUS;
+ }
+
+ DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect,
+ IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState);
+ break;
+
+ case NS_THEME_SEARCHFIELD:
+ DrawSearchField(cgContext, macRect, aFrame, eventState);
+ break;
+
+ case NS_THEME_PROGRESSBAR:
+ {
+ double value = GetProgressValue(aFrame);
+ double maxValue = GetProgressMaxValue(aFrame);
+ // Don't request repaints for scrollbars at 100% because those don't animate.
+ if (value < maxValue) {
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("Unable to animate progressbar!");
+ }
+ }
+ DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
+ !IsVerticalProgress(aFrame),
+ value, maxValue, aFrame);
+ break;
+ }
+
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
+ false, GetProgressValue(aFrame),
+ GetProgressMaxValue(aFrame), aFrame);
+ break;
+
+ case NS_THEME_METERBAR:
+ DrawMeter(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_METERCHUNK:
+ // Do nothing: progress and meter bars cases will draw chunks.
+ break;
+
+ case NS_THEME_TREETWISTY:
+ DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
+ kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
+ break;
+
+ case NS_THEME_TREETWISTYOPEN:
+ DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
+ kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
+ break;
+
+ case NS_THEME_TREEHEADERCELL: {
+ TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
+ DrawButton(cgContext, kThemeListHeaderButton, macRect, false,
+ sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
+ sortDirection == eTreeSortDirection_Ascending ?
+ kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);
+ }
+ break;
+
+ case NS_THEME_TREEITEM:
+ case NS_THEME_TREEVIEW:
+ // HIThemeSetFill is not available on 10.3
+ // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+ break;
+
+ case NS_THEME_TREEHEADER:
+ // do nothing, taken care of by individual header cells
+ case NS_THEME_TREEHEADERSORTARROW:
+ // do nothing, taken care of by treeview header
+ case NS_THEME_TREELINE:
+ // do nothing, these lines don't exist on macos
+ break;
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL: {
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
+ if (!maxpos)
+ maxpos = 100;
+
+ bool reverse = aFrame->GetContent()->
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ NS_LITERAL_STRING("reverse"), eCaseMatters);
+ DrawScale(cgContext, macRect, eventState,
+ (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
+ curpos, minpos, maxpos, aFrame);
+ }
+ break;
+
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+ // do nothing, drawn by scale
+ break;
+
+ case NS_THEME_RANGE: {
+ nsRangeFrame *rangeFrame = do_QueryFrame(aFrame);
+ if (!rangeFrame) {
+ break;
+ }
+ // DrawScale requires integer min, max and value. This is purely for
+ // drawing, so we normalize to a range 0-1000 here.
+ int32_t value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000);
+ int32_t min = 0;
+ int32_t max = 1000;
+ bool isVertical = !IsRangeHorizontal(aFrame);
+ bool reverseDir = isVertical || rangeFrame->IsRightToLeft();
+ DrawScale(cgContext, macRect, eventState, isVertical, reverseDir,
+ value, min, max, aFrame);
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR:
+ break;
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: {
+ BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
+ BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL);
+ BOOL isRolledOver = IsParentScrollbarRolledOver(aFrame);
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ bool isSmall = (scrollbarFrame && scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ if (isOverlay && !isRolledOver) {
+ if (isHorizontal) {
+ macRect.origin.y += 4;
+ macRect.size.height -= 4;
+ } else {
+ if (aFrame->StyleVisibility()->mDirection !=
+ NS_STYLE_DIRECTION_RTL) {
+ macRect.origin.x += 4;
+ }
+ macRect.size.width -= 4;
+ }
+ }
+ const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
+ NSMutableDictionary* options = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ (isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
+ (isSmall ? @"small" : @"regular"), @"size",
+ (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+ (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
+ [NSNumber numberWithBool:YES], @"indiconly",
+ [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil];
+ if (isRolledOver) {
+ [options setObject:@"rollover" forKey:@"state"];
+ }
+ RenderWithCoreUI(macRect, cgContext, options, true);
+ }
+ break;
+
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+#if SCROLLBARS_VISUAL_DEBUG
+ CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
+ CGContextFillRect(cgContext, macRect);
+#endif
+ break;
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+#if SCROLLBARS_VISUAL_DEBUG
+ CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
+ CGContextFillRect(cgContext, macRect);
+#endif
+ break;
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL: {
+ BOOL isOverlay = nsLookAndFeel::UseOverlayScrollbars();
+ if (!isOverlay || IsParentScrollbarRolledOver(aFrame)) {
+ BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL);
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ bool isSmall = (scrollbarFrame && scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame);
+ RenderWithCoreUI(macRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ (isOverlay ? @"kCUIWidgetOverlayScrollBar" : @"scrollbar"), @"widget",
+ (isSmall ? @"small" : @"regular"), @"size",
+ (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey",
+ (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey",
+ [NSNumber numberWithBool:YES], @"noindicator",
+ [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ nil],
+ true);
+ }
+ }
+ break;
+
+ case NS_THEME_TEXTFIELD_MULTILINE: {
+ // we have to draw this by hand because there is no HITheme value for it
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+
+ CGContextFillRect(cgContext, macRect);
+
+ // #737373 for the top border, #999999 for the rest.
+ float x = macRect.origin.x, y = macRect.origin.y;
+ float w = macRect.size.width, h = macRect.size.height;
+ CGContextSetRGBFillColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
+ CGContextSetRGBFillColor(cgContext, 0.6, 0.6, 0.6, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
+
+ // draw a focus ring
+ if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
+ CGContextSaveGState(cgContext);
+ NSSetFocusRingStyle(NSFocusRingOnly);
+ NSRectFill(NSRectFromCGRect(macRect));
+ CGContextRestoreGState(cgContext);
+ [NSGraphicsContext setCurrentContext:savedContext];
+ }
+ }
+ break;
+
+ case NS_THEME_LISTBOX: {
+ // We have to draw this by hand because kHIThemeFrameListBox drawing
+ // is buggy on 10.5, see bug 579259.
+ CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
+ CGContextFillRect(cgContext, macRect);
+
+ // #8E8E8E for the top border, #BEBEBE for the rest.
+ float x = macRect.origin.x, y = macRect.origin.y;
+ float w = macRect.size.width, h = macRect.size.height;
+ CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
+ CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
+ CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
+ CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
+ }
+ break;
+
+ case NS_THEME_MAC_SOURCE_LIST: {
+ if (VibrancyManager::SystemSupportsVibrancy()) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ CGGradientRef backgroundGradient;
+ CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
+ CGFloat activeGradientColors[8] = { 0.9137, 0.9294, 0.9490, 1.0,
+ 0.8196, 0.8471, 0.8784, 1.0 };
+ CGFloat inactiveGradientColors[8] = { 0.9686, 0.9686, 0.9686, 1.0,
+ 0.9216, 0.9216, 0.9216, 1.0 };
+ CGPoint start = macRect.origin;
+ CGPoint end = CGPointMake(macRect.origin.x,
+ macRect.origin.y + macRect.size.height);
+ BOOL isActive = FrameIsInActiveWindow(aFrame);
+ backgroundGradient =
+ CGGradientCreateWithColorComponents(rgb, isActive ? activeGradientColors
+ : inactiveGradientColors, NULL, 2);
+ CGContextDrawLinearGradient(cgContext, backgroundGradient, start, end, 0);
+ CGGradientRelease(backgroundGradient);
+ CGColorSpaceRelease(rgb);
+ }
+ }
+ break;
+
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION: {
+ // If we're in XUL tree, we need to rely on the source list's clear
+ // background display item. If we cleared the background behind the
+ // selections, the source list would not pick up the right font
+ // smoothing background. So, to simplify a bit, we only support vibrancy
+ // if we're in a source list.
+ if (VibrancyManager::SystemSupportsVibrancy() && IsInSourceList(aFrame)) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ } else {
+ BOOL isActiveSelection =
+ aWidgetType == NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION;
+ RenderWithCoreUI(macRect, cgContext,
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:isActiveSelection], @"focus",
+ [NSNumber numberWithBool:YES], @"is.flipped",
+ @"kCUIVariantGradientSideBarSelection", @"kCUIVariantKey",
+ (FrameIsInActiveWindow(aFrame) ? @"normal" : @"inactive"), @"state",
+ @"gradient", @"widget",
+ nil]);
+ }
+ }
+ break;
+
+ case NS_THEME_TAB:
+ DrawSegment(cgContext, macRect, eventState, aFrame, tabRenderSettings);
+ break;
+
+ case NS_THEME_TABPANELS:
+ DrawTabPanel(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_RESIZER:
+ DrawResizer(cgContext, macRect, aFrame);
+ break;
+
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK: {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ DrawVibrancyBackground(cgContext, macRect, aFrame, type);
+ break;
+ }
+ }
+
+ if (hidpi) {
+ // Reset the base CTM.
+ CGContextSetBaseCTM(cgContext, CGAffineTransformIdentity);
+ }
+
+ nativeDrawing.EndNativeDrawing();
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+nsIntMargin
+nsNativeThemeCocoa::DirectionAwareMargin(const nsIntMargin& aMargin,
+ nsIFrame* aFrame)
+{
+ // Assuming aMargin was originally specified for a horizontal LTR context,
+ // reinterpret the values as logical, and then map to physical coords
+ // according to aFrame's actual writing mode.
+ WritingMode wm = aFrame->GetWritingMode();
+ nsMargin m = LogicalMargin(wm, aMargin.top, aMargin.right, aMargin.bottom,
+ aMargin.left).GetPhysicalMargin(wm);
+ return nsIntMargin(m.top, m.right, m.bottom, m.left);
+}
+
+static const nsIntMargin kAquaDropdownBorder(1, 22, 2, 5);
+static const nsIntMargin kAquaComboboxBorder(3, 20, 3, 4);
+static const nsIntMargin kAquaSearchfieldBorder(3, 5, 2, 19);
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ aResult->SizeTo(0, 0, 0, 0);
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ {
+ if (IsButtonTypeMenu(aFrame)) {
+ *aResult = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
+ } else {
+ *aResult = DirectionAwareMargin(nsIntMargin(1, 7, 3, 7), aFrame);
+ }
+ break;
+ }
+
+ case NS_THEME_TOOLBARBUTTON:
+ {
+ *aResult = DirectionAwareMargin(nsIntMargin(1, 4, 1, 4), aFrame);
+ break;
+ }
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ {
+ // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
+ // assume a border width of 2px.
+ aResult->SizeTo(2, 2, 2, 2);
+ break;
+ }
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ *aResult = DirectionAwareMargin(kAquaDropdownBorder, aFrame);
+ break;
+
+ case NS_THEME_MENULIST_TEXTFIELD:
+ *aResult = DirectionAwareMargin(kAquaComboboxBorder, aFrame);
+ break;
+
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
+
+ SInt32 textPadding = 0;
+ ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
+
+ frameOutset += textPadding;
+
+ aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
+ break;
+ }
+
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ aResult->SizeTo(1, 1, 1, 1);
+ break;
+
+ case NS_THEME_SEARCHFIELD:
+ *aResult = DirectionAwareMargin(kAquaSearchfieldBorder, aFrame);
+ break;
+
+ case NS_THEME_LISTBOX:
+ {
+ SInt32 frameOutset = 0;
+ ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
+ aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
+ break;
+ }
+
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ {
+ bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL);
+ if (nsLookAndFeel::UseOverlayScrollbars()) {
+ if (!nsCocoaFeatures::OnYosemiteOrLater()) {
+ // Pre-10.10, we have to center the thumb rect in the middle of the
+ // scrollbar. Starting with 10.10, the expected rect for thumb
+ // rendering is the full width of the scrollbar.
+ if (isHorizontal) {
+ aResult->top = 2;
+ aResult->bottom = 1;
+ } else {
+ aResult->left = 2;
+ aResult->right = 1;
+ }
+ }
+ // Leave a bit of space at the start and the end on all OS X versions.
+ if (isHorizontal) {
+ aResult->left = 1;
+ aResult->right = 1;
+ } else {
+ aResult->top = 1;
+ aResult->bottom = 1;
+ }
+ }
+
+ break;
+ }
+
+ case NS_THEME_STATUSBAR:
+ aResult->SizeTo(1, 0, 0, 0);
+ break;
+ }
+
+ if (IsHiDPIContext(aFrame->PresContext())) {
+ *aResult = *aResult + *aResult; // doubled
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+// Return false here to indicate that CSS padding values should be used. There is
+// no reason to make a distinction between padding and border values, just specify
+// whatever values you want in GetWidgetBorder and only use this to return true
+// if you want to override CSS padding values.
+bool
+nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nsIntMargin* aResult)
+{
+ // We don't want CSS padding being used for certain widgets.
+ // See bug 381639 for an example of why.
+ switch (aWidgetType) {
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ }
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ uint8_t aWidgetType, nsRect* aOverflowRect)
+{
+ int32_t p2a = aFrame->PresContext()->AppUnitsPerDevPixel();
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_RADIO:
+ case NS_THEME_TAB:
+ {
+ // We assume that the above widgets can draw a focus ring that will be less than
+ // or equal to 4 pixels thick.
+ nsIntMargin extraSize = nsIntMargin(kMaxFocusRingWidth,
+ kMaxFocusRingWidth,
+ kMaxFocusRingWidth,
+ kMaxFocusRingWidth);
+ nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
+ NSIntPixelsToAppUnits(extraSize.right, p2a),
+ NSIntPixelsToAppUnits(extraSize.bottom, p2a),
+ NSIntPixelsToAppUnits(extraSize.left, p2a));
+ aOverflowRect->Inflate(m);
+ return true;
+ }
+ case NS_THEME_PROGRESSBAR:
+ {
+ // Progress bars draw a 2 pixel white shadow under their progress indicators
+ nsMargin m(0, 0, NSIntPixelsToAppUnits(2, p2a), 0);
+ aOverflowRect->Inflate(m);
+ return true;
+ }
+ case NS_THEME_FOCUS_OUTLINE:
+ {
+ aOverflowRect->Inflate(NSIntPixelsToAppUnits(2, p2a));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static const int32_t kRegularScrollbarThumbMinSize = 26;
+static const int32_t kSmallScrollbarThumbMinSize = 26;
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable)
+{
+ NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+ aResult->SizeTo(0,0);
+ *aIsOverridable = true;
+
+ switch (aWidgetType) {
+ case NS_THEME_BUTTON:
+ {
+ aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
+ pushButtonSettings.naturalSizes[miniControlSize].height);
+ break;
+ }
+
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ {
+ aResult->SizeTo(kMenuScrollArrowSize.width, kMenuScrollArrowSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MENUARROW:
+ {
+ aResult->SizeTo(kMenuarrowSize.width, kMenuarrowSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ {
+ aResult->SizeTo(kDisclosureButtonSize.width, kDisclosureButtonSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_HELP_BUTTON:
+ {
+ aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_TOOLBARBUTTON:
+ {
+ aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
+ break;
+ }
+
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ {
+ SInt32 buttonHeight = 0, buttonWidth = 0;
+ if (aFrame->GetContent()->IsXULElement()) {
+ ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
+ ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
+ } else {
+ NSSize size =
+ spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)];
+ buttonWidth = size.width;
+ buttonHeight = size.height;
+ if (aWidgetType != NS_THEME_SPINNER) {
+ // the buttons are half the height of the spinner
+ buttonHeight /= 2;
+ }
+ }
+ aResult->SizeTo(buttonWidth, buttonHeight);
+ *aIsOverridable = true;
+ break;
+ }
+
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ {
+ SInt32 popupHeight = 0;
+ ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
+ aResult->SizeTo(0, popupHeight);
+ break;
+ }
+
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ {
+ // at minimum, we should be tall enough for 9pt text.
+ // I'm using hardcoded values here because the appearance manager
+ // values for the frame size are incorrect.
+ aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
+ break;
+ }
+
+ case NS_THEME_WINDOW_BUTTON_BOX: {
+ NSSize size = WindowButtonsSize(aFrame);
+ aResult->SizeTo(size.width, size.height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_MAC_FULLSCREEN_BUTTON: {
+ if ([NativeWindowForFrame(aFrame) respondsToSelector:@selector(toggleFullScreen:)] &&
+ !nsCocoaFeatures::OnYosemiteOrLater()) {
+ // This value is hardcoded because it's needed before we can measure the
+ // position and size of the fullscreen button.
+ aResult->SizeTo(16, 17);
+ }
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_PROGRESSBAR:
+ {
+ SInt32 barHeight = 0;
+ ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
+ aResult->SizeTo(0, barHeight);
+ break;
+ }
+
+ case NS_THEME_TREETWISTY:
+ case NS_THEME_TREETWISTYOPEN:
+ {
+ SInt32 twistyHeight = 0, twistyWidth = 0;
+ ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
+ ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
+ aResult->SizeTo(twistyWidth, twistyHeight);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_TREEHEADER:
+ case NS_THEME_TREEHEADERCELL:
+ {
+ SInt32 headerHeight = 0;
+ ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
+ aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
+ break;
+ }
+
+ case NS_THEME_TAB:
+ {
+ aResult->SizeTo(0, tabHeights[miniControlSize]);
+ break;
+ }
+
+ case NS_THEME_RANGE:
+ {
+ // The Mac Appearance Manager API (the old API we're currently using)
+ // doesn't define constants to obtain a minimum size for sliders. We use
+ // the "thickness" of a slider that has default dimensions for both the
+ // minimum width and height to get something sane and so that paint
+ // invalidation works.
+ SInt32 size = 0;
+ if (IsRangeHorizontal(aFrame)) {
+ ::GetThemeMetric(kThemeMetricHSliderHeight, &size);
+ } else {
+ ::GetThemeMetric(kThemeMetricVSliderWidth, &size);
+ }
+ aResult->SizeTo(size, size);
+ *aIsOverridable = true;
+ break;
+ }
+
+ case NS_THEME_RANGE_THUMB:
+ {
+ SInt32 width = 0;
+ SInt32 height = 0;
+ ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width);
+ ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height);
+ aResult->SizeTo(width, height);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ {
+ SInt32 scaleHeight = 0;
+ ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
+ aResult->SizeTo(scaleHeight, scaleHeight);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCALE_VERTICAL:
+ {
+ SInt32 scaleWidth = 0;
+ ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
+ aResult->SizeTo(scaleWidth, scaleWidth);
+ *aIsOverridable = false;
+ break;
+ }
+
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ {
+ // Find our parent scrollbar frame in order to find out whether we're in
+ // a small or a large scrollbar.
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame)
+ return NS_ERROR_FAILURE;
+
+ bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
+ bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL);
+ int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
+ minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ {
+ *aIsOverridable = false;
+
+ if (nsLookAndFeel::UseOverlayScrollbars()) {
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (scrollbarFrame &&
+ scrollbarFrame->StyleDisplay()->mAppearance ==
+ NS_THEME_SCROLLBAR_SMALL) {
+ aResult->SizeTo(14, 14);
+ }
+ else {
+ aResult->SizeTo(16, 16);
+ }
+ break;
+ }
+
+ // yeah, i know i'm cheating a little here, but i figure that it
+ // really doesn't matter if the scrollbar is vertical or horizontal
+ // and the width metric is a really good metric for every piece
+ // of the scrollbar.
+
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame) return NS_ERROR_FAILURE;
+
+ int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
+ kThemeMetricSmallScrollBarWidth :
+ kThemeMetricScrollBarWidth;
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth);
+ break;
+ }
+
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ {
+ int32_t themeMetric = kThemeMetricScrollBarWidth;
+
+ if (aFrame) {
+ nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (scrollbarFrame &&
+ scrollbarFrame->StyleDisplay()->mAppearance ==
+ NS_THEME_SCROLLBAR_SMALL) {
+ // XXX We're interested in the width of non-disappearing scrollbars
+ // to leave enough space for a dropmarker in non-native styled
+ // comboboxes (bug 869314). It isn't clear to me if comboboxes can
+ // ever have small scrollbars.
+ themeMetric = kThemeMetricSmallScrollBarWidth;
+ }
+ }
+
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth);
+ break;
+ }
+
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ {
+ nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
+ if (!scrollbarFrame) return NS_ERROR_FAILURE;
+
+ // Since there is no NS_THEME_SCROLLBARBUTTON_UP_SMALL we need to ask the parent what appearance style it has.
+ int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
+ kThemeMetricSmallScrollBarWidth :
+ kThemeMetricScrollBarWidth;
+ SInt32 scrollbarWidth = 0;
+ ::GetThemeMetric(themeMetric, &scrollbarWidth);
+
+ // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
+ if (aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT)
+ aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
+ else
+ aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
+
+ *aIsOverridable = false;
+ break;
+ }
+ case NS_THEME_RESIZER:
+ {
+ HIThemeGrowBoxDrawInfo drawInfo;
+ drawInfo.version = 0;
+ drawInfo.state = kThemeStateActive;
+ drawInfo.kind = kHIThemeGrowBoxKindNormal;
+ drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
+ drawInfo.size = kHIThemeGrowBoxSizeNormal;
+ HIPoint pnt = { 0, 0 };
+ HIRect bounds;
+ HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
+ aResult->SizeTo(bounds.size.width, bounds.size.height);
+ *aIsOverridable = false;
+ }
+ }
+
+ if (IsHiDPIContext(aPresContext)) {
+ *aResult = *aResult * 2;
+ }
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
+ nsIAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue)
+{
+ // Some widget types just never change state.
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_STATUSBARPANEL:
+ case NS_THEME_RESIZERPANEL:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TABPANEL:
+ case NS_THEME_DIALOG:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_METERBAR:
+ case NS_THEME_METERCHUNK:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ *aShouldRepaint = false;
+ return NS_OK;
+ }
+
+ // XXXdwh Not sure what can really be done here. Can at least guess for
+ // specific widgets that they're highly unlikely to have certain states.
+ // For example, a toolbar doesn't care about any states.
+ if (!aAttribute) {
+ // Hover/focus/active changed. Always repaint.
+ *aShouldRepaint = true;
+ } else {
+ // Check the attribute to see if it's relevant.
+ // disabled, checked, dlgtype, default, etc.
+ *aShouldRepaint = false;
+ if (aAttribute == nsGkAtoms::disabled ||
+ aAttribute == nsGkAtoms::checked ||
+ aAttribute == nsGkAtoms::selected ||
+ aAttribute == nsGkAtoms::visuallyselected ||
+ aAttribute == nsGkAtoms::menuactive ||
+ aAttribute == nsGkAtoms::sortDirection ||
+ aAttribute == nsGkAtoms::focused ||
+ aAttribute == nsGkAtoms::_default ||
+ aAttribute == nsGkAtoms::open ||
+ aAttribute == nsGkAtoms::hover)
+ *aShouldRepaint = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeCocoa::ThemeChanged()
+{
+ // This is unimplemented because we don't care if gecko changes its theme
+ // and Mac OS X doesn't have themes.
+ return NS_OK;
+}
+
+bool
+nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
+ // render natively even if native theme support is disabled.
+ if (aWidgetType != NS_THEME_SCROLLBAR &&
+ aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
+ return false;
+
+ // if this is a dropdown button in a combobox the answer is always no
+ if (aWidgetType == NS_THEME_MENULIST_BUTTON) {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (parentFrame && (parentFrame->GetType() == nsGkAtoms::comboboxControlFrame))
+ return false;
+ }
+
+ switch (aWidgetType) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case NS_THEME_MENULIST:
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_MENULIST_TEXT:
+ case NS_THEME_MENULIST_TEXTFIELD:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ MOZ_FALLTHROUGH;
+
+ case NS_THEME_LISTBOX:
+
+ case NS_THEME_DIALOG:
+ case NS_THEME_WINDOW:
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ case NS_THEME_WINDOW_TITLEBAR:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_MAC_FULLSCREEN_BUTTON:
+ case NS_THEME_TOOLTIP:
+
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_CHECKBOX_CONTAINER:
+ case NS_THEME_RADIO:
+ case NS_THEME_RADIO_CONTAINER:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ case NS_THEME_BUTTON:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_BUTTON_BEVEL:
+ case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_TOOLBAR:
+ case NS_THEME_STATUSBAR:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_SEARCHFIELD:
+ case NS_THEME_TOOLBOX:
+ //case NS_THEME_TOOLBARBUTTON:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_PROGRESSBAR_VERTICAL:
+ case NS_THEME_PROGRESSCHUNK:
+ case NS_THEME_PROGRESSCHUNK_VERTICAL:
+ case NS_THEME_METERBAR:
+ case NS_THEME_METERCHUNK:
+ case NS_THEME_SEPARATOR:
+
+ case NS_THEME_TABPANELS:
+ case NS_THEME_TAB:
+
+ case NS_THEME_TREETWISTY:
+ case NS_THEME_TREETWISTYOPEN:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TREEHEADER:
+ case NS_THEME_TREEHEADERCELL:
+ case NS_THEME_TREEHEADERSORTARROW:
+ case NS_THEME_TREEITEM:
+ case NS_THEME_TREELINE:
+ case NS_THEME_MAC_SOURCE_LIST:
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+
+ case NS_THEME_RANGE:
+
+ case NS_THEME_SCALE_HORIZONTAL:
+ case NS_THEME_SCALETHUMB_HORIZONTAL:
+ case NS_THEME_SCALE_VERTICAL:
+ case NS_THEME_SCALETHUMB_VERTICAL:
+
+ case NS_THEME_SCROLLBAR:
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBARBUTTON_UP:
+ case NS_THEME_SCROLLBARBUTTON_DOWN:
+ case NS_THEME_SCROLLBARBUTTON_LEFT:
+ case NS_THEME_SCROLLBARBUTTON_RIGHT:
+ case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
+ case NS_THEME_SCROLLBARTHUMB_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_VERTICAL:
+ case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
+ case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
+ return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
+
+ case NS_THEME_RESIZER:
+ {
+ nsIFrame* parentFrame = aFrame->GetParent();
+ if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame)
+ return true;
+
+ // Note that IsWidgetStyled is not called for resizers on Mac. This is
+ // because for scrollable containers, the native resizer looks better
+ // when (non-overlay) scrollbars are present even when the style is
+ // overriden, and the custom transparent resizer looks better when
+ // scrollbars are not present.
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
+ return (!nsLookAndFeel::UseOverlayScrollbars() &&
+ scrollFrame && scrollFrame->GetScrollbarVisibility());
+ }
+
+ case NS_THEME_FOCUS_OUTLINE:
+ return true;
+
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ return VibrancyManager::SystemSupportsVibrancy();
+ }
+
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::WidgetIsContainer(uint8_t aWidgetType)
+{
+ // flesh this out at some point
+ switch (aWidgetType) {
+ case NS_THEME_MENULIST_BUTTON:
+ case NS_THEME_RADIO:
+ case NS_THEME_CHECKBOX:
+ case NS_THEME_PROGRESSBAR:
+ case NS_THEME_METERBAR:
+ case NS_THEME_RANGE:
+ case NS_THEME_MAC_HELP_BUTTON:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN:
+ case NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED:
+ return false;
+ }
+ return true;
+}
+
+bool
+nsNativeThemeCocoa::ThemeDrawsFocusForWidget(uint8_t aWidgetType)
+{
+ if (aWidgetType == NS_THEME_MENULIST ||
+ aWidgetType == NS_THEME_MENULIST_TEXTFIELD ||
+ aWidgetType == NS_THEME_BUTTON ||
+ aWidgetType == NS_THEME_MAC_HELP_BUTTON ||
+ aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_OPEN ||
+ aWidgetType == NS_THEME_MAC_DISCLOSURE_BUTTON_CLOSED ||
+ aWidgetType == NS_THEME_RADIO ||
+ aWidgetType == NS_THEME_RANGE ||
+ aWidgetType == NS_THEME_CHECKBOX)
+ return true;
+
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
+{
+ return false;
+}
+
+bool
+nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_DIALOG:
+ case NS_THEME_GROUPBOX:
+ case NS_THEME_TABPANELS:
+ case NS_THEME_BUTTON_ARROW_UP:
+ case NS_THEME_BUTTON_ARROW_DOWN:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUARROW:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_MENUSEPARATOR:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_SPINNER:
+ case NS_THEME_SPINNER_UPBUTTON:
+ case NS_THEME_SPINNER_DOWNBUTTON:
+ case NS_THEME_SEPARATOR:
+ case NS_THEME_TOOLBOX:
+ case NS_THEME_NUMBER_INPUT:
+ case NS_THEME_TEXTFIELD:
+ case NS_THEME_TREEVIEW:
+ case NS_THEME_TREELINE:
+ case NS_THEME_TEXTFIELD_MULTILINE:
+ case NS_THEME_LISTBOX:
+ case NS_THEME_RESIZER:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool
+nsNativeThemeCocoa::IsWindowSheet(nsIFrame* aFrame)
+{
+ NSWindow* win = NativeWindowForFrame(aFrame);
+ id winDelegate = [win delegate];
+ nsIWidget* widget = [(WindowDelegate *)winDelegate geckoWidget];
+ if (!widget) {
+ return false;
+ }
+ return (widget->WindowType() == eWindowType_sheet);
+}
+
+bool
+nsNativeThemeCocoa::NeedToClearBackgroundBehindWidget(nsIFrame* aFrame,
+ uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MAC_SOURCE_LIST:
+ // If we're in a XUL tree, we don't want to clear the background behind the
+ // selections below, since that would make our source list to not pick up
+ // the right font smoothing background. But since we don't call this method
+ // in nsTreeBodyFrame::BuildDisplayList, we never get here.
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ return true;
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame);
+ default:
+ return false;
+ }
+}
+
+static nscolor ConvertNSColor(NSColor* aColor)
+{
+ NSColor* deviceColor = [aColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ return NS_RGBA((unsigned int)([deviceColor redComponent] * 255.0),
+ (unsigned int)([deviceColor greenComponent] * 255.0),
+ (unsigned int)([deviceColor blueComponent] * 255.0),
+ (unsigned int)([deviceColor alphaComponent] * 255.0));
+}
+
+bool
+nsNativeThemeCocoa::WidgetProvidesFontSmoothingBackgroundColor(nsIFrame* aFrame,
+ uint8_t aWidgetType,
+ nscolor* aColor)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MAC_SOURCE_LIST:
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ case NS_THEME_TOOLTIP:
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM:
+ case NS_THEME_DIALOG:
+ {
+ if ((aWidgetType == NS_THEME_DIALOG && !IsWindowSheet(aFrame)) ||
+ ((aWidgetType == NS_THEME_MAC_SOURCE_LIST_SELECTION ||
+ aWidgetType == NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION) &&
+ !IsInSourceList(aFrame))) {
+ return false;
+ }
+ ChildView* childView = ChildViewForFrame(aFrame);
+ if (childView) {
+ ThemeGeometryType type = ThemeGeometryTypeForWidget(aFrame, aWidgetType);
+ NSColor* color = [childView vibrancyFontSmoothingBackgroundColorForThemeGeometryType:type];
+ *aColor = ConvertNSColor(color);
+ return true;
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+}
+
+nsITheme::ThemeGeometryType
+nsNativeThemeCocoa::ThemeGeometryTypeForWidget(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_WINDOW_TITLEBAR:
+ return eThemeGeometryTypeTitlebar;
+ case NS_THEME_TOOLBAR:
+ return eThemeGeometryTypeToolbar;
+ case NS_THEME_TOOLBOX:
+ return eThemeGeometryTypeToolbox;
+ case NS_THEME_WINDOW_BUTTON_BOX:
+ return eThemeGeometryTypeWindowButtons;
+ case NS_THEME_MAC_FULLSCREEN_BUTTON:
+ return eThemeGeometryTypeFullscreenButton;
+ case NS_THEME_MAC_VIBRANCY_LIGHT:
+ return eThemeGeometryTypeVibrancyLight;
+ case NS_THEME_MAC_VIBRANCY_DARK:
+ return eThemeGeometryTypeVibrancyDark;
+ case NS_THEME_TOOLTIP:
+ return eThemeGeometryTypeTooltip;
+ case NS_THEME_MENUPOPUP:
+ return eThemeGeometryTypeMenu;
+ case NS_THEME_MENUITEM:
+ case NS_THEME_CHECKMENUITEM: {
+ EventStates eventState = GetContentState(aFrame, aWidgetType);
+ bool isDisabled = IsDisabled(aFrame, eventState);
+ bool isSelected = !isDisabled && CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ return isSelected ? eThemeGeometryTypeHighlightedMenuItem : eThemeGeometryTypeMenu;
+ }
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame) ? eThemeGeometryTypeSheet : eThemeGeometryTypeUnknown;
+ case NS_THEME_MAC_SOURCE_LIST:
+ return eThemeGeometryTypeSourceList;
+ case NS_THEME_MAC_SOURCE_LIST_SELECTION:
+ return IsInSourceList(aFrame) ? eThemeGeometryTypeSourceListSelection
+ : eThemeGeometryTypeUnknown;
+ case NS_THEME_MAC_ACTIVE_SOURCE_LIST_SELECTION:
+ return IsInSourceList(aFrame) ? eThemeGeometryTypeActiveSourceListSelection
+ : eThemeGeometryTypeUnknown;
+ default:
+ return eThemeGeometryTypeUnknown;
+ }
+}
+
+nsITheme::Transparency
+nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
+{
+ switch (aWidgetType) {
+ case NS_THEME_MENUPOPUP:
+ case NS_THEME_TOOLTIP:
+ return eTransparent;
+
+ case NS_THEME_DIALOG:
+ return IsWindowSheet(aFrame) ? eTransparent : eOpaque;
+
+ case NS_THEME_SCROLLBAR_SMALL:
+ case NS_THEME_SCROLLBAR:
+ return nsLookAndFeel::UseOverlayScrollbars() ? eTransparent : eOpaque;
+
+ case NS_THEME_STATUSBAR:
+ // Knowing that scrollbars and statusbars are opaque improves
+ // performance, because we create layers for them.
+ return eOpaque;
+
+ case NS_THEME_TOOLBAR:
+ return eOpaque;
+
+ default:
+ return eUnknownTransparency;
+ }
+}