michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsNativeThemeCocoa.h" michael@0: #include "nsObjCExceptions.h" michael@0: #include "nsNumberControlFrame.h" michael@0: #include "nsRangeFrame.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsRect.h" michael@0: #include "nsSize.h" michael@0: #include "nsThemeConstants.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsCocoaFeatures.h" michael@0: #include "nsCocoaWindow.h" michael@0: #include "nsNativeThemeColors.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/HTMLMeterElement.h" michael@0: #include "nsLookAndFeel.h" michael@0: michael@0: #include "gfxContext.h" michael@0: #include "gfxQuartzSurface.h" michael@0: #include "gfxQuartzNativeDrawing.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: using mozilla::dom::HTMLMeterElement; michael@0: michael@0: #define DRAW_IN_FRAME_DEBUG 0 michael@0: #define SCROLLBARS_VISUAL_DEBUG 0 michael@0: michael@0: // private Quartz routines needed here michael@0: extern "C" { michael@0: CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform); michael@0: } michael@0: michael@0: // Workaround for NSCell control tint drawing michael@0: // Without this workaround, NSCells are always drawn with the clear control tint michael@0: // as long as they're not attached to an NSControl which is a subview of an active window. michael@0: // XXXmstange Why doesn't Webkit need this? michael@0: @implementation NSCell (ControlTintWorkaround) michael@0: - (int)_realControlTint { return [self controlTint]; } michael@0: @end michael@0: michael@0: // The purpose of this class is to provide objects that can be used when drawing michael@0: // NSCells using drawWithFrame:inView: without causing any harm. The only michael@0: // messages that will be sent to such an object are "isFlipped" and michael@0: // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs michael@0: // on 10.4 (see bug 465069); currentEditor (which isn't even a method of michael@0: // NSView) will be called when drawing search fields, and we only provide it in michael@0: // order to prevent "unrecognized selector" exceptions. michael@0: // There's no need to pass the actual NSView that we're drawing into to michael@0: // drawWithFrame:inView:. What's more, doing so even causes unnecessary michael@0: // invalidations as soon as we draw a focusring! michael@0: @interface CellDrawView : NSView michael@0: michael@0: @end; michael@0: michael@0: @implementation CellDrawView michael@0: michael@0: - (BOOL)isFlipped michael@0: { michael@0: return YES; michael@0: } michael@0: michael@0: - (NSText*)currentEditor michael@0: { michael@0: return nil; michael@0: } michael@0: michael@0: @end michael@0: michael@0: static void michael@0: DrawCellIncludingFocusRing(NSCell* aCell, NSRect aWithFrame, NSView* aInView) michael@0: { michael@0: [aCell drawWithFrame:aWithFrame inView:aInView]; michael@0: michael@0: #if defined(MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 michael@0: // When building with the 10.8 SDK or higher, focus rings don't draw as part michael@0: // of -[NSCell drawWithFrame:inView:] and must be drawn by a separate call michael@0: // to -[NSCell drawFocusRingMaskWithFrame:inView:]; . michael@0: // See the NSButtonCell section under michael@0: // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes michael@0: if ([aCell showsFirstResponder]) { michael@0: CGContextRef cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; michael@0: CGContextSaveGState(cgContext); michael@0: michael@0: // It's important to set the focus ring style before we enter the michael@0: // transparency layer so that the transparency layer only contains michael@0: // the normal button mask without the focus ring, and the conversion michael@0: // to the focus ring shape happens only when the transparency layer is michael@0: // ended. michael@0: NSSetFocusRingStyle(NSFocusRingOnly); michael@0: michael@0: // We need to draw the whole button into a transparency layer because michael@0: // many button types are composed of multiple parts, and if these parts michael@0: // were drawn while the focus ring style was active, each individual part michael@0: // would produce a focus ring for itself. But we only want one focus ring michael@0: // for the whole button. The transparency layer is a way to merge the michael@0: // individual button parts together before the focus ring shape is michael@0: // calculated. michael@0: CGContextBeginTransparencyLayerWithRect(cgContext, NSRectToCGRect(aWithFrame), 0); michael@0: [aCell drawFocusRingMaskWithFrame:aWithFrame inView:aInView]; michael@0: CGContextEndTransparencyLayer(cgContext); michael@0: michael@0: CGContextRestoreGState(cgContext); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: /** michael@0: * NSProgressBarCell is used to draw progress bars of any size. michael@0: */ michael@0: @interface NSProgressBarCell : NSCell michael@0: { michael@0: /*All instance variables are private*/ michael@0: double mValue; michael@0: double mMax; michael@0: bool mIsIndeterminate; michael@0: bool mIsHorizontal; michael@0: } michael@0: michael@0: - (void)setValue:(double)value; michael@0: - (double)value; michael@0: - (void)setMax:(double)max; michael@0: - (double)max; michael@0: - (void)setIndeterminate:(bool)aIndeterminate; michael@0: - (bool)isIndeterminate; michael@0: - (void)setHorizontal:(bool)aIsHorizontal; michael@0: - (bool)isHorizontal; michael@0: - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView; michael@0: @end michael@0: michael@0: @implementation NSProgressBarCell michael@0: michael@0: - (void)setMax:(double)aMax michael@0: { michael@0: mMax = aMax; michael@0: } michael@0: michael@0: - (double)max michael@0: { michael@0: return mMax; michael@0: } michael@0: michael@0: - (void)setValue:(double)aValue michael@0: { michael@0: mValue = aValue; michael@0: } michael@0: michael@0: - (double)value michael@0: { michael@0: return mValue; michael@0: } michael@0: michael@0: - (void)setIndeterminate:(bool)aIndeterminate michael@0: { michael@0: mIsIndeterminate = aIndeterminate; michael@0: } michael@0: michael@0: - (bool)isIndeterminate michael@0: { michael@0: return mIsIndeterminate; michael@0: } michael@0: michael@0: - (void)setHorizontal:(bool)aIsHorizontal michael@0: { michael@0: mIsHorizontal = aIsHorizontal; michael@0: } michael@0: michael@0: - (bool)isHorizontal michael@0: { michael@0: return mIsHorizontal; michael@0: } michael@0: michael@0: - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView michael@0: { michael@0: CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; michael@0: michael@0: HIThemeTrackDrawInfo tdi; michael@0: michael@0: tdi.version = 0; michael@0: tdi.min = 0; michael@0: michael@0: tdi.value = INT32_MAX * (mValue / mMax); michael@0: tdi.max = INT32_MAX; michael@0: tdi.bounds = NSRectToCGRect(cellFrame); michael@0: tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0; michael@0: tdi.enableState = [self controlTint] == NSClearControlTint ? kThemeTrackInactive michael@0: : kThemeTrackActive; michael@0: michael@0: NSControlSize size = [self controlSize]; michael@0: if (size == NSRegularControlSize) { michael@0: tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar michael@0: : kThemeLargeProgressBar; michael@0: } else { michael@0: NS_ASSERTION(size == NSSmallControlSize, michael@0: "We shouldn't have another size than small and regular for the moment"); michael@0: tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar michael@0: : kThemeMediumProgressBar; michael@0: } michael@0: michael@0: int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30; michael@0: int32_t milliSecondsPerStep = 1000 / stepsPerSecond; michael@0: tdi.trackInfo.progress.phase = uint8_t(PR_IntervalToMilliseconds(PR_IntervalNow()) / michael@0: milliSecondsPerStep); michael@0: michael@0: HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal); michael@0: } michael@0: michael@0: @end michael@0: michael@0: @interface ContextAwareSearchFieldCell : NSSearchFieldCell michael@0: { michael@0: nsIFrame* mContext; michael@0: } michael@0: michael@0: // setContext: stores the searchfield nsIFrame so that it can be consulted michael@0: // during painting. Please reset this by calling setContext:nullptr as soon as michael@0: // you're done with painting because we don't want to keep a dangling pointer. michael@0: - (void)setContext:(nsIFrame*)aContext; michael@0: @end michael@0: michael@0: @implementation ContextAwareSearchFieldCell michael@0: michael@0: - (id)initTextCell:(NSString*)aString michael@0: { michael@0: if ((self = [super initTextCell:aString])) { michael@0: mContext = nullptr; michael@0: } michael@0: return self; michael@0: } michael@0: michael@0: - (void)setContext:(nsIFrame*)aContext michael@0: { michael@0: mContext = aContext; michael@0: } michael@0: michael@0: static BOOL IsToolbarStyleContainer(nsIFrame* aFrame) michael@0: { michael@0: nsIContent* content = aFrame->GetContent(); michael@0: if (!content) michael@0: return NO; michael@0: michael@0: if (content->Tag() == nsGkAtoms::toolbar || michael@0: content->Tag() == nsGkAtoms::toolbox || michael@0: content->Tag() == nsGkAtoms::statusbar) michael@0: return YES; michael@0: michael@0: switch (aFrame->StyleDisplay()->mAppearance) { michael@0: case NS_THEME_TOOLBAR: michael@0: case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR: michael@0: case NS_THEME_STATUSBAR: michael@0: return YES; michael@0: default: michael@0: return NO; michael@0: } michael@0: } michael@0: michael@0: - (BOOL)_isToolbarMode michael@0: { michael@0: // On 10.7, searchfields have two different styles, depending on whether michael@0: // the searchfield is on top of of window chrome. This function is called on michael@0: // 10.7 during drawing in order to determine which style to use. michael@0: for (nsIFrame* frame = mContext; frame; frame = frame->GetParent()) { michael@0: if (IsToolbarStyleContainer(frame)) { michael@0: return YES; michael@0: } michael@0: } michael@0: return NO; michael@0: } michael@0: michael@0: @end michael@0: michael@0: // Workaround for Bug 542048 michael@0: // On 64-bit, NSSearchFieldCells don't draw focus rings. michael@0: #if defined(__x86_64__) michael@0: michael@0: static void DrawFocusRing(NSRect rect, float radius) michael@0: { michael@0: NSSetFocusRingStyle(NSFocusRingOnly); michael@0: NSBezierPath* path = [NSBezierPath bezierPath]; michael@0: rect = NSInsetRect(rect, radius, radius); michael@0: [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMinY(rect)) radius:radius startAngle:180.0 endAngle:270.0]; michael@0: [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMinY(rect)) radius:radius startAngle:270.0 endAngle:360.0]; michael@0: [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMaxY(rect)) radius:radius startAngle: 0.0 endAngle: 90.0]; michael@0: [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMaxY(rect)) radius:radius startAngle: 90.0 endAngle:180.0]; michael@0: [path closePath]; michael@0: [path fill]; michael@0: } michael@0: michael@0: @interface SearchFieldCellWithFocusRing : ContextAwareSearchFieldCell {} @end michael@0: michael@0: @implementation SearchFieldCellWithFocusRing michael@0: michael@0: - (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView michael@0: { michael@0: [super drawWithFrame:rect inView:controlView]; michael@0: if ([self showsFirstResponder]) { michael@0: DrawFocusRing(rect, NSHeight(rect) / 2); michael@0: } michael@0: } michael@0: michael@0: @end michael@0: michael@0: #endif michael@0: michael@0: // Copied from nsLookAndFeel.h michael@0: // Apple hasn't defined a constant for scollbars with two arrows on each end, so we'll use this one. michael@0: static const int kThemeScrollBarArrowsBoth = 2; michael@0: michael@0: #define HITHEME_ORIENTATION kHIThemeOrientationNormal michael@0: #define MAX_FOCUS_RING_WIDTH 4 michael@0: michael@0: // These enums are for indexing into the margin array. michael@0: enum { michael@0: leopardOS = 0 michael@0: }; michael@0: michael@0: enum { michael@0: miniControlSize, michael@0: smallControlSize, michael@0: regularControlSize michael@0: }; michael@0: michael@0: enum { michael@0: leftMargin, michael@0: topMargin, michael@0: rightMargin, michael@0: bottomMargin michael@0: }; michael@0: michael@0: static int EnumSizeForCocoaSize(NSControlSize cocoaControlSize) { michael@0: if (cocoaControlSize == NSMiniControlSize) michael@0: return miniControlSize; michael@0: else if (cocoaControlSize == NSSmallControlSize) michael@0: return smallControlSize; michael@0: else michael@0: return regularControlSize; michael@0: } michael@0: michael@0: static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) { michael@0: if (enumControlSize == miniControlSize) michael@0: return NSMiniControlSize; michael@0: else if (enumControlSize == smallControlSize) michael@0: return NSSmallControlSize; michael@0: else michael@0: return NSRegularControlSize; michael@0: } michael@0: michael@0: static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize) michael@0: { michael@0: if (aControlSize == NSRegularControlSize) michael@0: return @"regular"; michael@0: else if (aControlSize == NSSmallControlSize) michael@0: return @"small"; michael@0: else michael@0: return @"mini"; michael@0: } michael@0: michael@0: static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4]) michael@0: { michael@0: if (!marginSet) michael@0: return; michael@0: michael@0: static int osIndex = leopardOS; michael@0: int controlSize = EnumSizeForCocoaSize(cocoaControlSize); michael@0: const float* buttonMargins = marginSet[osIndex][controlSize]; michael@0: rect->origin.x -= buttonMargins[leftMargin]; michael@0: rect->origin.y -= buttonMargins[bottomMargin]; michael@0: rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin]; michael@0: rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin]; michael@0: } michael@0: michael@0: static NSWindow* NativeWindowForFrame(nsIFrame* aFrame, michael@0: nsIWidget** aTopLevelWidget = NULL) michael@0: { michael@0: if (!aFrame) michael@0: return nil; michael@0: michael@0: nsIWidget* widget = aFrame->GetNearestWidget(); michael@0: if (!widget) michael@0: return nil; michael@0: michael@0: nsIWidget* topLevelWidget = widget->GetTopLevelWidget(); michael@0: if (aTopLevelWidget) michael@0: *aTopLevelWidget = topLevelWidget; michael@0: michael@0: return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW); michael@0: } michael@0: michael@0: static NSSize michael@0: WindowButtonsSize(nsIFrame* aFrame) michael@0: { michael@0: NSWindow* window = NativeWindowForFrame(aFrame); michael@0: if (!window) { michael@0: // Return fallback values. michael@0: if (!nsCocoaFeatures::OnLionOrLater()) michael@0: return NSMakeSize(57, 16); michael@0: return NSMakeSize(54, 16); michael@0: } michael@0: michael@0: NSRect buttonBox = NSZeroRect; michael@0: NSButton* closeButton = [window standardWindowButton:NSWindowCloseButton]; michael@0: if (closeButton) { michael@0: buttonBox = NSUnionRect(buttonBox, [closeButton frame]); michael@0: } michael@0: NSButton* minimizeButton = [window standardWindowButton:NSWindowMiniaturizeButton]; michael@0: if (minimizeButton) { michael@0: buttonBox = NSUnionRect(buttonBox, [minimizeButton frame]); michael@0: } michael@0: NSButton* zoomButton = [window standardWindowButton:NSWindowZoomButton]; michael@0: if (zoomButton) { michael@0: buttonBox = NSUnionRect(buttonBox, [zoomButton frame]); michael@0: } michael@0: return buttonBox.size; michael@0: } michael@0: michael@0: static BOOL FrameIsInActiveWindow(nsIFrame* aFrame) michael@0: { michael@0: nsIWidget* topLevelWidget = NULL; michael@0: NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget); michael@0: if (!topLevelWidget || !win) michael@0: return YES; michael@0: michael@0: // XUL popups, e.g. the toolbar customization popup, can't become key windows, michael@0: // but controls in these windows should still get the active look. michael@0: if (topLevelWidget->WindowType() == eWindowType_popup) michael@0: return YES; michael@0: if ([win isSheet]) michael@0: return [win isKeyWindow]; michael@0: return [win isMainWindow] && ![win attachedSheet]; michael@0: } michael@0: michael@0: // Toolbar controls and content controls respond to different window michael@0: // activeness states. michael@0: static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl) michael@0: { michael@0: if (aIsToolbarControl) michael@0: return [NativeWindowForFrame(aFrame) isMainWindow]; michael@0: return FrameIsInActiveWindow(aFrame); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeCocoa, nsNativeTheme, nsITheme) michael@0: michael@0: michael@0: nsNativeThemeCocoa::nsNativeThemeCocoa() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: // provide a local autorelease pool, as this is called during startup michael@0: // before the main event-loop pool is in place michael@0: nsAutoreleasePool pool; michael@0: michael@0: mHelpButtonCell = [[NSButtonCell alloc] initTextCell:nil]; michael@0: [mHelpButtonCell setBezelStyle:NSHelpButtonBezelStyle]; michael@0: [mHelpButtonCell setButtonType:NSMomentaryPushInButton]; michael@0: [mHelpButtonCell setHighlightsBy:NSPushInCellMask]; michael@0: michael@0: mPushButtonCell = [[NSButtonCell alloc] initTextCell:nil]; michael@0: [mPushButtonCell setButtonType:NSMomentaryPushInButton]; michael@0: [mPushButtonCell setHighlightsBy:NSPushInCellMask]; michael@0: michael@0: mRadioButtonCell = [[NSButtonCell alloc] initTextCell:nil]; michael@0: [mRadioButtonCell setButtonType:NSRadioButton]; michael@0: michael@0: mCheckboxCell = [[NSButtonCell alloc] initTextCell:nil]; michael@0: [mCheckboxCell setButtonType:NSSwitchButton]; michael@0: [mCheckboxCell setAllowsMixedState:YES]; michael@0: michael@0: #if defined(__x86_64__) michael@0: mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""]; michael@0: #else michael@0: mSearchFieldCell = [[ContextAwareSearchFieldCell alloc] initTextCell:@""]; michael@0: #endif michael@0: [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel]; michael@0: [mSearchFieldCell setBezeled:YES]; michael@0: [mSearchFieldCell setEditable:YES]; michael@0: [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior]; michael@0: michael@0: mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]; michael@0: michael@0: mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""]; michael@0: [mComboBoxCell setBezeled:YES]; michael@0: [mComboBoxCell setEditable:YES]; michael@0: [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior]; michael@0: michael@0: mProgressBarCell = [[NSProgressBarCell alloc] init]; michael@0: michael@0: mMeterBarCell = [[NSLevelIndicatorCell alloc] michael@0: initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle]; michael@0: michael@0: mCellDrawView = [[CellDrawView alloc] init]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsNativeThemeCocoa::~nsNativeThemeCocoa() michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [mMeterBarCell release]; michael@0: [mProgressBarCell release]; michael@0: [mHelpButtonCell release]; michael@0: [mPushButtonCell release]; michael@0: [mRadioButtonCell release]; michael@0: [mCheckboxCell release]; michael@0: [mSearchFieldCell release]; michael@0: [mDropdownCell release]; michael@0: [mComboBoxCell release]; michael@0: [mCellDrawView release]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: // Limit on the area of the target rect (in pixels^2) in michael@0: // DrawCellWithScaling(), DrawButton() and DrawScrollbar(), above which we michael@0: // don't draw the object into a bitmap buffer. This is to avoid crashes in michael@0: // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and michael@0: // CGContextDrawImage(), and also to avoid very poor drawing performance in michael@0: // CGContextDrawImage() when it scales the bitmap (particularly if xscale or michael@0: // yscale is less than but near 1 -- e.g. 0.9). This value was determined michael@0: // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with michael@0: // different amounts of RAM. michael@0: #define BITMAP_MAX_AREA 500000 michael@0: michael@0: static int michael@0: GetBackingScaleFactorForRendering(CGContextRef cgContext) michael@0: { michael@0: CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext); michael@0: CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm); michael@0: float maxScale = std::max(fabs(transformedUserSpacePixel.size.width), michael@0: fabs(transformedUserSpacePixel.size.height)); michael@0: return maxScale > 1.0 ? 2 : 1; michael@0: } michael@0: michael@0: /* michael@0: * Draw the given NSCell into the given cgContext. michael@0: * michael@0: * destRect - the size and position of the resulting control rectangle michael@0: * controlSize - the NSControlSize which will be given to the NSCell before michael@0: * asking it to render michael@0: * naturalSize - The natural dimensions of this control. michael@0: * If the control rect size is not equal to either of these, a scale michael@0: * will be applied to the context so that rendering the control at the michael@0: * natural size will result in it filling the destRect space. michael@0: * If a control has no natural dimensions in either/both axes, pass 0.0f. michael@0: * minimumSize - The minimum dimensions of this control. michael@0: * If the control rect size is less than the minimum for a given axis, michael@0: * a scale will be applied to the context so that the minimum is used michael@0: * for drawing. If a control has no minimum dimensions in either/both michael@0: * axes, pass 0.0f. michael@0: * marginSet - an array of margins; a multidimensional array of [2][3][4], michael@0: * with the first dimension being the OS version (Tiger or Leopard), michael@0: * the second being the control size (mini, small, regular), and the third michael@0: * being the 4 margin values (left, top, right, bottom). michael@0: * view - The NSView that we're drawing into. As far as I can tell, it doesn't michael@0: * matter if this is really the right view; it just has to return YES when michael@0: * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4. michael@0: * mirrorHorizontal - whether to mirror the cell horizontally michael@0: */ michael@0: static void DrawCellWithScaling(NSCell *cell, michael@0: CGContextRef cgContext, michael@0: const HIRect& destRect, michael@0: NSControlSize controlSize, michael@0: NSSize naturalSize, michael@0: NSSize minimumSize, michael@0: const float marginSet[][3][4], michael@0: NSView* view, michael@0: BOOL mirrorHorizontal) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height); michael@0: michael@0: if (naturalSize.width != 0.0f) michael@0: drawRect.size.width = naturalSize.width; michael@0: if (naturalSize.height != 0.0f) michael@0: drawRect.size.height = naturalSize.height; michael@0: michael@0: // Keep aspect ratio when scaling if one dimension is free. michael@0: if (naturalSize.width == 0.0f && naturalSize.height != 0.0f) michael@0: drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height; michael@0: if (naturalSize.height == 0.0f && naturalSize.width != 0.0f) michael@0: drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width; michael@0: michael@0: // Honor minimum sizes. michael@0: if (drawRect.size.width < minimumSize.width) michael@0: drawRect.size.width = minimumSize.width; michael@0: if (drawRect.size.height < minimumSize.height) michael@0: drawRect.size.height = minimumSize.height; michael@0: michael@0: [NSGraphicsContext saveGraphicsState]; michael@0: michael@0: // Only skip the buffer if the area of our cell (in pixels^2) is too large. michael@0: if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) { michael@0: // Inflate the rect Gecko gave us by the margin for the control. michael@0: InflateControlRect(&drawRect, controlSize, marginSet); michael@0: michael@0: NSGraphicsContext* savedContext = [NSGraphicsContext currentContext]; michael@0: [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]]; michael@0: michael@0: DrawCellIncludingFocusRing(cell, drawRect, view); michael@0: michael@0: [NSGraphicsContext setCurrentContext:savedContext]; michael@0: } michael@0: else { michael@0: float w = ceil(drawRect.size.width); michael@0: float h = ceil(drawRect.size.height); michael@0: NSRect tmpRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, w, h); michael@0: michael@0: // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h michael@0: InflateControlRect(&tmpRect, controlSize, marginSet); michael@0: michael@0: // and then, expand by MAX_FOCUS_RING_WIDTH size to make sure we can capture any focus ring michael@0: w += MAX_FOCUS_RING_WIDTH * 2.0; michael@0: h += MAX_FOCUS_RING_WIDTH * 2.0; michael@0: michael@0: int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext); michael@0: CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB(); michael@0: CGContextRef ctx = CGBitmapContextCreate(NULL, michael@0: (int) w * backingScaleFactor, (int) h * backingScaleFactor, michael@0: 8, (int) w * backingScaleFactor * 4, michael@0: rgb, kCGImageAlphaPremultipliedFirst); michael@0: CGColorSpaceRelease(rgb); michael@0: michael@0: // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069. michael@0: // This is the first flip transform, applied to cgContext. michael@0: CGContextScaleCTM(cgContext, 1.0f, -1.0f); michael@0: CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height)); michael@0: if (mirrorHorizontal) { michael@0: CGContextScaleCTM(cgContext, -1.0f, 1.0f); michael@0: CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f); michael@0: } michael@0: michael@0: NSGraphicsContext* savedContext = [NSGraphicsContext currentContext]; michael@0: [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]]; michael@0: michael@0: CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor); michael@0: michael@0: // This is the second flip transform, applied to ctx. michael@0: CGContextScaleCTM(ctx, 1.0f, -1.0f); michael@0: CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height)); michael@0: michael@0: DrawCellIncludingFocusRing(cell, tmpRect, view); michael@0: michael@0: [NSGraphicsContext setCurrentContext:savedContext]; michael@0: michael@0: CGImageRef img = CGBitmapContextCreateImage(ctx); michael@0: michael@0: // Drop the image into the original destination rectangle, scaling to fit michael@0: // Only scale MAX_FOCUS_RING_WIDTH by xscale/yscale when the resulting rect michael@0: // doesn't extend beyond the overflow rect michael@0: float xscale = destRect.size.width / drawRect.size.width; michael@0: float yscale = destRect.size.height / drawRect.size.height; michael@0: float scaledFocusRingX = xscale < 1.0f ? MAX_FOCUS_RING_WIDTH * xscale : MAX_FOCUS_RING_WIDTH; michael@0: float scaledFocusRingY = yscale < 1.0f ? MAX_FOCUS_RING_WIDTH * yscale : MAX_FOCUS_RING_WIDTH; michael@0: CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX, michael@0: destRect.origin.y - scaledFocusRingY, michael@0: destRect.size.width + scaledFocusRingX * 2, michael@0: destRect.size.height + scaledFocusRingY * 2), michael@0: img); michael@0: michael@0: CGImageRelease(img); michael@0: CGContextRelease(ctx); michael@0: } michael@0: michael@0: [NSGraphicsContext restoreGraphicsState]; michael@0: michael@0: #if DRAW_IN_FRAME_DEBUG michael@0: CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25); michael@0: CGContextFillRect(cgContext, destRect); michael@0: #endif michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: struct CellRenderSettings { michael@0: // The natural dimensions of the control. michael@0: // If a control has no natural dimensions in either/both axes, set to 0.0f. michael@0: NSSize naturalSizes[3]; michael@0: michael@0: // The minimum dimensions of the control. michael@0: // If a control has no minimum dimensions in either/both axes, set to 0.0f. michael@0: NSSize minimumSizes[3]; michael@0: michael@0: // A three-dimensional array, michael@0: // with the first dimension being the OS version (only Leopard for the moment), michael@0: // the second being the control size (mini, small, regular), and the third michael@0: // being the 4 margin values (left, top, right, bottom). michael@0: float margins[1][3][4]; michael@0: }; michael@0: michael@0: /* michael@0: * This is a helper method that returns the required NSControlSize given a size michael@0: * and the size of the three controls plus a tolerance. michael@0: * size - The width or the height of the element to draw. michael@0: * sizes - An array with the all the width/height of the element for its michael@0: * different sizes. michael@0: * tolerance - The tolerance as passed to DrawCellWithSnapping. michael@0: * NOTE: returns NSRegularControlSize if all values in 'sizes' are zero. michael@0: */ michael@0: static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance) michael@0: { michael@0: for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) { michael@0: if (sizes[i] == 0) { michael@0: continue; michael@0: } michael@0: michael@0: CGFloat next = 0; michael@0: // Find next value. michael@0: for (uint32_t j = i+1; j <= regularControlSize; ++j) { michael@0: if (sizes[j] != 0) { michael@0: next = sizes[j]; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // If it's the latest value, we pick it. michael@0: if (next == 0) { michael@0: return CocoaSizeForEnum(i); michael@0: } michael@0: michael@0: if (size <= sizes[i] + tolerance && size < next) { michael@0: return CocoaSizeForEnum(i); michael@0: } michael@0: } michael@0: michael@0: // If we are here, that means sizes[] was an array with only empty values michael@0: // or the algorithm above is wrong. michael@0: // The former can happen but the later would be wrong. michael@0: NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0, michael@0: "We found no control! We shouldn't be there!"); michael@0: return CocoaSizeForEnum(regularControlSize); michael@0: } michael@0: michael@0: /* michael@0: * Draw the given NSCell into the given cgContext with a nice control size. michael@0: * michael@0: * This function is similar to DrawCellWithScaling, but it decides what michael@0: * control size to use based on the destRect's size. michael@0: * Scaling is only applied when the difference between the destRect's size michael@0: * and the next smaller natural size is greater than snapTolerance. Otherwise michael@0: * it snaps to the next smaller control size without scaling because unscaled michael@0: * controls look nicer. michael@0: */ michael@0: static void DrawCellWithSnapping(NSCell *cell, michael@0: CGContextRef cgContext, michael@0: const HIRect& destRect, michael@0: const CellRenderSettings settings, michael@0: float verticalAlignFactor, michael@0: NSView* view, michael@0: BOOL mirrorHorizontal, michael@0: float snapTolerance = 2.0f) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: const float rectWidth = destRect.size.width, rectHeight = destRect.size.height; michael@0: const NSSize *sizes = settings.naturalSizes; michael@0: const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)]; michael@0: const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)]; michael@0: const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)]; michael@0: michael@0: HIRect drawRect = destRect; michael@0: michael@0: CGFloat controlWidths[3] = { miniSize.width, smallSize.width, regularSize.width }; michael@0: NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance); michael@0: CGFloat controlHeights[3] = { miniSize.height, smallSize.height, regularSize.height }; michael@0: NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance); michael@0: michael@0: NSControlSize controlSize = NSRegularControlSize; michael@0: int sizeIndex = 0; michael@0: michael@0: // At some sizes, don't scale but snap. michael@0: const NSControlSize smallerControlSize = michael@0: EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ? michael@0: controlSizeX : controlSizeY; michael@0: const int smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize); michael@0: const NSSize size = sizes[smallerControlSizeIndex]; michael@0: float diffWidth = size.width ? rectWidth - size.width : 0.0f; michael@0: float diffHeight = size.height ? rectHeight - size.height : 0.0f; michael@0: if (diffWidth >= 0.0f && diffHeight >= 0.0f && michael@0: diffWidth <= snapTolerance && diffHeight <= snapTolerance) { michael@0: // Snap to the smaller control size. michael@0: controlSize = smallerControlSize; michael@0: sizeIndex = smallerControlSizeIndex; michael@0: // Resize and center the drawRect. michael@0: if (sizes[sizeIndex].width) { michael@0: drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2); michael@0: drawRect.size.width = sizes[sizeIndex].width; michael@0: } michael@0: if (sizes[sizeIndex].height) { michael@0: drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor); michael@0: drawRect.size.height = sizes[sizeIndex].height; michael@0: } michael@0: } else { michael@0: // Use the larger control size. michael@0: controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ? michael@0: controlSizeX : controlSizeY; michael@0: sizeIndex = EnumSizeForCocoaSize(controlSize); michael@0: } michael@0: michael@0: [cell setControlSize:controlSize]; michael@0: michael@0: NSSize minimumSize = settings.minimumSizes ? settings.minimumSizes[sizeIndex] : NSZeroSize; michael@0: DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex], michael@0: minimumSize, settings.margins, view, mirrorHorizontal); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static float VerticalAlignFactor(nsIFrame *aFrame) michael@0: { michael@0: if (!aFrame) michael@0: return 0.5f; // default: center michael@0: michael@0: const nsStyleCoord& va = aFrame->StyleTextReset()->mVerticalAlign; michael@0: uint8_t intval = (va.GetUnit() == eStyleUnit_Enumerated) michael@0: ? va.GetIntValue() michael@0: : NS_STYLE_VERTICAL_ALIGN_MIDDLE; michael@0: switch (intval) { michael@0: case NS_STYLE_VERTICAL_ALIGN_TOP: michael@0: case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP: michael@0: return 0.0f; michael@0: michael@0: case NS_STYLE_VERTICAL_ALIGN_SUB: michael@0: case NS_STYLE_VERTICAL_ALIGN_SUPER: michael@0: case NS_STYLE_VERTICAL_ALIGN_MIDDLE: michael@0: case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE: michael@0: return 0.5f; michael@0: michael@0: case NS_STYLE_VERTICAL_ALIGN_BASELINE: michael@0: case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM: michael@0: case NS_STYLE_VERTICAL_ALIGN_BOTTOM: michael@0: return 1.0f; michael@0: michael@0: default: michael@0: NS_NOTREACHED("invalid vertical-align"); michael@0: return 0.5f; michael@0: } michael@0: } michael@0: michael@0: // These are the sizes that Gecko needs to request to draw if it wants michael@0: // to get a standard-sized Aqua radio button drawn. Note that the rects michael@0: // that draw these are actually a little bigger. michael@0: static const CellRenderSettings radioSettings = { michael@0: { michael@0: NSMakeSize(11, 11), // mini michael@0: NSMakeSize(13, 13), // small michael@0: NSMakeSize(16, 16) // regular michael@0: }, michael@0: { michael@0: NSZeroSize, NSZeroSize, NSZeroSize michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 0, 0}, // mini michael@0: {0, 1, 1, 1}, // small michael@0: {0, 0, 0, 0} // regular michael@0: } michael@0: } michael@0: }; michael@0: michael@0: static const CellRenderSettings checkboxSettings = { michael@0: { michael@0: NSMakeSize(11, 11), // mini michael@0: NSMakeSize(13, 13), // small michael@0: NSMakeSize(16, 16) // regular michael@0: }, michael@0: { michael@0: NSZeroSize, NSZeroSize, NSZeroSize michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 1, 0, 0}, // mini michael@0: {0, 1, 0, 1}, // small michael@0: {0, 1, 0, 1} // regular michael@0: } michael@0: } michael@0: }; michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox, michael@0: const HIRect& inBoxRect, bool inSelected, michael@0: EventStates inState, nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell; michael@0: NSCellStateValue state = inSelected ? NSOnState : NSOffState; michael@0: michael@0: // Check if we have an indeterminate checkbox michael@0: if (inCheckbox && GetIndeterminate(aFrame)) michael@0: state = NSMixedState; michael@0: michael@0: [cell setEnabled:!IsDisabled(aFrame, inState)]; michael@0: [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)]; michael@0: [cell setState:state]; michael@0: [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)]; michael@0: [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)]; michael@0: michael@0: // Ensure that the control is square. michael@0: float length = std::min(inBoxRect.size.width, inBoxRect.size.height); michael@0: HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f), michael@0: inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f), michael@0: length, length); michael@0: michael@0: DrawCellWithSnapping(cell, cgContext, drawRect, michael@0: inCheckbox ? checkboxSettings : radioSettings, michael@0: VerticalAlignFactor(aFrame), mCellDrawView, NO); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static const CellRenderSettings searchFieldSettings = { michael@0: { michael@0: NSMakeSize(0, 16), // mini michael@0: NSMakeSize(0, 19), // small michael@0: NSMakeSize(0, 22) // regular michael@0: }, michael@0: { michael@0: NSMakeSize(32, 0), // mini michael@0: NSMakeSize(38, 0), // small michael@0: NSMakeSize(44, 0) // regular michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 0, 0}, // mini michael@0: {0, 0, 0, 0}, // small michael@0: {0, 0, 0, 0} // regular michael@0: } michael@0: } michael@0: }; michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: nsIFrame* aFrame, EventStates inState) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: ContextAwareSearchFieldCell* cell = mSearchFieldCell; michael@0: [cell setContext:aFrame]; michael@0: [cell setEnabled:!IsDisabled(aFrame, inState)]; michael@0: // NOTE: this could probably use inState michael@0: [cell setShowsFirstResponder:IsFocused(aFrame)]; michael@0: michael@0: DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings, michael@0: VerticalAlignFactor(aFrame), mCellDrawView, michael@0: IsFrameRTL(aFrame)); michael@0: michael@0: [cell setContext:nullptr]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static const NSSize kHelpButtonSize = NSMakeSize(20, 20); michael@0: michael@0: static const CellRenderSettings pushButtonSettings = { michael@0: { michael@0: NSMakeSize(0, 16), // mini michael@0: NSMakeSize(0, 19), // small michael@0: NSMakeSize(0, 22) // regular michael@0: }, michael@0: { michael@0: NSMakeSize(18, 0), // mini michael@0: NSMakeSize(26, 0), // small michael@0: NSMakeSize(30, 0) // regular michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 0, 0}, // mini michael@0: {4, 0, 4, 1}, // small michael@0: {5, 0, 5, 2} // regular michael@0: } michael@0: } michael@0: }; michael@0: michael@0: // The height at which we start doing square buttons instead of rounded buttons michael@0: // Rounded buttons look bad if drawn at a height greater than 26, so at that point michael@0: // we switch over to doing square buttons which looks fine at any size. michael@0: #define DO_SQUARE_BUTTON_HEIGHT 26 michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: EventStates inState, uint8_t aWidgetType, michael@0: nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: BOOL isActive = FrameIsInActiveWindow(aFrame); michael@0: BOOL isDisabled = IsDisabled(aFrame, inState); michael@0: michael@0: NSButtonCell* cell = (aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON) ? mHelpButtonCell : mPushButtonCell; michael@0: [cell setEnabled:!isDisabled]; michael@0: [cell setHighlighted:isActive && michael@0: inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)]; michael@0: [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive]; michael@0: michael@0: if (aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON) { michael@0: DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize, michael@0: NSZeroSize, kHelpButtonSize, NULL, mCellDrawView, michael@0: false); // Don't mirror icon in RTL. michael@0: } else { michael@0: // If the button is tall enough, draw the square button style so that michael@0: // buttons with non-standard content look good. Otherwise draw normal michael@0: // rounded aqua buttons. michael@0: if (inBoxRect.size.height > DO_SQUARE_BUTTON_HEIGHT) { michael@0: [cell setBezelStyle:NSShadowlessSquareBezelStyle]; michael@0: DrawCellWithScaling(cell, cgContext, inBoxRect, NSRegularControlSize, michael@0: NSZeroSize, NSMakeSize(14, 0), NULL, mCellDrawView, michael@0: IsFrameRTL(aFrame)); michael@0: } else { michael@0: [cell setBezelStyle:NSRoundedBezelStyle]; michael@0: DrawCellWithSnapping(cell, cgContext, inBoxRect, pushButtonSettings, 0.5f, michael@0: mCellDrawView, IsFrameRTL(aFrame), 1.0f); michael@0: } michael@0: } michael@0: michael@0: #if DRAW_IN_FRAME_DEBUG michael@0: CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25); michael@0: CGContextFillRect(cgContext, inBoxRect); michael@0: #endif michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData); michael@0: michael@0: static void michael@0: RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect, michael@0: RenderHIThemeControlFunction aFunc, void* aData, michael@0: BOOL mirrorHorizontally = NO) michael@0: { michael@0: CGAffineTransform savedCTM = CGContextGetCTM(aCGContext); michael@0: CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y); michael@0: michael@0: bool drawDirect; michael@0: HIRect drawRect = aRect; michael@0: drawRect.origin = CGPointZero; michael@0: michael@0: if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f && michael@0: savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) { michael@0: drawDirect = TRUE; michael@0: } else { michael@0: drawDirect = FALSE; michael@0: } michael@0: michael@0: // Fall back to no bitmap buffer if the area of our control (in pixels^2) michael@0: // is too large. michael@0: if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) { michael@0: aFunc(aCGContext, drawRect, aData); michael@0: } else { michael@0: // Inflate the buffer to capture focus rings. michael@0: int w = ceil(drawRect.size.width) + 2 * MAX_FOCUS_RING_WIDTH; michael@0: int h = ceil(drawRect.size.height) + 2 * MAX_FOCUS_RING_WIDTH; michael@0: michael@0: int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext); michael@0: CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); michael@0: CGContextRef bitmapctx = CGBitmapContextCreate(NULL, michael@0: w * backingScaleFactor, michael@0: h * backingScaleFactor, michael@0: 8, michael@0: w * backingScaleFactor * 4, michael@0: colorSpace, michael@0: kCGImageAlphaPremultipliedFirst); michael@0: CGColorSpaceRelease(colorSpace); michael@0: michael@0: CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor); michael@0: CGContextTranslateCTM(bitmapctx, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH); michael@0: michael@0: // HITheme always wants to draw into a flipped context, or things michael@0: // get confused. michael@0: CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height); michael@0: CGContextScaleCTM(bitmapctx, 1.0f, -1.0f); michael@0: michael@0: aFunc(bitmapctx, drawRect, aData); michael@0: michael@0: CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx); michael@0: michael@0: CGAffineTransform ctm = CGContextGetCTM(aCGContext); michael@0: michael@0: // We need to unflip, so that we can do a DrawImage without getting a flipped image. michael@0: CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height); michael@0: CGContextScaleCTM(aCGContext, 1.0f, -1.0f); michael@0: michael@0: if (mirrorHorizontally) { michael@0: CGContextTranslateCTM(aCGContext, aRect.size.width, 0); michael@0: CGContextScaleCTM(aCGContext, -1.0f, 1.0f); michael@0: } michael@0: michael@0: HIRect inflatedDrawRect = CGRectMake(-MAX_FOCUS_RING_WIDTH, -MAX_FOCUS_RING_WIDTH, w, h); michael@0: CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap); michael@0: michael@0: CGContextSetCTM(aCGContext, ctm); michael@0: michael@0: CGImageRelease(bitmap); michael@0: CGContextRelease(bitmapctx); michael@0: } michael@0: michael@0: CGContextSetCTM(aCGContext, savedCTM); michael@0: } michael@0: michael@0: static void michael@0: RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) michael@0: { michael@0: HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData; michael@0: HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL); michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind, michael@0: const HIRect& inBoxRect, bool inIsDefault, michael@0: ThemeButtonValue inValue, ThemeButtonAdornment inAdornment, michael@0: EventStates inState, nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: BOOL isActive = FrameIsInActiveWindow(aFrame); michael@0: BOOL isDisabled = IsDisabled(aFrame, inState); michael@0: michael@0: HIThemeButtonDrawInfo bdi; michael@0: bdi.version = 0; michael@0: bdi.kind = inKind; michael@0: bdi.value = inValue; michael@0: bdi.adornment = inAdornment; michael@0: michael@0: if (isDisabled) { michael@0: bdi.state = kThemeStateUnavailable; michael@0: } michael@0: else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) { michael@0: bdi.state = kThemeStatePressed; michael@0: } michael@0: else { michael@0: if (inKind == kThemeArrowButton) michael@0: bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable michael@0: else if (!isActive && inKind == kThemeListHeaderButton) michael@0: bdi.state = kThemeStateInactive; michael@0: else michael@0: bdi.state = kThemeStateActive; michael@0: } michael@0: michael@0: if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive) michael@0: bdi.adornment |= kThemeAdornmentFocus; michael@0: michael@0: if (inIsDefault && !isDisabled && isActive && michael@0: !inState.HasState(NS_EVENT_STATE_ACTIVE)) { michael@0: bdi.adornment |= kThemeAdornmentDefault; michael@0: bdi.animation.time.start = 0; michael@0: bdi.animation.time.current = CFAbsoluteTimeGetCurrent(); michael@0: } michael@0: michael@0: HIRect drawFrame = inBoxRect; michael@0: michael@0: if (inKind == kThemePushButton) { michael@0: drawFrame.size.height -= 2; michael@0: if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) { michael@0: bdi.kind = kThemePushButtonMini; michael@0: } michael@0: else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) { michael@0: bdi.kind = kThemePushButtonSmall; michael@0: drawFrame.origin.y -= 1; michael@0: drawFrame.origin.x += 1; michael@0: drawFrame.size.width -= 2; michael@0: } michael@0: } michael@0: else if (inKind == kThemeListHeaderButton) { michael@0: CGContextClipToRect(cgContext, inBoxRect); michael@0: // Always remove the top border. michael@0: drawFrame.origin.y -= 1; michael@0: drawFrame.size.height += 1; michael@0: // Remove the left border in LTR mode and the right border in RTL mode. michael@0: drawFrame.size.width += 1; michael@0: bool isLast = IsLastTreeHeaderCell(aFrame); michael@0: if (isLast) michael@0: drawFrame.size.width += 1; // Also remove the other border. michael@0: if (!IsFrameRTL(aFrame) || isLast) michael@0: drawFrame.origin.x -= 1; michael@0: } michael@0: michael@0: RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi, michael@0: IsFrameRTL(aFrame)); michael@0: michael@0: #if DRAW_IN_FRAME_DEBUG michael@0: CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25); michael@0: CGContextFillRect(cgContext, inBoxRect); michael@0: #endif michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static const CellRenderSettings dropdownSettings = { michael@0: { michael@0: NSMakeSize(0, 16), // mini michael@0: NSMakeSize(0, 19), // small michael@0: NSMakeSize(0, 22) // regular michael@0: }, michael@0: { michael@0: NSMakeSize(18, 0), // mini michael@0: NSMakeSize(38, 0), // small michael@0: NSMakeSize(44, 0) // regular michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {1, 1, 2, 1}, // mini michael@0: {3, 0, 3, 1}, // small michael@0: {3, 0, 3, 0} // regular michael@0: } michael@0: } michael@0: }; michael@0: michael@0: static const CellRenderSettings editableMenulistSettings = { michael@0: { michael@0: NSMakeSize(0, 15), // mini michael@0: NSMakeSize(0, 18), // small michael@0: NSMakeSize(0, 21) // regular michael@0: }, michael@0: { michael@0: NSMakeSize(18, 0), // mini michael@0: NSMakeSize(38, 0), // small michael@0: NSMakeSize(44, 0) // regular michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 2, 2}, // mini michael@0: {0, 0, 3, 2}, // small michael@0: {0, 1, 3, 3} // regular michael@0: } michael@0: } michael@0: }; michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: EventStates inState, uint8_t aWidgetType, michael@0: nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)]; michael@0: michael@0: BOOL isEditable = (aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD); michael@0: NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell; michael@0: michael@0: [cell setEnabled:!IsDisabled(aFrame, inState)]; michael@0: [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))]; michael@0: [cell setHighlighted:IsOpenButton(aFrame)]; michael@0: [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)]; michael@0: michael@0: const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings; michael@0: DrawCellWithSnapping(cell, cgContext, inBoxRect, settings, michael@0: 0.5f, mCellDrawView, IsFrameRTL(aFrame)); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static const CellRenderSettings spinnerSettings = { michael@0: { michael@0: NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border) michael@0: NSMakeSize(15, 22), // small michael@0: NSMakeSize(19, 27) // regular michael@0: }, michael@0: { michael@0: NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border) michael@0: NSMakeSize(15, 22), // small michael@0: NSMakeSize(19, 27) // regular michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 0, 0}, // mini michael@0: {0, 0, 0, 0}, // small michael@0: {0, 0, 0, 0} // regular michael@0: } michael@0: } michael@0: }; michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind, michael@0: const HIRect& inBoxRect, ThemeDrawState inDrawState, michael@0: ThemeButtonAdornment inAdornment, michael@0: EventStates inState, nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: HIThemeButtonDrawInfo bdi; michael@0: bdi.version = 0; michael@0: bdi.kind = inKind; michael@0: bdi.value = kThemeButtonOff; michael@0: bdi.adornment = inAdornment; michael@0: michael@0: if (IsDisabled(aFrame, inState)) michael@0: bdi.state = kThemeStateUnavailable; michael@0: else michael@0: bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive; michael@0: michael@0: HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, michael@0: ThemeButtonKind inKind, michael@0: const HIRect& inBoxRect, michael@0: ThemeDrawState inDrawState, michael@0: ThemeButtonAdornment inAdornment, michael@0: EventStates inState, michael@0: nsIFrame* aFrame, michael@0: uint8_t aWidgetType) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UP_BUTTON || michael@0: aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON); michael@0: michael@0: HIThemeButtonDrawInfo bdi; michael@0: bdi.version = 0; michael@0: bdi.kind = inKind; michael@0: bdi.value = kThemeButtonOff; michael@0: bdi.adornment = inAdornment; michael@0: michael@0: if (IsDisabled(aFrame, inState)) michael@0: bdi.state = kThemeStateUnavailable; michael@0: else michael@0: bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive; michael@0: michael@0: // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons michael@0: // together as a single unit (presumably because when one button is active, michael@0: // the appearance of both changes (in different ways)). Here we have to paint michael@0: // both buttons, using clip to hide the one we don't want to paint. michael@0: HIRect drawRect = inBoxRect; michael@0: drawRect.size.height *= 2; michael@0: if (aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) { michael@0: drawRect.origin.y -= inBoxRect.size.height; michael@0: } michael@0: michael@0: // Shift the drawing a little to the left, since cocoa paints with more michael@0: // blank space around the visual buttons than we'd like: michael@0: drawRect.origin.x -= 1; michael@0: michael@0: CGContextSaveGState(cgContext); michael@0: CGContextClipToRect(cgContext, inBoxRect); michael@0: michael@0: HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL); michael@0: michael@0: CGContextRestoreGState(cgContext); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind, michael@0: const HIRect& inBoxRect, bool inDisabled, michael@0: EventStates inState) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: HIThemeFrameDrawInfo fdi; michael@0: fdi.version = 0; michael@0: fdi.kind = inKind; michael@0: michael@0: // We don't ever set an inactive state for this because it doesn't michael@0: // look right (see other apps). michael@0: fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive; michael@0: michael@0: // for some reason focus rings on listboxes draw incorrectly michael@0: if (inKind == kHIThemeFrameListBox) michael@0: fdi.isFocused = 0; michael@0: else michael@0: fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS); michael@0: michael@0: // HIThemeDrawFrame takes the rect for the content area of the frame, not michael@0: // the bounding rect for the frame. Here we reduce the size of the rect we michael@0: // will pass to make it the size of the content. michael@0: HIRect drawRect = inBoxRect; michael@0: if (inKind == kHIThemeFrameTextFieldSquare) { michael@0: SInt32 frameOutset = 0; michael@0: ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset); michael@0: drawRect.origin.x += frameOutset; michael@0: drawRect.origin.y += frameOutset; michael@0: drawRect.size.width -= frameOutset * 2; michael@0: drawRect.size.height -= frameOutset * 2; michael@0: } michael@0: else if (inKind == kHIThemeFrameListBox) { michael@0: SInt32 frameOutset = 0; michael@0: ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset); michael@0: drawRect.origin.x += frameOutset; michael@0: drawRect.origin.y += frameOutset; michael@0: drawRect.size.width -= frameOutset * 2; michael@0: drawRect.size.height -= frameOutset * 2; michael@0: } michael@0: michael@0: #if DRAW_IN_FRAME_DEBUG michael@0: CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25); michael@0: CGContextFillRect(cgContext, inBoxRect); michael@0: #endif michael@0: michael@0: HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static const CellRenderSettings progressSettings[2][2] = { michael@0: // Vertical progress bar. michael@0: { michael@0: // Determined settings. michael@0: { michael@0: { michael@0: NSZeroSize, // mini michael@0: NSMakeSize(10, 0), // small michael@0: NSMakeSize(16, 0) // regular michael@0: }, michael@0: { michael@0: NSZeroSize, NSZeroSize, NSZeroSize michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 0, 0}, // mini michael@0: {1, 1, 1, 1}, // small michael@0: {1, 1, 1, 1} // regular michael@0: } michael@0: } michael@0: }, michael@0: // There is no horizontal margin in regular undetermined size. michael@0: { michael@0: { michael@0: NSZeroSize, // mini michael@0: NSMakeSize(10, 0), // small michael@0: NSMakeSize(16, 0) // regular michael@0: }, michael@0: { michael@0: NSZeroSize, NSZeroSize, NSZeroSize michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 0, 0}, // mini michael@0: {1, 1, 1, 1}, // small michael@0: {1, 0, 1, 0} // regular michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: // Horizontal progress bar. michael@0: { michael@0: // Determined settings. michael@0: { michael@0: { michael@0: NSZeroSize, // mini michael@0: NSMakeSize(0, 10), // small michael@0: NSMakeSize(0, 16) // regular michael@0: }, michael@0: { michael@0: NSZeroSize, NSZeroSize, NSZeroSize michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 0, 0}, // mini michael@0: {1, 1, 1, 1}, // small michael@0: {1, 1, 1, 1} // regular michael@0: } michael@0: } michael@0: }, michael@0: // There is no horizontal margin in regular undetermined size. michael@0: { michael@0: { michael@0: NSZeroSize, // mini michael@0: NSMakeSize(0, 10), // small michael@0: NSMakeSize(0, 16) // regular michael@0: }, michael@0: { michael@0: NSZeroSize, NSZeroSize, NSZeroSize michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {0, 0, 0, 0}, // mini michael@0: {1, 1, 1, 1}, // small michael@0: {0, 1, 0, 1} // regular michael@0: } michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: bool inIsIndeterminate, bool inIsHorizontal, michael@0: double inValue, double inMaxValue, michael@0: nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NSProgressBarCell* cell = mProgressBarCell; michael@0: michael@0: [cell setValue:inValue]; michael@0: [cell setMax:inMaxValue]; michael@0: [cell setIndeterminate:inIsIndeterminate]; michael@0: [cell setHorizontal:inIsHorizontal]; michael@0: [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] michael@0: : NSClearControlTint)]; michael@0: michael@0: DrawCellWithSnapping(cell, cgContext, inBoxRect, michael@0: progressSettings[inIsHorizontal][inIsIndeterminate], michael@0: VerticalAlignFactor(aFrame), mCellDrawView, michael@0: IsFrameRTL(aFrame)); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static const CellRenderSettings meterSetting = { michael@0: { michael@0: NSMakeSize(0, 16), // mini michael@0: NSMakeSize(0, 16), // small michael@0: NSMakeSize(0, 16) // regular michael@0: }, michael@0: { michael@0: NSZeroSize, NSZeroSize, NSZeroSize michael@0: }, michael@0: { michael@0: { // Leopard michael@0: {1, 1, 1, 1}, // mini michael@0: {1, 1, 1, 1}, // small michael@0: {1, 1, 1, 1} // regular michael@0: } michael@0: } michael@0: }; michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK michael@0: michael@0: NS_PRECONDITION(aFrame, "aFrame should not be null here!"); michael@0: michael@0: // When using -moz-meterbar on an non meter element, we will not be able to michael@0: // get all the needed information so we just draw an empty meter. michael@0: nsIContent* content = aFrame->GetContent(); michael@0: if (!(content && content->IsHTML(nsGkAtoms::meter))) { michael@0: DrawCellWithSnapping(mMeterBarCell, cgContext, inBoxRect, michael@0: meterSetting, VerticalAlignFactor(aFrame), michael@0: mCellDrawView, IsFrameRTL(aFrame)); michael@0: return; michael@0: } michael@0: michael@0: HTMLMeterElement* meterElement = static_cast(content); michael@0: double value = meterElement->Value(); michael@0: double min = meterElement->Min(); michael@0: double max = meterElement->Max(); michael@0: michael@0: NSLevelIndicatorCell* cell = mMeterBarCell; michael@0: michael@0: [cell setMinValue:min]; michael@0: [cell setMaxValue:max]; michael@0: [cell setDoubleValue:value]; michael@0: michael@0: /** michael@0: * The way HTML and Cocoa defines the meter/indicator widget are different. michael@0: * So, we are going to use a trick to get the Cocoa widget showing what we michael@0: * are expecting: we set the warningValue or criticalValue to the current michael@0: * value when we want to have the widget to be in the warning or critical michael@0: * state. michael@0: */ michael@0: EventStates states = aFrame->GetContent()->AsElement()->State(); michael@0: michael@0: // Reset previously set warning and critical values. michael@0: [cell setWarningValue:max+1]; michael@0: [cell setCriticalValue:max+1]; michael@0: michael@0: if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) { michael@0: [cell setWarningValue:value]; michael@0: } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) { michael@0: [cell setCriticalValue:value]; michael@0: } michael@0: michael@0: HIRect rect = CGRectStandardize(inBoxRect); michael@0: BOOL vertical = IsVerticalMeter(aFrame); michael@0: michael@0: CGContextSaveGState(cgContext); michael@0: michael@0: if (vertical) { michael@0: /** michael@0: * Cocoa doesn't provide a vertical meter bar so to show one, we have to michael@0: * show a rotated horizontal meter bar. michael@0: * Given that we want to show a vertical meter bar, we assume that the rect michael@0: * has vertical dimensions but we can't correctly draw a meter widget inside michael@0: * such a rectangle so we need to inverse width and height (and re-position) michael@0: * to get a rectangle with horizontal dimensions. michael@0: * Finally, we want to show a vertical meter so we want to rotate the result michael@0: * so it is vertical. We do that by changing the context. michael@0: */ michael@0: CGFloat tmp = rect.size.width; michael@0: rect.size.width = rect.size.height; michael@0: rect.size.height = tmp; michael@0: rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f; michael@0: rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f; michael@0: michael@0: CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect)); michael@0: CGContextRotateCTM(cgContext, -M_PI / 2.f); michael@0: CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect)); michael@0: } michael@0: michael@0: DrawCellWithSnapping(cell, cgContext, rect, michael@0: meterSetting, VerticalAlignFactor(aFrame), michael@0: mCellDrawView, !vertical && IsFrameRTL(aFrame)); michael@0: michael@0: CGContextRestoreGState(cgContext); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: HIThemeTabPaneDrawInfo tpdi; michael@0: michael@0: tpdi.version = 1; michael@0: tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive; michael@0: tpdi.direction = kThemeTabNorth; michael@0: tpdi.size = kHIThemeTabSizeNormal; michael@0: tpdi.kind = kHIThemeTabKindNormal; michael@0: michael@0: HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: EventStates inState, bool inIsVertical, michael@0: bool inIsReverse, int32_t inCurrentValue, michael@0: int32_t inMinValue, int32_t inMaxValue, michael@0: nsIFrame* aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: HIThemeTrackDrawInfo tdi; michael@0: michael@0: tdi.version = 0; michael@0: tdi.kind = kThemeMediumSlider; michael@0: tdi.bounds = inBoxRect; michael@0: tdi.min = inMinValue; michael@0: tdi.max = inMaxValue; michael@0: tdi.value = inCurrentValue; michael@0: tdi.attributes = kThemeTrackShowThumb; michael@0: if (!inIsVertical) michael@0: tdi.attributes |= kThemeTrackHorizontal; michael@0: if (inIsReverse) michael@0: tdi.attributes |= kThemeTrackRightToLeft; michael@0: if (inState.HasState(NS_EVENT_STATE_FOCUS)) michael@0: tdi.attributes |= kThemeTrackHasFocus; michael@0: if (IsDisabled(aFrame, inState)) michael@0: tdi.enableState = kThemeTrackDisabled; michael@0: else michael@0: tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive; michael@0: tdi.trackInfo.slider.thumbDir = kThemeThumbPlain; michael@0: tdi.trackInfo.slider.pressState = 0; michael@0: michael@0: HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter) michael@0: { michael@0: // Usually a separator is drawn by the segment to the right of the michael@0: // separator, but pressed and selected segments have higher priority. michael@0: if (!aBefore || !aAfter) michael@0: return nullptr; michael@0: if (IsSelectedButton(aAfter)) michael@0: return aAfter; michael@0: if (IsSelectedButton(aBefore) || IsPressedButton(aBefore)) michael@0: return aBefore; michael@0: return aAfter; michael@0: } michael@0: michael@0: CGRect michael@0: nsNativeThemeCocoa::SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft, michael@0: nsIFrame* aCurrent, nsIFrame* aRight) michael@0: { michael@0: // A separator between two segments should always be located in the leftmost michael@0: // pixel column of the segment to the right of the separator, regardless of michael@0: // who ends up drawing it. michael@0: // CoreUI draws the separators inside the drawing rect. michael@0: if (aLeft && SeparatorResponsibility(aLeft, aCurrent) == aLeft) { michael@0: // The left button draws the separator, so we need to make room for it. michael@0: aRect.origin.x += 1; michael@0: aRect.size.width -= 1; michael@0: } michael@0: if (SeparatorResponsibility(aCurrent, aRight) == aCurrent) { michael@0: // We draw the right separator, so we need to extend the draw rect into the michael@0: // segment to our right. michael@0: aRect.size.width += 1; michael@0: } michael@0: return aRect; michael@0: } michael@0: michael@0: static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast) michael@0: { michael@0: if (aIsFirst) { michael@0: if (aIsLast) michael@0: return @"kCUISegmentPositionOnly"; michael@0: return @"kCUISegmentPositionFirst"; michael@0: } michael@0: if (aIsLast) michael@0: return @"kCUISegmentPositionLast"; michael@0: return @"kCUISegmentPositionMiddle"; michael@0: } michael@0: michael@0: struct SegmentedControlRenderSettings { michael@0: const CGFloat* heights; michael@0: const NSString* widgetName; michael@0: const BOOL ignoresPressedWhenSelected; michael@0: const BOOL isToolbarControl; michael@0: }; michael@0: michael@0: static const CGFloat tabHeights[3] = { 17, 20, 23 }; michael@0: michael@0: static const SegmentedControlRenderSettings tabRenderSettings = { michael@0: tabHeights, @"tab", YES, NO michael@0: }; michael@0: michael@0: static const CGFloat toolbarButtonHeights[3] = { 15, 18, 22 }; michael@0: michael@0: static const SegmentedControlRenderSettings toolbarButtonRenderSettings = { michael@0: toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve", NO, YES michael@0: }; michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: EventStates inState, nsIFrame* aFrame, michael@0: const SegmentedControlRenderSettings& aSettings) michael@0: { michael@0: BOOL isActive = IsActive(aFrame, aSettings.isToolbarControl); michael@0: BOOL isFocused = inState.HasState(NS_EVENT_STATE_FOCUS); michael@0: BOOL isSelected = IsSelectedButton(aFrame); michael@0: BOOL isPressed = IsPressedButton(aFrame); michael@0: if (isSelected && aSettings.ignoresPressedWhenSelected) { michael@0: isPressed = NO; michael@0: } michael@0: michael@0: BOOL isRTL = IsFrameRTL(aFrame); michael@0: nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL); michael@0: nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL); michael@0: CGRect drawRect = SeparatorAdjustedRect(inBoxRect, left, aFrame, right); michael@0: if (drawRect.size.width * drawRect.size.height > CUIDRAW_MAX_AREA) { michael@0: return; michael@0: } michael@0: BOOL drawLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame; michael@0: BOOL drawRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame; michael@0: NSControlSize controlSize = FindControlSize(drawRect.size.height, aSettings.heights, 4.0f); michael@0: michael@0: CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext, michael@0: (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: michael@0: aSettings.widgetName, @"widget", michael@0: ToolbarButtonPosition(!left, !right), @"kCUIPositionKey", michael@0: [NSNumber numberWithBool:drawLeftSeparator], @"kCUISegmentLeadingSeparatorKey", michael@0: [NSNumber numberWithBool:drawRightSeparator], @"kCUISegmentTrailingSeparatorKey", michael@0: [NSNumber numberWithBool:isSelected], @"value", michael@0: (isPressed ? @"pressed" : (isActive ? @"normal" : @"inactive")), @"state", michael@0: [NSNumber numberWithBool:isFocused], @"focus", michael@0: CUIControlSizeForCocoaSize(controlSize), @"size", michael@0: [NSNumber numberWithBool:YES], @"is.flipped", michael@0: @"up", @"direction", michael@0: nil], michael@0: nil); michael@0: } michael@0: michael@0: static inline UInt8 michael@0: ConvertToPressState(EventStates aButtonState, UInt8 aPressState) michael@0: { michael@0: // If the button is pressed, return the press state passed in. Otherwise, return 0. michael@0: return aButtonState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER) ? aPressState : 0; michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame* aFrame, michael@0: EventStates aButtonStates[]) michael@0: { michael@0: static nsIContent::AttrValuesArray attributeValues[] = { michael@0: &nsGkAtoms::scrollbarUpTop, michael@0: &nsGkAtoms::scrollbarDownTop, michael@0: &nsGkAtoms::scrollbarUpBottom, michael@0: &nsGkAtoms::scrollbarDownBottom, michael@0: nullptr michael@0: }; michael@0: michael@0: // Get the state of any scrollbar buttons in our child frames michael@0: for (nsIFrame *childFrame = aFrame->GetFirstPrincipalChild(); michael@0: childFrame; michael@0: childFrame = childFrame->GetNextSibling()) { michael@0: michael@0: nsIContent *childContent = childFrame->GetContent(); michael@0: if (!childContent) continue; michael@0: int32_t attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sbattr, michael@0: attributeValues, eCaseMatters); michael@0: if (attrIndex < 0) continue; michael@0: michael@0: aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON); michael@0: } michael@0: } michael@0: michael@0: // Both of the following sets of numbers were derived by loading the testcase in michael@0: // bmo bug 380185 in Safari and observing its behavior for various heights of scrollbar. michael@0: // These magic numbers are the minimum sizes we can draw a scrollbar and still michael@0: // have room for everything to display, including the thumb michael@0: #define MIN_SCROLLBAR_SIZE_WITH_THUMB 61 michael@0: #define MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB 49 michael@0: // And these are the minimum sizes if we don't draw the thumb michael@0: #define MIN_SCROLLBAR_SIZE 56 michael@0: #define MIN_SMALL_SCROLLBAR_SIZE 46 michael@0: michael@0: void michael@0: nsNativeThemeCocoa::GetScrollbarDrawInfo(HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame, michael@0: const CGSize& aSize, bool aShouldGetButtonStates) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0); michael@0: int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0); michael@0: int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100); michael@0: int32_t thumbSize = CheckIntAttr(aFrame, nsGkAtoms::pageincrement, 10); michael@0: michael@0: bool isHorizontal = aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient, michael@0: nsGkAtoms::horizontal, eCaseMatters); michael@0: bool isSmall = aFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL; michael@0: michael@0: aTdi.version = 0; michael@0: aTdi.kind = isSmall ? kThemeSmallScrollBar : kThemeMediumScrollBar; michael@0: aTdi.bounds.origin = CGPointZero; michael@0: aTdi.bounds.size = aSize; michael@0: aTdi.min = minpos; michael@0: aTdi.max = maxpos; michael@0: aTdi.value = curpos; michael@0: aTdi.attributes = 0; michael@0: aTdi.enableState = kThemeTrackActive; michael@0: if (isHorizontal) michael@0: aTdi.attributes |= kThemeTrackHorizontal; michael@0: michael@0: aTdi.trackInfo.scrollbar.viewsize = (SInt32)thumbSize; michael@0: michael@0: // This should be done early on so things like "kThemeTrackNothingToScroll" can michael@0: // override the active enable state. michael@0: aTdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive; michael@0: michael@0: /* Only display features if we have enough room for them. michael@0: * Gecko still maintains the scrollbar info; this is just a visual issue (bug 380185). michael@0: */ michael@0: int32_t longSideLength = (int32_t)(isHorizontal ? (aSize.width) : (aSize.height)); michael@0: if (longSideLength >= (isSmall ? MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB : MIN_SCROLLBAR_SIZE_WITH_THUMB)) { michael@0: aTdi.attributes |= kThemeTrackShowThumb; michael@0: } michael@0: else if (longSideLength < (isSmall ? MIN_SMALL_SCROLLBAR_SIZE : MIN_SCROLLBAR_SIZE)) { michael@0: aTdi.enableState = kThemeTrackNothingToScroll; michael@0: return; michael@0: } michael@0: michael@0: aTdi.trackInfo.scrollbar.pressState = 0; michael@0: michael@0: // Only go get these scrollbar button states if we need it. For example, michael@0: // there's no reason to look up scrollbar button states when we're only michael@0: // creating a TrackDrawInfo to determine the size of the thumb. There's michael@0: // also no reason to do this on Lion or later, whose scrollbars have no michael@0: // arrow buttons. michael@0: if (aShouldGetButtonStates && !nsCocoaFeatures::OnLionOrLater()) { michael@0: EventStates buttonStates[4]; michael@0: GetScrollbarPressStates(aFrame, buttonStates); michael@0: NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"]; michael@0: // It seems that unless all four buttons are showing, kThemeTopOutsideArrowPressed is the correct constant for michael@0: // the up scrollbar button. michael@0: if ([buttonPlacement isEqualToString:@"DoubleBoth"]) { michael@0: aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) | michael@0: ConvertToPressState(buttonStates[1], kThemeTopInsideArrowPressed) | michael@0: ConvertToPressState(buttonStates[2], kThemeBottomInsideArrowPressed) | michael@0: ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed); michael@0: } else { michael@0: aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) | michael@0: ConvertToPressState(buttonStates[1], kThemeBottomOutsideArrowPressed) | michael@0: ConvertToPressState(buttonStates[2], kThemeTopOutsideArrowPressed) | michael@0: ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed); michael@0: } michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static void michael@0: RenderScrollbar(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) michael@0: { michael@0: HIThemeTrackDrawInfo* tdi = (HIThemeTrackDrawInfo*)aData; michael@0: HIThemeDrawTrack(tdi, NULL, cgContext, HITHEME_ORIENTATION); michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: HIThemeTrackDrawInfo tdi; michael@0: GetScrollbarDrawInfo(tdi, aFrame, aBoxRect.size, true); // True means we want the press states michael@0: RenderTransformedHIThemeControl(aCGContext, aBoxRect, RenderScrollbar, &tdi); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame) michael@0: { michael@0: // Walk our parents to find a scrollbar frame michael@0: nsIFrame *scrollbarFrame = aFrame; michael@0: do { michael@0: if (scrollbarFrame->GetType() == nsGkAtoms::scrollbarFrame) break; michael@0: } while ((scrollbarFrame = scrollbarFrame->GetParent())); michael@0: michael@0: // We return null if we can't find a parent scrollbar frame michael@0: return scrollbarFrame; michael@0: } michael@0: michael@0: static bool michael@0: ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow) michael@0: { michael@0: if (![aWindow isKindOfClass:[ToolbarWindow class]]) michael@0: return false; michael@0: michael@0: ToolbarWindow* win = (ToolbarWindow*)aWindow; michael@0: float unifiedToolbarHeight = [win unifiedToolbarHeight]; michael@0: return inBoxRect.origin.x == 0 && michael@0: inBoxRect.size.width >= [win frame].size.width && michael@0: CGRectGetMaxY(inBoxRect) <= unifiedToolbarHeight; michael@0: } michael@0: michael@0: // By default, kCUIWidgetWindowFrame drawing draws rounded corners in the michael@0: // upper corners. Depending on the context type, it fills the background in michael@0: // the corners with black or leaves it transparent. Unfortunately, this corner michael@0: // rounding interacts poorly with the window corner masking we apply during michael@0: // titlebar drawing and results in small remnants of the corner background michael@0: // appearing at the rounded edge. michael@0: // So we draw square corners. michael@0: static void michael@0: DrawNativeTitlebarToolbarWithSquareCorners(CGContextRef aContext, const CGRect& aRect, michael@0: CGFloat aUnifiedHeight, BOOL aIsMain) michael@0: { michael@0: // We extend the draw rect horizontally and clip away the rounded corners. michael@0: const CGFloat extendHorizontal = 10; michael@0: CGRect drawRect = CGRectInset(aRect, -extendHorizontal, 0); michael@0: if (drawRect.size.width * drawRect.size.height <= CUIDRAW_MAX_AREA) { michael@0: CGContextSaveGState(aContext); michael@0: CGContextClipToRect(aContext, aRect); michael@0: michael@0: CUIDraw([NSWindow coreUIRenderer], drawRect, aContext, michael@0: (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: michael@0: @"kCUIWidgetWindowFrame", @"widget", michael@0: @"regularwin", @"windowtype", michael@0: (aIsMain ? @"normal" : @"inactive"), @"state", michael@0: [NSNumber numberWithDouble:aUnifiedHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey", michael@0: [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey", michael@0: [NSNumber numberWithBool:YES], @"is.flipped", michael@0: nil], michael@0: nil); michael@0: michael@0: CGContextRestoreGState(aContext); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: NSWindow* aWindow) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: CGContextSaveGState(cgContext); michael@0: CGContextClipToRect(cgContext, inBoxRect); michael@0: michael@0: CGFloat unifiedHeight = std::max([(ToolbarWindow*)aWindow unifiedToolbarHeight], michael@0: inBoxRect.size.height); michael@0: BOOL isMain = [aWindow isMainWindow]; michael@0: CGFloat titlebarHeight = unifiedHeight - inBoxRect.size.height; michael@0: CGRect drawRect = CGRectMake(inBoxRect.origin.x, inBoxRect.origin.y - titlebarHeight, michael@0: inBoxRect.size.width, inBoxRect.size.height + titlebarHeight); michael@0: DrawNativeTitlebarToolbarWithSquareCorners(cgContext, drawRect, unifiedHeight, isMain); michael@0: michael@0: CGContextRestoreGState(cgContext); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect, michael@0: nsIFrame *aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (inBoxRect.size.height < 2.0f) michael@0: return; michael@0: michael@0: CGContextSaveGState(cgContext); michael@0: CGContextClipToRect(cgContext, inBoxRect); michael@0: michael@0: // kCUIWidgetWindowFrame draws a complete window frame with both title bar michael@0: // and bottom bar. We only want the bottom bar, so we extend the draw rect michael@0: // upwards to make space for the title bar, and then we clip it away. michael@0: CGRect drawRect = inBoxRect; michael@0: const int extendUpwards = 40; michael@0: drawRect.origin.y -= extendUpwards; michael@0: drawRect.size.height += extendUpwards; michael@0: if (drawRect.size.width * drawRect.size.height <= CUIDRAW_MAX_AREA) { michael@0: CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext, michael@0: (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: michael@0: @"kCUIWidgetWindowFrame", @"widget", michael@0: @"regularwin", @"windowtype", michael@0: (IsActive(aFrame, YES) ? @"normal" : @"inactive"), @"state", michael@0: [NSNumber numberWithInt:inBoxRect.size.height], @"kCUIWindowFrameBottomBarHeightKey", michael@0: [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawBottomBarSeparatorKey", michael@0: [NSNumber numberWithBool:YES], @"is.flipped", michael@0: nil], michael@0: nil); michael@0: } michael@0: michael@0: CGContextRestoreGState(cgContext); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawNativeTitlebar(CGContextRef aContext, CGRect aTitlebarRect, michael@0: CGFloat aUnifiedHeight, BOOL aIsMain) michael@0: { michael@0: CGFloat unifiedHeight = std::max(aUnifiedHeight, aTitlebarRect.size.height); michael@0: DrawNativeTitlebarToolbarWithSquareCorners(aContext, aTitlebarRect, unifiedHeight, aIsMain); michael@0: } michael@0: michael@0: static void michael@0: RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData) michael@0: { michael@0: HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData; michael@0: HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal); michael@0: } michael@0: michael@0: void michael@0: nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect, michael@0: nsIFrame *aFrame) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: HIThemeGrowBoxDrawInfo drawInfo; michael@0: drawInfo.version = 0; michael@0: drawInfo.state = kThemeStateActive; michael@0: drawInfo.kind = kHIThemeGrowBoxKindNormal; michael@0: drawInfo.direction = kThemeGrowRight | kThemeGrowDown; michael@0: drawInfo.size = kHIThemeGrowBoxSizeNormal; michael@0: michael@0: RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo, michael@0: IsFrameRTL(aFrame)); michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: static bool michael@0: IsHiDPIContext(nsDeviceContext* aContext) michael@0: { michael@0: return nsPresContext::AppUnitsPerCSSPixel() >= michael@0: 2 * aContext->UnscaledAppUnitsPerDevPixel(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext, michael@0: nsIFrame* aFrame, michael@0: uint8_t aWidgetType, michael@0: const nsRect& aRect, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: // setup to draw into the correct port michael@0: int32_t p2a = aContext->AppUnitsPerDevPixel(); michael@0: michael@0: gfxRect nativeDirtyRect(aDirtyRect.x, aDirtyRect.y, michael@0: aDirtyRect.width, aDirtyRect.height); michael@0: gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height); michael@0: nativeWidgetRect.ScaleInverse(gfxFloat(p2a)); michael@0: nativeDirtyRect.ScaleInverse(gfxFloat(p2a)); michael@0: nativeWidgetRect.Round(); michael@0: if (nativeWidgetRect.IsEmpty()) michael@0: return NS_OK; // Don't attempt to draw invisible widgets. michael@0: michael@0: gfxContext* thebesCtx = aContext->ThebesContext(); michael@0: if (!thebesCtx) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: gfxContextMatrixAutoSaveRestore save(thebesCtx); michael@0: michael@0: bool hidpi = IsHiDPIContext(aContext->DeviceContext()); michael@0: if (hidpi) { michael@0: // Use high-resolution drawing. michael@0: nativeWidgetRect.ScaleInverse(2.0f); michael@0: nativeDirtyRect.ScaleInverse(2.0f); michael@0: thebesCtx->Scale(2.0f, 2.0f); michael@0: } michael@0: michael@0: gfxQuartzNativeDrawing nativeDrawing(thebesCtx, nativeDirtyRect, michael@0: hidpi ? 2.0f : 1.0f); michael@0: michael@0: CGContextRef cgContext = nativeDrawing.BeginNativeDrawing(); michael@0: if (cgContext == nullptr) { michael@0: // The Quartz surface handles 0x0 surfaces by internally michael@0: // making all operations no-ops; there's no cgcontext created for them. michael@0: // Unfortunately, this means that callers that want to render michael@0: // directly to the CGContext need to be aware of this quirk. michael@0: return NS_OK; michael@0: } michael@0: michael@0: #if 0 michael@0: if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) { michael@0: fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n", michael@0: aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height); michael@0: fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n", michael@0: mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0); michael@0: fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n", michael@0: mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty); michael@0: CGAffineTransform mm = CGContextGetCTM(cgContext); michael@0: fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n", michael@0: mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty); michael@0: } michael@0: #endif michael@0: michael@0: CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(), michael@0: nativeWidgetRect.Width(), nativeWidgetRect.Height()); michael@0: michael@0: #if 0 michael@0: fprintf(stderr, " --> macRect %f %f %f %f\n", michael@0: macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height); michael@0: CGRect bounds = CGContextGetClipBoundingBox(cgContext); michael@0: fprintf(stderr, " --> clip bounds: %f %f %f %f\n", michael@0: bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height); michael@0: michael@0: //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1); michael@0: //CGContextFillRect(cgContext, bounds); michael@0: #endif michael@0: michael@0: EventStates eventState = GetContentState(aFrame, aWidgetType); michael@0: michael@0: switch (aWidgetType) { michael@0: case NS_THEME_DIALOG: { michael@0: HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION); michael@0: CGContextFillRect(cgContext, macRect); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_MENUPOPUP: { michael@0: HIThemeMenuDrawInfo mdi; michael@0: memset(&mdi, 0, sizeof(mdi)); michael@0: mdi.version = 0; michael@0: mdi.menuType = IsDisabled(aFrame, eventState) ? michael@0: static_cast(kThemeMenuTypeInactive) : michael@0: static_cast(kThemeMenuTypePopUp); michael@0: michael@0: bool isLeftOfParent = false; michael@0: if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) { michael@0: mdi.menuType = kThemeMenuTypeHierarchical; michael@0: } michael@0: michael@0: // The rounded corners draw outside the frame. michael@0: CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4, michael@0: macRect.size.width, macRect.size.height - 8); michael@0: HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_MENUITEM: { michael@0: bool isTransparent; michael@0: if (thebesCtx->IsCairo()) { michael@0: isTransparent = thebesCtx->OriginalSurface()->GetContentType() == gfxContentType::COLOR_ALPHA; michael@0: } else { michael@0: SurfaceFormat format = thebesCtx->GetDrawTarget()->GetFormat(); michael@0: isTransparent = (format == SurfaceFormat::R8G8B8A8) || michael@0: (format == SurfaceFormat::B8G8R8A8); michael@0: } michael@0: if (isTransparent) { michael@0: // Clear the background to get correct transparency. michael@0: CGContextClearRect(cgContext, macRect); michael@0: } michael@0: michael@0: // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain? michael@0: HIThemeMenuItemDrawInfo drawInfo; michael@0: memset(&drawInfo, 0, sizeof(drawInfo)); michael@0: drawInfo.version = 0; michael@0: drawInfo.itemType = kThemeMenuItemPlain; michael@0: drawInfo.state = (IsDisabled(aFrame, eventState) ? michael@0: static_cast(kThemeMenuDisabled) : michael@0: CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ? michael@0: static_cast(kThemeMenuSelected) : michael@0: static_cast(kThemeMenuActive)); michael@0: michael@0: // XXX pass in the menu rect instead of always using the item rect michael@0: HIRect ignored; michael@0: HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_MENUSEPARATOR: { michael@0: ThemeMenuState menuState; michael@0: if (IsDisabled(aFrame, eventState)) { michael@0: menuState = kThemeMenuDisabled; michael@0: } michael@0: else { michael@0: menuState = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ? michael@0: kThemeMenuSelected : kThemeMenuActive; michael@0: } michael@0: michael@0: HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState }; michael@0: HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_TOOLTIP: michael@0: CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950); michael@0: CGContextFillRect(cgContext, macRect); michael@0: break; michael@0: michael@0: case NS_THEME_CHECKBOX: michael@0: case NS_THEME_RADIO: { michael@0: bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX); michael@0: DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox), michael@0: eventState, aFrame); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_BUTTON: michael@0: if (IsDefaultButton(aFrame)) { michael@0: if (!IsDisabled(aFrame, eventState) && FrameIsInActiveWindow(aFrame) && michael@0: !QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) { michael@0: NS_WARNING("Unable to animate button!"); michael@0: } michael@0: DrawButton(cgContext, kThemePushButton, macRect, true, michael@0: kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame); michael@0: } else if (IsButtonTypeMenu(aFrame)) { michael@0: DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame); michael@0: } else { michael@0: DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_MOZ_MAC_HELP_BUTTON: michael@0: DrawPushButton(cgContext, macRect, eventState, aWidgetType, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_BUTTON_BEVEL: michael@0: DrawButton(cgContext, kThemeMediumBevelButton, macRect, michael@0: IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone, michael@0: eventState, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_SPINNER: { michael@0: nsIContent* content = aFrame->GetContent(); michael@0: if (content->IsHTML()) { michael@0: // In HTML the theming for the spin buttons is drawn individually into michael@0: // their own backgrounds instead of being drawn into the background of michael@0: // their spinner parent as it is for XUL. michael@0: break; michael@0: } michael@0: ThemeDrawState state = kThemeStateActive; michael@0: if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, michael@0: NS_LITERAL_STRING("up"), eCaseMatters)) { michael@0: state = kThemeStatePressedUp; michael@0: } michael@0: else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, michael@0: NS_LITERAL_STRING("down"), eCaseMatters)) { michael@0: state = kThemeStatePressedDown; michael@0: } michael@0: michael@0: DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state, michael@0: kThemeAdornmentNone, eventState, aFrame); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_SPINNER_UP_BUTTON: michael@0: case NS_THEME_SPINNER_DOWN_BUTTON: { michael@0: nsNumberControlFrame* numberControlFrame = michael@0: nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame); michael@0: if (numberControlFrame) { michael@0: ThemeDrawState state = kThemeStateActive; michael@0: if (numberControlFrame->SpinnerUpButtonIsDepressed()) { michael@0: state = kThemeStatePressedUp; michael@0: } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) { michael@0: state = kThemeStatePressedDown; michael@0: } michael@0: DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state, michael@0: kThemeAdornmentNone, eventState, aFrame, aWidgetType); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_TOOLBAR_BUTTON: michael@0: DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings); michael@0: break; michael@0: michael@0: case NS_THEME_TOOLBAR_SEPARATOR: { michael@0: HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive }; michael@0: HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR: michael@0: case NS_THEME_TOOLBAR: { michael@0: NSWindow* win = NativeWindowForFrame(aFrame); michael@0: if (ToolbarCanBeUnified(cgContext, macRect, win)) { michael@0: DrawUnifiedToolbar(cgContext, macRect, win); michael@0: break; michael@0: } michael@0: BOOL isMain = [win isMainWindow]; michael@0: CGRect drawRect = macRect; michael@0: michael@0: // top border michael@0: drawRect.size.height = 1.0f; michael@0: DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain); michael@0: michael@0: // background michael@0: drawRect.origin.y += drawRect.size.height; michael@0: drawRect.size.height = macRect.size.height - 2.0f; michael@0: DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, isMain); michael@0: michael@0: // bottom border michael@0: drawRect.origin.y += drawRect.size.height; michael@0: drawRect.size.height = 1.0f; michael@0: DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, isMain); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_WINDOW_TITLEBAR: { michael@0: NSWindow* win = NativeWindowForFrame(aFrame); michael@0: BOOL isMain = [win isMainWindow]; michael@0: float unifiedToolbarHeight = [win isKindOfClass:[ToolbarWindow class]] ? michael@0: [(ToolbarWindow*)win unifiedToolbarHeight] : macRect.size.height; michael@0: DrawNativeTitlebar(cgContext, macRect, unifiedToolbarHeight, isMain); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_TOOLBOX: { michael@0: HIThemeHeaderDrawInfo hdi = { 0, kThemeStateActive, kHIThemeHeaderKindWindow }; michael@0: HIThemeDrawHeader(&macRect, &hdi, cgContext, HITHEME_ORIENTATION); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_STATUSBAR: michael@0: DrawStatusBar(cgContext, macRect, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_DROPDOWN: michael@0: case NS_THEME_DROPDOWN_TEXTFIELD: michael@0: DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_DROPDOWN_BUTTON: michael@0: DrawButton(cgContext, kThemeArrowButton, macRect, false, kThemeButtonOn, michael@0: kThemeAdornmentArrowDownArrow, eventState, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_GROUPBOX: { michael@0: HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary }; michael@0: HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_TEXTFIELD: michael@0: case NS_THEME_NUMBER_INPUT: michael@0: // HIThemeSetFill is not available on 10.3 michael@0: CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0); michael@0: CGContextFillRect(cgContext, macRect); michael@0: michael@0: // XUL textboxes set the native appearance on the containing box, while michael@0: // concrete focus is set on the html:input element within it. We can michael@0: // though, check the focused attribute of xul textboxes in this case. michael@0: // On Mac, focus rings are always shown for textboxes, so we do not need michael@0: // to check the window's focus ring state here michael@0: if (aFrame->GetContent()->IsXUL() && IsFocused(aFrame)) { michael@0: eventState |= NS_EVENT_STATE_FOCUS; michael@0: } michael@0: michael@0: DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect, michael@0: IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState); michael@0: break; michael@0: michael@0: case NS_THEME_SEARCHFIELD: michael@0: DrawSearchField(cgContext, macRect, aFrame, eventState); michael@0: break; michael@0: michael@0: case NS_THEME_PROGRESSBAR: michael@0: if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) { michael@0: NS_WARNING("Unable to animate progressbar!"); michael@0: } michael@0: DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState), michael@0: aFrame->StyleDisplay()->mOrient != NS_STYLE_ORIENT_VERTICAL, michael@0: GetProgressValue(aFrame), GetProgressMaxValue(aFrame), aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_PROGRESSBAR_VERTICAL: michael@0: DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState), michael@0: false, GetProgressValue(aFrame), michael@0: GetProgressMaxValue(aFrame), aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_METERBAR: michael@0: DrawMeter(cgContext, macRect, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_PROGRESSBAR_CHUNK: michael@0: case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL: michael@0: case NS_THEME_METERBAR_CHUNK: michael@0: // Do nothing: progress and meter bars cases will draw chunks. michael@0: break; michael@0: michael@0: case NS_THEME_TREEVIEW_TWISTY: michael@0: DrawButton(cgContext, kThemeDisclosureButton, macRect, false, michael@0: kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_TREEVIEW_TWISTY_OPEN: michael@0: DrawButton(cgContext, kThemeDisclosureButton, macRect, false, michael@0: kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_TREEVIEW_HEADER_CELL: { michael@0: TreeSortDirection sortDirection = GetTreeSortDirection(aFrame); michael@0: DrawButton(cgContext, kThemeListHeaderButton, macRect, false, michael@0: sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn, michael@0: sortDirection == eTreeSortDirection_Ascending ? michael@0: kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_TREEVIEW_TREEITEM: michael@0: case NS_THEME_TREEVIEW: michael@0: // HIThemeSetFill is not available on 10.3 michael@0: // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION); michael@0: CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0); michael@0: CGContextFillRect(cgContext, macRect); michael@0: break; michael@0: michael@0: case NS_THEME_TREEVIEW_HEADER: michael@0: // do nothing, taken care of by individual header cells michael@0: case NS_THEME_TREEVIEW_HEADER_SORTARROW: michael@0: // do nothing, taken care of by treeview header michael@0: case NS_THEME_TREEVIEW_LINE: michael@0: // do nothing, these lines don't exist on macos michael@0: break; michael@0: michael@0: case NS_THEME_SCALE_HORIZONTAL: michael@0: case NS_THEME_SCALE_VERTICAL: { michael@0: int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0); michael@0: int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0); michael@0: int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100); michael@0: if (!maxpos) michael@0: maxpos = 100; michael@0: michael@0: bool reverse = aFrame->GetContent()-> michael@0: AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, michael@0: NS_LITERAL_STRING("reverse"), eCaseMatters); michael@0: DrawScale(cgContext, macRect, eventState, michael@0: (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse, michael@0: curpos, minpos, maxpos, aFrame); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_SCALE_THUMB_HORIZONTAL: michael@0: case NS_THEME_SCALE_THUMB_VERTICAL: michael@0: // do nothing, drawn by scale michael@0: break; michael@0: michael@0: case NS_THEME_RANGE: { michael@0: nsRangeFrame *rangeFrame = do_QueryFrame(aFrame); michael@0: if (!rangeFrame) { michael@0: break; michael@0: } michael@0: // DrawScale requires integer min, max and value. This is purely for michael@0: // drawing, so we normalize to a range 0-1000 here. michael@0: int32_t value = int32_t(rangeFrame->GetValueAsFractionOfRange() * 1000); michael@0: int32_t min = 0; michael@0: int32_t max = 1000; michael@0: bool isVertical = !IsRangeHorizontal(aFrame); michael@0: bool reverseDir = michael@0: isVertical || michael@0: rangeFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: DrawScale(cgContext, macRect, eventState, isVertical, reverseDir, michael@0: value, min, max, aFrame); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SCROLLBAR_SMALL: michael@0: case NS_THEME_SCROLLBAR: michael@0: if (!nsLookAndFeel::UseOverlayScrollbars()) { michael@0: DrawScrollbar(cgContext, macRect, aFrame); michael@0: } michael@0: break; michael@0: case NS_THEME_SCROLLBAR_THUMB_VERTICAL: michael@0: case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL: michael@0: if (nsLookAndFeel::UseOverlayScrollbars()) { michael@0: BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL); michael@0: BOOL isRolledOver = CheckBooleanAttr(GetParentScrollbarFrame(aFrame), michael@0: nsGkAtoms::hover); michael@0: if (!nsCocoaFeatures::OnMountainLionOrLater() || !isRolledOver) { michael@0: if (isHorizontal) { michael@0: macRect.origin.y += 4; michael@0: macRect.size.height -= 4; michael@0: } else { michael@0: if (aFrame->StyleVisibility()->mDirection != michael@0: NS_STYLE_DIRECTION_RTL) { michael@0: macRect.origin.x += 4; michael@0: } michael@0: macRect.size.width -= 4; michael@0: } michael@0: } michael@0: const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame); michael@0: CUIDraw([NSWindow coreUIRenderer], macRect, cgContext, michael@0: (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: michael@0: @"kCUIWidgetOverlayScrollBar", @"widget", michael@0: @"regular", @"size", michael@0: (isRolledOver ? @"rollover" : @""), @"state", michael@0: (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey", michael@0: (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey", michael@0: [NSNumber numberWithBool:YES], @"indiconly", michael@0: [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey", michael@0: [NSNumber numberWithBool:YES], @"is.flipped", michael@0: nil], michael@0: nil); michael@0: } michael@0: break; michael@0: case NS_THEME_SCROLLBAR_BUTTON_UP: michael@0: case NS_THEME_SCROLLBAR_BUTTON_LEFT: michael@0: #if SCROLLBARS_VISUAL_DEBUG michael@0: CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6); michael@0: CGContextFillRect(cgContext, macRect); michael@0: #endif michael@0: break; michael@0: case NS_THEME_SCROLLBAR_BUTTON_DOWN: michael@0: case NS_THEME_SCROLLBAR_BUTTON_RIGHT: michael@0: #if SCROLLBARS_VISUAL_DEBUG michael@0: CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6); michael@0: CGContextFillRect(cgContext, macRect); michael@0: #endif michael@0: break; michael@0: case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL: michael@0: case NS_THEME_SCROLLBAR_TRACK_VERTICAL: michael@0: if (nsLookAndFeel::UseOverlayScrollbars() && michael@0: CheckBooleanAttr(GetParentScrollbarFrame(aFrame), nsGkAtoms::hover)) { michael@0: BOOL isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL); michael@0: if (!nsCocoaFeatures::OnMountainLionOrLater()) { michael@0: // On OSX 10.7, scrollbars don't grow when hovered. The adjustments michael@0: // below were obtained by trial and error. michael@0: if (isHorizontal) { michael@0: macRect.origin.y += 2.0; michael@0: } else { michael@0: if (aFrame->StyleVisibility()->mDirection != michael@0: NS_STYLE_DIRECTION_RTL) { michael@0: macRect.origin.x += 3.0; michael@0: } else { michael@0: macRect.origin.x -= 1.0; michael@0: } michael@0: } michael@0: } michael@0: const BOOL isOnTopOfDarkBackground = IsDarkBackground(aFrame); michael@0: CUIDraw([NSWindow coreUIRenderer], macRect, cgContext, michael@0: (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys: michael@0: @"kCUIWidgetOverlayScrollBar", @"widget", michael@0: @"regular", @"size", michael@0: (isHorizontal ? @"kCUIOrientHorizontal" : @"kCUIOrientVertical"), @"kCUIOrientationKey", michael@0: (isOnTopOfDarkBackground ? @"kCUIVariantWhite" : @""), @"kCUIVariantKey", michael@0: [NSNumber numberWithBool:YES], @"noindicator", michael@0: [NSNumber numberWithBool:YES], @"kCUIThumbProportionKey", michael@0: [NSNumber numberWithBool:YES], @"is.flipped", michael@0: nil], michael@0: nil); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_TEXTFIELD_MULTILINE: { michael@0: // we have to draw this by hand because there is no HITheme value for it michael@0: CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0); michael@0: michael@0: CGContextFillRect(cgContext, macRect); michael@0: michael@0: CGContextSetLineWidth(cgContext, 1.0); michael@0: CGContextSetShouldAntialias(cgContext, false); michael@0: michael@0: // stroke everything but the top line of the text area michael@0: CGContextSetRGBStrokeColor(cgContext, 0.6, 0.6, 0.6, 1.0); michael@0: CGContextBeginPath(cgContext); michael@0: CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1); michael@0: CGContextAddLineToPoint(cgContext, macRect.origin.x, macRect.origin.y + macRect.size.height); michael@0: CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + macRect.size.height); michael@0: CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1); michael@0: CGContextStrokePath(cgContext); michael@0: michael@0: // stroke the line across the top of the text area michael@0: CGContextSetRGBStrokeColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0); michael@0: CGContextBeginPath(cgContext); michael@0: CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1); michael@0: CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1); michael@0: CGContextStrokePath(cgContext); michael@0: michael@0: // draw a focus ring michael@0: if (eventState.HasState(NS_EVENT_STATE_FOCUS)) { michael@0: // We need to bring the rectangle in by 1 pixel on each side. michael@0: CGRect cgr = CGRectMake(macRect.origin.x + 1, michael@0: macRect.origin.y + 1, michael@0: macRect.size.width - 2, michael@0: macRect.size.height - 2); michael@0: HIThemeDrawFocusRect(&cgr, true, cgContext, kHIThemeOrientationNormal); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_LISTBOX: { michael@0: // We have to draw this by hand because kHIThemeFrameListBox drawing michael@0: // is buggy on 10.5, see bug 579259. michael@0: CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0); michael@0: CGContextFillRect(cgContext, macRect); michael@0: michael@0: // #8E8E8E for the top border, #BEBEBE for the rest. michael@0: float x = macRect.origin.x, y = macRect.origin.y; michael@0: float w = macRect.size.width, h = macRect.size.height; michael@0: CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0); michael@0: CGContextFillRect(cgContext, CGRectMake(x, y, w, 1)); michael@0: CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0); michael@0: CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1)); michael@0: CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1)); michael@0: CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1)); michael@0: } michael@0: break; michael@0: michael@0: case NS_THEME_TAB: michael@0: DrawSegment(cgContext, macRect, eventState, aFrame, tabRenderSettings); michael@0: break; michael@0: michael@0: case NS_THEME_TAB_PANELS: michael@0: DrawTabPanel(cgContext, macRect, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_RESIZER: michael@0: DrawResizer(cgContext, macRect, aFrame); michael@0: break; michael@0: } michael@0: michael@0: nativeDrawing.EndNativeDrawing(); michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: nsIntMargin michael@0: nsNativeThemeCocoa::RTLAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame) michael@0: { michael@0: if (IsFrameRTL(aFrame)) { michael@0: // Return a copy of aMargin w/ right & left reversed: michael@0: return nsIntMargin(aMargin.top, aMargin.left, michael@0: aMargin.bottom, aMargin.right); michael@0: } michael@0: michael@0: return aMargin; michael@0: } michael@0: michael@0: static const nsIntMargin kAquaDropdownBorder(1, 22, 2, 5); michael@0: static const nsIntMargin kAquaComboboxBorder(3, 20, 3, 4); michael@0: static const nsIntMargin kAquaSearchfieldBorder(3, 5, 2, 19); michael@0: michael@0: NS_IMETHODIMP michael@0: nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext, michael@0: nsIFrame* aFrame, michael@0: uint8_t aWidgetType, michael@0: nsIntMargin* aResult) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: aResult->SizeTo(0, 0, 0, 0); michael@0: michael@0: switch (aWidgetType) { michael@0: case NS_THEME_BUTTON: michael@0: { michael@0: if (IsButtonTypeMenu(aFrame)) { michael@0: *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame); michael@0: } else { michael@0: aResult->SizeTo(1, 7, 3, 7); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_TOOLBAR_BUTTON: michael@0: { michael@0: aResult->SizeTo(1, 4, 1, 4); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_CHECKBOX: michael@0: case NS_THEME_RADIO: michael@0: { michael@0: // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight michael@0: // assume a border width of 2px. michael@0: aResult->SizeTo(2, 2, 2, 2); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_DROPDOWN: michael@0: case NS_THEME_DROPDOWN_BUTTON: michael@0: *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_DROPDOWN_TEXTFIELD: michael@0: *aResult = RTLAwareMargin(kAquaComboboxBorder, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_NUMBER_INPUT: michael@0: case NS_THEME_TEXTFIELD: michael@0: { michael@0: SInt32 frameOutset = 0; michael@0: ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset); michael@0: michael@0: SInt32 textPadding = 0; michael@0: ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding); michael@0: michael@0: frameOutset += textPadding; michael@0: michael@0: aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_TEXTFIELD_MULTILINE: michael@0: aResult->SizeTo(1, 1, 1, 1); michael@0: break; michael@0: michael@0: case NS_THEME_SEARCHFIELD: michael@0: *aResult = RTLAwareMargin(kAquaSearchfieldBorder, aFrame); michael@0: break; michael@0: michael@0: case NS_THEME_LISTBOX: michael@0: { michael@0: SInt32 frameOutset = 0; michael@0: ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset); michael@0: aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL: michael@0: case NS_THEME_SCROLLBAR_TRACK_VERTICAL: michael@0: { michael@0: bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL); michael@0: michael@0: // On Lion and later, scrollbars have no arrows. michael@0: if (!nsCocoaFeatures::OnLionOrLater()) { michael@0: // There's only an endcap to worry about when both arrows are on the bottom michael@0: NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"]; michael@0: if (!buttonPlacement || [buttonPlacement isEqualToString:@"DoubleMax"]) { michael@0: nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame); michael@0: if (!scrollbarFrame) return NS_ERROR_FAILURE; michael@0: bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL); michael@0: michael@0: // There isn't a metric for this, so just hardcode a best guess at the value. michael@0: // This value is even less exact due to the fact that the endcap is partially concave. michael@0: int32_t endcapSize = isSmall ? 5 : 6; michael@0: michael@0: if (isHorizontal) michael@0: aResult->SizeTo(0, 0, 0, endcapSize); michael@0: else michael@0: aResult->SizeTo(endcapSize, 0, 0, 0); michael@0: } michael@0: } michael@0: michael@0: if (nsLookAndFeel::UseOverlayScrollbars()) { michael@0: if (isHorizontal) { michael@0: aResult->SizeTo(2, 1, 1, 1); michael@0: } else { michael@0: aResult->SizeTo(1, 1, 1, 2); michael@0: } michael@0: } michael@0: michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_STATUSBAR: michael@0: aResult->SizeTo(1, 0, 0, 0); michael@0: break; michael@0: } michael@0: michael@0: if (IsHiDPIContext(aContext)) { michael@0: *aResult = *aResult + *aResult; // doubled michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: // Return false here to indicate that CSS padding values should be used. There is michael@0: // no reason to make a distinction between padding and border values, just specify michael@0: // whatever values you want in GetWidgetBorder and only use this to return true michael@0: // if you want to override CSS padding values. michael@0: bool michael@0: nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext, michael@0: nsIFrame* aFrame, michael@0: uint8_t aWidgetType, michael@0: nsIntMargin* aResult) michael@0: { michael@0: // We don't want CSS padding being used for certain widgets. michael@0: // See bug 381639 for an example of why. michael@0: switch (aWidgetType) { michael@0: case NS_THEME_BUTTON: michael@0: // Radios and checkboxes return a fixed size in GetMinimumWidgetSize michael@0: // and have a meaningful baseline, so they can't have michael@0: // author-specified padding. michael@0: case NS_THEME_CHECKBOX: michael@0: case NS_THEME_RADIO: michael@0: aResult->SizeTo(0, 0, 0, 0); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame, michael@0: uint8_t aWidgetType, nsRect* aOverflowRect) michael@0: { michael@0: int32_t p2a = aContext->AppUnitsPerDevPixel(); michael@0: switch (aWidgetType) { michael@0: case NS_THEME_BUTTON: michael@0: case NS_THEME_MOZ_MAC_HELP_BUTTON: michael@0: case NS_THEME_TOOLBAR_BUTTON: michael@0: case NS_THEME_NUMBER_INPUT: michael@0: case NS_THEME_TEXTFIELD: michael@0: case NS_THEME_TEXTFIELD_MULTILINE: michael@0: case NS_THEME_SEARCHFIELD: michael@0: case NS_THEME_LISTBOX: michael@0: case NS_THEME_DROPDOWN: michael@0: case NS_THEME_DROPDOWN_BUTTON: michael@0: case NS_THEME_DROPDOWN_TEXTFIELD: michael@0: case NS_THEME_CHECKBOX: michael@0: case NS_THEME_RADIO: michael@0: case NS_THEME_TAB: michael@0: { michael@0: // We assume that the above widgets can draw a focus ring that will be less than michael@0: // or equal to 4 pixels thick. michael@0: nsIntMargin extraSize = nsIntMargin(MAX_FOCUS_RING_WIDTH, michael@0: MAX_FOCUS_RING_WIDTH, michael@0: MAX_FOCUS_RING_WIDTH, michael@0: MAX_FOCUS_RING_WIDTH); michael@0: nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a), michael@0: NSIntPixelsToAppUnits(extraSize.right, p2a), michael@0: NSIntPixelsToAppUnits(extraSize.bottom, p2a), michael@0: NSIntPixelsToAppUnits(extraSize.left, p2a)); michael@0: aOverflowRect->Inflate(m); michael@0: return true; michael@0: } michael@0: case NS_THEME_PROGRESSBAR: michael@0: { michael@0: // Progress bars draw a 2 pixel white shadow under their progress indicators michael@0: nsMargin m(0, 0, NSIntPixelsToAppUnits(2, p2a), 0); michael@0: aOverflowRect->Inflate(m); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static const int32_t kRegularScrollbarThumbMinSize = 26; michael@0: static const int32_t kSmallScrollbarThumbMinSize = 26; michael@0: michael@0: NS_IMETHODIMP michael@0: nsNativeThemeCocoa::GetMinimumWidgetSize(nsRenderingContext* aContext, michael@0: nsIFrame* aFrame, michael@0: uint8_t aWidgetType, michael@0: nsIntSize* aResult, michael@0: bool* aIsOverridable) michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; michael@0: michael@0: aResult->SizeTo(0,0); michael@0: *aIsOverridable = true; michael@0: michael@0: switch (aWidgetType) { michael@0: case NS_THEME_BUTTON: michael@0: { michael@0: aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width, michael@0: pushButtonSettings.naturalSizes[miniControlSize].height); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_MOZ_MAC_HELP_BUTTON: michael@0: { michael@0: aResult->SizeTo(kHelpButtonSize.width, kHelpButtonSize.height); michael@0: *aIsOverridable = false; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_TOOLBAR_BUTTON: michael@0: { michael@0: aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SPINNER: michael@0: case NS_THEME_SPINNER_UP_BUTTON: michael@0: case NS_THEME_SPINNER_DOWN_BUTTON: michael@0: { michael@0: SInt32 buttonHeight = 0, buttonWidth = 0; michael@0: if (aFrame->GetContent()->IsXUL()) { michael@0: ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth); michael@0: ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight); michael@0: } else { michael@0: NSSize size = michael@0: spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)]; michael@0: buttonWidth = size.width; michael@0: buttonHeight = size.height; michael@0: if (aWidgetType != NS_THEME_SPINNER) { michael@0: // the buttons are half the height of the spinner michael@0: buttonHeight /= 2; michael@0: } michael@0: } michael@0: aResult->SizeTo(buttonWidth, buttonHeight); michael@0: *aIsOverridable = true; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_DROPDOWN: michael@0: case NS_THEME_DROPDOWN_BUTTON: michael@0: { michael@0: SInt32 popupHeight = 0; michael@0: ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight); michael@0: aResult->SizeTo(0, popupHeight); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_NUMBER_INPUT: michael@0: case NS_THEME_TEXTFIELD: michael@0: case NS_THEME_TEXTFIELD_MULTILINE: michael@0: case NS_THEME_SEARCHFIELD: michael@0: { michael@0: // at minimum, we should be tall enough for 9pt text. michael@0: // I'm using hardcoded values here because the appearance manager michael@0: // values for the frame size are incorrect. michael@0: aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_WINDOW_BUTTON_BOX: { michael@0: NSSize size = WindowButtonsSize(aFrame); michael@0: aResult->SizeTo(size.width, size.height); michael@0: *aIsOverridable = false; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_MOZ_MAC_FULLSCREEN_BUTTON: { michael@0: if ([NativeWindowForFrame(aFrame) respondsToSelector:@selector(toggleFullScreen:)]) { michael@0: // This value is hardcoded because it's needed before we can measure the michael@0: // position and size of the fullscreen button. michael@0: aResult->SizeTo(16, 17); michael@0: } michael@0: *aIsOverridable = false; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_PROGRESSBAR: michael@0: { michael@0: SInt32 barHeight = 0; michael@0: ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight); michael@0: aResult->SizeTo(0, barHeight); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_TREEVIEW_TWISTY: michael@0: case NS_THEME_TREEVIEW_TWISTY_OPEN: michael@0: { michael@0: SInt32 twistyHeight = 0, twistyWidth = 0; michael@0: ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth); michael@0: ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight); michael@0: aResult->SizeTo(twistyWidth, twistyHeight); michael@0: *aIsOverridable = false; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_TREEVIEW_HEADER: michael@0: case NS_THEME_TREEVIEW_HEADER_CELL: michael@0: { michael@0: SInt32 headerHeight = 0; michael@0: ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight); michael@0: aResult->SizeTo(0, headerHeight - 1); // We don't need the top border. michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_TAB: michael@0: { michael@0: aResult->SizeTo(0, tabHeights[miniControlSize]); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_RANGE: michael@0: { michael@0: // The Mac Appearance Manager API (the old API we're currently using) michael@0: // doesn't define constants to obtain a minimum size for sliders. We use michael@0: // the "thickness" of a slider that has default dimensions for both the michael@0: // minimum width and height to get something sane and so that paint michael@0: // invalidation works. michael@0: SInt32 size = 0; michael@0: if (IsRangeHorizontal(aFrame)) { michael@0: ::GetThemeMetric(kThemeMetricHSliderHeight, &size); michael@0: } else { michael@0: ::GetThemeMetric(kThemeMetricVSliderWidth, &size); michael@0: } michael@0: aResult->SizeTo(size, size); michael@0: *aIsOverridable = true; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_RANGE_THUMB: michael@0: { michael@0: SInt32 width = 0; michael@0: SInt32 height = 0; michael@0: ::GetThemeMetric(kThemeMetricSliderMinThumbWidth, &width); michael@0: ::GetThemeMetric(kThemeMetricSliderMinThumbHeight, &height); michael@0: aResult->SizeTo(width, height); michael@0: *aIsOverridable = false; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SCALE_HORIZONTAL: michael@0: { michael@0: SInt32 scaleHeight = 0; michael@0: ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight); michael@0: aResult->SizeTo(scaleHeight, scaleHeight); michael@0: *aIsOverridable = false; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SCALE_VERTICAL: michael@0: { michael@0: SInt32 scaleWidth = 0; michael@0: ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth); michael@0: aResult->SizeTo(scaleWidth, scaleWidth); michael@0: *aIsOverridable = false; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL: michael@0: case NS_THEME_SCROLLBAR_THUMB_VERTICAL: michael@0: { michael@0: // Find our parent scrollbar frame in order to find out whether we're in michael@0: // a small or a large scrollbar. michael@0: nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame); michael@0: if (!scrollbarFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: bool isSmall = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL); michael@0: bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL); michael@0: int32_t& minSize = isHorizontal ? aResult->width : aResult->height; michael@0: minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize; michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SCROLLBAR: michael@0: case NS_THEME_SCROLLBAR_SMALL: michael@0: case NS_THEME_SCROLLBAR_TRACK_VERTICAL: michael@0: case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL: michael@0: { michael@0: *aIsOverridable = false; michael@0: michael@0: if (nsLookAndFeel::UseOverlayScrollbars()) { michael@0: nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame); michael@0: if (scrollbarFrame && michael@0: scrollbarFrame->StyleDisplay()->mAppearance == michael@0: NS_THEME_SCROLLBAR_SMALL) { michael@0: aResult->SizeTo(14, 14); michael@0: } michael@0: else { michael@0: aResult->SizeTo(16, 16); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: // yeah, i know i'm cheating a little here, but i figure that it michael@0: // really doesn't matter if the scrollbar is vertical or horizontal michael@0: // and the width metric is a really good metric for every piece michael@0: // of the scrollbar. michael@0: michael@0: nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame); michael@0: if (!scrollbarFrame) return NS_ERROR_FAILURE; michael@0: michael@0: int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ? michael@0: kThemeMetricSmallScrollBarWidth : michael@0: kThemeMetricScrollBarWidth; michael@0: SInt32 scrollbarWidth = 0; michael@0: ::GetThemeMetric(themeMetric, &scrollbarWidth); michael@0: aResult->SizeTo(scrollbarWidth, scrollbarWidth); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SCROLLBAR_NON_DISAPPEARING: michael@0: { michael@0: int32_t themeMetric = kThemeMetricScrollBarWidth; michael@0: michael@0: if (aFrame) { michael@0: nsIFrame* scrollbarFrame = GetParentScrollbarFrame(aFrame); michael@0: if (scrollbarFrame && michael@0: scrollbarFrame->StyleDisplay()->mAppearance == michael@0: NS_THEME_SCROLLBAR_SMALL) { michael@0: // XXX We're interested in the width of non-disappearing scrollbars michael@0: // to leave enough space for a dropmarker in non-native styled michael@0: // comboboxes (bug 869314). It isn't clear to me if comboboxes can michael@0: // ever have small scrollbars. michael@0: themeMetric = kThemeMetricSmallScrollBarWidth; michael@0: } michael@0: } michael@0: michael@0: SInt32 scrollbarWidth = 0; michael@0: ::GetThemeMetric(themeMetric, &scrollbarWidth); michael@0: aResult->SizeTo(scrollbarWidth, scrollbarWidth); michael@0: break; michael@0: } michael@0: michael@0: case NS_THEME_SCROLLBAR_BUTTON_UP: michael@0: case NS_THEME_SCROLLBAR_BUTTON_DOWN: michael@0: case NS_THEME_SCROLLBAR_BUTTON_LEFT: michael@0: case NS_THEME_SCROLLBAR_BUTTON_RIGHT: michael@0: { michael@0: nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame); michael@0: if (!scrollbarFrame) return NS_ERROR_FAILURE; michael@0: michael@0: // Since there is no NS_THEME_SCROLLBAR_BUTTON_UP_SMALL we need to ask the parent what appearance style it has. michael@0: int32_t themeMetric = (scrollbarFrame->StyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ? michael@0: kThemeMetricSmallScrollBarWidth : michael@0: kThemeMetricScrollBarWidth; michael@0: SInt32 scrollbarWidth = 0; michael@0: ::GetThemeMetric(themeMetric, &scrollbarWidth); michael@0: michael@0: // It seems that for both sizes of scrollbar, the buttons are one pixel "longer". michael@0: if (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT) michael@0: aResult->SizeTo(scrollbarWidth+1, scrollbarWidth); michael@0: else michael@0: aResult->SizeTo(scrollbarWidth, scrollbarWidth+1); michael@0: michael@0: *aIsOverridable = false; michael@0: break; michael@0: } michael@0: case NS_THEME_RESIZER: michael@0: { michael@0: HIThemeGrowBoxDrawInfo drawInfo; michael@0: drawInfo.version = 0; michael@0: drawInfo.state = kThemeStateActive; michael@0: drawInfo.kind = kHIThemeGrowBoxKindNormal; michael@0: drawInfo.direction = kThemeGrowRight | kThemeGrowDown; michael@0: drawInfo.size = kHIThemeGrowBoxSizeNormal; michael@0: HIPoint pnt = { 0, 0 }; michael@0: HIRect bounds; michael@0: HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds); michael@0: aResult->SizeTo(bounds.size.width, bounds.size.height); michael@0: *aIsOverridable = false; michael@0: } michael@0: } michael@0: michael@0: if (IsHiDPIContext(aContext->DeviceContext())) { michael@0: *aResult = *aResult * 2; michael@0: } michael@0: michael@0: return NS_OK; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType, michael@0: nsIAtom* aAttribute, bool* aShouldRepaint) michael@0: { michael@0: // Some widget types just never change state. michael@0: switch (aWidgetType) { michael@0: case NS_THEME_WINDOW_TITLEBAR: michael@0: case NS_THEME_TOOLBOX: michael@0: case NS_THEME_TOOLBAR: michael@0: case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR: michael@0: case NS_THEME_STATUSBAR: michael@0: case NS_THEME_STATUSBAR_PANEL: michael@0: case NS_THEME_STATUSBAR_RESIZER_PANEL: michael@0: case NS_THEME_TOOLTIP: michael@0: case NS_THEME_TAB_PANELS: michael@0: case NS_THEME_TAB_PANEL: michael@0: case NS_THEME_DIALOG: michael@0: case NS_THEME_MENUPOPUP: michael@0: case NS_THEME_GROUPBOX: michael@0: case NS_THEME_PROGRESSBAR_CHUNK: michael@0: case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL: michael@0: case NS_THEME_PROGRESSBAR: michael@0: case NS_THEME_PROGRESSBAR_VERTICAL: michael@0: case NS_THEME_METERBAR: michael@0: case NS_THEME_METERBAR_CHUNK: michael@0: *aShouldRepaint = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // XXXdwh Not sure what can really be done here. Can at least guess for michael@0: // specific widgets that they're highly unlikely to have certain states. michael@0: // For example, a toolbar doesn't care about any states. michael@0: if (!aAttribute) { michael@0: // Hover/focus/active changed. Always repaint. michael@0: *aShouldRepaint = true; michael@0: } else { michael@0: // Check the attribute to see if it's relevant. michael@0: // disabled, checked, dlgtype, default, etc. michael@0: *aShouldRepaint = false; michael@0: if (aAttribute == nsGkAtoms::disabled || michael@0: aAttribute == nsGkAtoms::checked || michael@0: aAttribute == nsGkAtoms::selected || michael@0: aAttribute == nsGkAtoms::menuactive || michael@0: aAttribute == nsGkAtoms::sortDirection || michael@0: aAttribute == nsGkAtoms::focused || michael@0: aAttribute == nsGkAtoms::_default || michael@0: aAttribute == nsGkAtoms::open || michael@0: aAttribute == nsGkAtoms::hover) michael@0: *aShouldRepaint = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNativeThemeCocoa::ThemeChanged() michael@0: { michael@0: // This is unimplemented because we don't care if gecko changes its theme michael@0: // and Mac OS X doesn't have themes. michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame, michael@0: uint8_t aWidgetType) michael@0: { michael@0: // We don't have CSS set up to render non-native scrollbars on Mac OS X so we michael@0: // render natively even if native theme support is disabled. michael@0: if (aWidgetType != NS_THEME_SCROLLBAR && michael@0: aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled()) michael@0: return false; michael@0: michael@0: // if this is a dropdown button in a combobox the answer is always no michael@0: if (aWidgetType == NS_THEME_DROPDOWN_BUTTON) { michael@0: nsIFrame* parentFrame = aFrame->GetParent(); michael@0: if (parentFrame && (parentFrame->GetType() == nsGkAtoms::comboboxControlFrame)) michael@0: return false; michael@0: } michael@0: michael@0: switch (aWidgetType) { michael@0: case NS_THEME_LISTBOX: michael@0: michael@0: case NS_THEME_DIALOG: michael@0: case NS_THEME_WINDOW: michael@0: case NS_THEME_WINDOW_BUTTON_BOX: michael@0: case NS_THEME_WINDOW_TITLEBAR: michael@0: case NS_THEME_MENUPOPUP: michael@0: case NS_THEME_MENUITEM: michael@0: case NS_THEME_MENUSEPARATOR: michael@0: case NS_THEME_MOZ_MAC_FULLSCREEN_BUTTON: michael@0: case NS_THEME_TOOLTIP: michael@0: michael@0: case NS_THEME_CHECKBOX: michael@0: case NS_THEME_CHECKBOX_CONTAINER: michael@0: case NS_THEME_RADIO: michael@0: case NS_THEME_RADIO_CONTAINER: michael@0: case NS_THEME_GROUPBOX: michael@0: case NS_THEME_MOZ_MAC_HELP_BUTTON: michael@0: case NS_THEME_BUTTON: michael@0: case NS_THEME_BUTTON_BEVEL: michael@0: case NS_THEME_TOOLBAR_BUTTON: michael@0: case NS_THEME_SPINNER: michael@0: case NS_THEME_SPINNER_UP_BUTTON: michael@0: case NS_THEME_SPINNER_DOWN_BUTTON: michael@0: case NS_THEME_TOOLBAR: michael@0: case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR: michael@0: case NS_THEME_STATUSBAR: michael@0: case NS_THEME_NUMBER_INPUT: michael@0: case NS_THEME_TEXTFIELD: michael@0: case NS_THEME_TEXTFIELD_MULTILINE: michael@0: case NS_THEME_SEARCHFIELD: michael@0: //case NS_THEME_TOOLBOX: michael@0: //case NS_THEME_TOOLBAR_BUTTON: michael@0: case NS_THEME_PROGRESSBAR: michael@0: case NS_THEME_PROGRESSBAR_VERTICAL: michael@0: case NS_THEME_PROGRESSBAR_CHUNK: michael@0: case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL: michael@0: case NS_THEME_METERBAR: michael@0: case NS_THEME_METERBAR_CHUNK: michael@0: case NS_THEME_TOOLBAR_SEPARATOR: michael@0: michael@0: case NS_THEME_TAB_PANELS: michael@0: case NS_THEME_TAB: michael@0: michael@0: case NS_THEME_TREEVIEW_TWISTY: michael@0: case NS_THEME_TREEVIEW_TWISTY_OPEN: michael@0: case NS_THEME_TREEVIEW: michael@0: case NS_THEME_TREEVIEW_HEADER: michael@0: case NS_THEME_TREEVIEW_HEADER_CELL: michael@0: case NS_THEME_TREEVIEW_HEADER_SORTARROW: michael@0: case NS_THEME_TREEVIEW_TREEITEM: michael@0: case NS_THEME_TREEVIEW_LINE: michael@0: michael@0: case NS_THEME_RANGE: michael@0: michael@0: case NS_THEME_SCALE_HORIZONTAL: michael@0: case NS_THEME_SCALE_THUMB_HORIZONTAL: michael@0: case NS_THEME_SCALE_VERTICAL: michael@0: case NS_THEME_SCALE_THUMB_VERTICAL: michael@0: michael@0: case NS_THEME_SCROLLBAR: michael@0: case NS_THEME_SCROLLBAR_SMALL: michael@0: case NS_THEME_SCROLLBAR_BUTTON_UP: michael@0: case NS_THEME_SCROLLBAR_BUTTON_DOWN: michael@0: case NS_THEME_SCROLLBAR_BUTTON_LEFT: michael@0: case NS_THEME_SCROLLBAR_BUTTON_RIGHT: michael@0: case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL: michael@0: case NS_THEME_SCROLLBAR_THUMB_VERTICAL: michael@0: case NS_THEME_SCROLLBAR_TRACK_VERTICAL: michael@0: case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL: michael@0: case NS_THEME_SCROLLBAR_NON_DISAPPEARING: michael@0: michael@0: case NS_THEME_DROPDOWN: michael@0: case NS_THEME_DROPDOWN_BUTTON: michael@0: case NS_THEME_DROPDOWN_TEXT: michael@0: case NS_THEME_DROPDOWN_TEXTFIELD: michael@0: return !IsWidgetStyled(aPresContext, aFrame, aWidgetType); michael@0: break; michael@0: michael@0: case NS_THEME_RESIZER: michael@0: { michael@0: nsIFrame* parentFrame = aFrame->GetParent(); michael@0: if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame) michael@0: return true; michael@0: michael@0: // Note that IsWidgetStyled is not called for resizers on Mac. This is michael@0: // because for scrollable containers, the native resizer looks better michael@0: // when (non-overlay) scrollbars are present even when the style is michael@0: // overriden, and the custom transparent resizer looks better when michael@0: // scrollbars are not present. michael@0: nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame); michael@0: return (!nsLookAndFeel::UseOverlayScrollbars() && michael@0: scrollFrame && scrollFrame->GetScrollbarVisibility()); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsNativeThemeCocoa::WidgetIsContainer(uint8_t aWidgetType) michael@0: { michael@0: // flesh this out at some point michael@0: switch (aWidgetType) { michael@0: case NS_THEME_DROPDOWN_BUTTON: michael@0: case NS_THEME_RADIO: michael@0: case NS_THEME_CHECKBOX: michael@0: case NS_THEME_PROGRESSBAR: michael@0: case NS_THEME_METERBAR: michael@0: case NS_THEME_RANGE: michael@0: case NS_THEME_MOZ_MAC_HELP_BUTTON: michael@0: return false; michael@0: break; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsNativeThemeCocoa::ThemeDrawsFocusForWidget(uint8_t aWidgetType) michael@0: { michael@0: if (aWidgetType == NS_THEME_DROPDOWN || michael@0: aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD || michael@0: aWidgetType == NS_THEME_BUTTON || michael@0: aWidgetType == NS_THEME_MOZ_MAC_HELP_BUTTON || michael@0: aWidgetType == NS_THEME_RADIO || michael@0: aWidgetType == NS_THEME_RANGE || michael@0: aWidgetType == NS_THEME_CHECKBOX) michael@0: return true; michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker() michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) michael@0: { michael@0: switch (aWidgetType) { michael@0: case NS_THEME_DIALOG: michael@0: case NS_THEME_GROUPBOX: michael@0: case NS_THEME_TAB_PANELS: michael@0: case NS_THEME_MENUPOPUP: michael@0: case NS_THEME_MENUITEM: michael@0: case NS_THEME_MENUSEPARATOR: michael@0: case NS_THEME_TOOLTIP: michael@0: case NS_THEME_SPINNER: michael@0: case NS_THEME_SPINNER_UP_BUTTON: michael@0: case NS_THEME_SPINNER_DOWN_BUTTON: michael@0: case NS_THEME_TOOLBAR_SEPARATOR: michael@0: case NS_THEME_TOOLBOX: michael@0: case NS_THEME_NUMBER_INPUT: michael@0: case NS_THEME_TEXTFIELD: michael@0: case NS_THEME_TREEVIEW: michael@0: case NS_THEME_TREEVIEW_LINE: michael@0: case NS_THEME_TEXTFIELD_MULTILINE: michael@0: case NS_THEME_LISTBOX: michael@0: case NS_THEME_RESIZER: michael@0: return false; michael@0: default: michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: nsITheme::Transparency michael@0: nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType) michael@0: { michael@0: switch (aWidgetType) { michael@0: case NS_THEME_MENUPOPUP: michael@0: case NS_THEME_TOOLTIP: michael@0: return eTransparent; michael@0: michael@0: case NS_THEME_SCROLLBAR_SMALL: michael@0: case NS_THEME_SCROLLBAR: michael@0: return nsLookAndFeel::UseOverlayScrollbars() ? eTransparent : eOpaque; michael@0: michael@0: case NS_THEME_STATUSBAR: michael@0: // Knowing that scrollbars and statusbars are opaque improves michael@0: // performance, because we create layers for them. michael@0: return eOpaque; michael@0: michael@0: case NS_THEME_TOOLBAR: michael@0: case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR: michael@0: return eOpaque; michael@0: michael@0: default: michael@0: return eUnknownTransparency; michael@0: } michael@0: }