1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsNativeThemeCocoa.h"
7 #include "nsObjCExceptions.h"
8 #include "nsRenderingContext.h"
11 #include "nsThemeConstants.h"
12 #include "nsIPresShell.h"
13 #include "nsPresContext.h"
14 #include "nsIContent.h"
15 #include "nsIDocument.h"
18 #include "nsEventStates.h"
19 #include "nsINameSpaceManager.h"
20 #include "nsPresContext.h"
21 #include "nsGkAtoms.h"
22 #include "nsCocoaFeatures.h"
23 #include "nsCocoaWindow.h"
24 #include "nsNativeThemeColors.h"
25 #include "nsIScrollableFrame.h"
26 #include "nsIDOMHTMLProgressElement.h"
27 #include "nsIDOMHTMLMeterElement.h"
28 #include "mozilla/dom/Element.h"
30 #include "gfxContext.h"
31 #include "gfxQuartzSurface.h"
32 #include "gfxQuartzNativeDrawing.h"
34 #define DRAW_IN_FRAME_DEBUG 0
35 #define SCROLLBARS_VISUAL_DEBUG 0
37 // private Quartz routines needed here
39 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
42 // Workaround for NSCell control tint drawing
43 // Without this workaround, NSCells are always drawn with the clear control tint
44 // as long as they're not attached to an NSControl which is a subview of an active window.
45 // XXXmstange Why doesn't Webkit need this?
46 @implementation NSCell (ControlTintWorkaround)
47 - (int)_realControlTint { return [self controlTint]; }
50 // The purpose of this class is to provide objects that can be used when drawing
51 // NSCells using drawWithFrame:inView: without causing any harm. The only
52 // messages that will be sent to such an object are "isFlipped" and
53 // "currentEditor": isFlipped needs to return YES in order to avoid drawing bugs
54 // on 10.4 (see bug 465069); currentEditor (which isn't even a method of
55 // NSView) will be called when drawing search fields, and we only provide it in
56 // order to prevent "unrecognized selector" exceptions.
57 // There's no need to pass the actual NSView that we're drawing into to
58 // drawWithFrame:inView:. What's more, doing so even causes unnecessary
59 // invalidations as soon as we draw a focusring!
60 @interface CellDrawView : NSView
64 @implementation CellDrawView
71 - (NSText*)currentEditor
79 * NSProgressBarCell is used to draw progress bars of any size.
81 @interface NSProgressBarCell : NSCell
83 /*All instance variables are private*/
86 bool mIsIndeterminate;
90 - (void)setValue:(double)value;
92 - (void)setMax:(double)max;
94 - (void)setIndeterminate:(bool)aIndeterminate;
95 - (bool)isIndeterminate;
96 - (void)setHorizontal:(bool)aIsHorizontal;
98 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
101 @implementation NSProgressBarCell
103 - (void)setMax:(double)aMax
113 - (void)setValue:(double)aValue
123 - (void)setIndeterminate:(bool)aIndeterminate
125 mIsIndeterminate = aIndeterminate;
128 - (bool)isIndeterminate
130 return mIsIndeterminate;
133 - (void)setHorizontal:(bool)aIsHorizontal
135 mIsHorizontal = aIsHorizontal;
140 return mIsHorizontal;
143 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
145 CGContext* cgContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
147 HIThemeTrackDrawInfo tdi;
152 tdi.value = INT32_MAX * (mValue / mMax);
154 tdi.bounds = NSRectToCGRect(cellFrame);
155 tdi.attributes = mIsHorizontal ? kThemeTrackHorizontal : 0;
156 tdi.enableState = [self controlTint] == NSClearControlTint ? kThemeTrackInactive
159 NSControlSize size = [self controlSize];
160 if (size == NSRegularControlSize) {
161 tdi.kind = mIsIndeterminate ? kThemeLargeIndeterminateBar
162 : kThemeLargeProgressBar;
164 NS_ASSERTION(size == NSSmallControlSize,
165 "We shouldn't have another size than small and regular for the moment");
166 tdi.kind = mIsIndeterminate ? kThemeMediumIndeterminateBar
167 : kThemeMediumProgressBar;
170 int32_t stepsPerSecond = mIsIndeterminate ? 60 : 30;
171 int32_t milliSecondsPerStep = 1000 / stepsPerSecond;
172 tdi.trackInfo.progress.phase = PR_IntervalToMilliseconds(PR_IntervalNow()) /
173 milliSecondsPerStep % 32;
175 HIThemeDrawTrack(&tdi, NULL, cgContext, kHIThemeOrientationNormal);
180 @interface ContextAwareSearchFieldCell : NSSearchFieldCell
185 // setContext: stores the searchfield nsIFrame so that it can be consulted
186 // during painting. Please reset this by calling setContext:nullptr as soon as
187 // you're done with painting because we don't want to keep a dangling pointer.
188 - (void)setContext:(nsIFrame*)aContext;
191 @implementation ContextAwareSearchFieldCell
193 - (id)initTextCell:(NSString*)aString
195 if ((self = [super initTextCell:aString])) {
201 - (void)setContext:(nsIFrame*)aContext
206 static BOOL IsToolbarStyleContainer(nsIFrame* aFrame)
208 nsIContent* content = aFrame->GetContent();
212 if (content->Tag() == nsGkAtoms::toolbar ||
213 content->Tag() == nsGkAtoms::toolbox ||
214 content->Tag() == nsGkAtoms::statusbar)
217 switch (aFrame->GetStyleDisplay()->mAppearance) {
218 case NS_THEME_TOOLBAR:
219 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
220 case NS_THEME_STATUSBAR:
227 - (BOOL)_isToolbarMode
229 // On 10.7, searchfields have two different styles, depending on whether
230 // the searchfield is on top of of window chrome. This function is called on
231 // 10.7 during drawing in order to determine which style to use.
232 for (nsIFrame* frame = mContext; frame; frame = frame->GetParent()) {
233 if (IsToolbarStyleContainer(frame)) {
242 // Workaround for Bug 542048
243 // On 64-bit, NSSearchFieldCells don't draw focus rings.
244 #if defined(__x86_64__)
246 static void DrawFocusRing(NSRect rect, float radius)
248 NSSetFocusRingStyle(NSFocusRingOnly);
249 NSBezierPath* path = [NSBezierPath bezierPath];
250 rect = NSInsetRect(rect, radius, radius);
251 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMinY(rect)) radius:radius startAngle:180.0 endAngle:270.0];
252 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMinY(rect)) radius:radius startAngle:270.0 endAngle:360.0];
253 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMaxY(rect)) radius:radius startAngle: 0.0 endAngle: 90.0];
254 [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMaxY(rect)) radius:radius startAngle: 90.0 endAngle:180.0];
259 @interface SearchFieldCellWithFocusRing : ContextAwareSearchFieldCell {} @end
261 @implementation SearchFieldCellWithFocusRing
263 - (void)drawWithFrame:(NSRect)rect inView:(NSView*)controlView
265 [super drawWithFrame:rect inView:controlView];
266 if ([self showsFirstResponder]) {
267 DrawFocusRing(rect, NSHeight(rect) / 2);
275 // Copied from nsLookAndFeel.h
276 // Apple hasn't defined a constant for scollbars with two arrows on each end, so we'll use this one.
277 static const int kThemeScrollBarArrowsBoth = 2;
279 #define HITHEME_ORIENTATION kHIThemeOrientationNormal
280 #define MAX_FOCUS_RING_WIDTH 4
282 // These enums are for indexing into the margin array.
300 static int EnumSizeForCocoaSize(NSControlSize cocoaControlSize) {
301 if (cocoaControlSize == NSMiniControlSize)
302 return miniControlSize;
303 else if (cocoaControlSize == NSSmallControlSize)
304 return smallControlSize;
306 return regularControlSize;
309 static NSControlSize CocoaSizeForEnum(int32_t enumControlSize) {
310 if (enumControlSize == miniControlSize)
311 return NSMiniControlSize;
312 else if (enumControlSize == smallControlSize)
313 return NSSmallControlSize;
315 return NSRegularControlSize;
318 static NSString* CUIControlSizeForCocoaSize(NSControlSize aControlSize)
320 if (aControlSize == NSRegularControlSize)
322 else if (aControlSize == NSSmallControlSize)
328 static void InflateControlRect(NSRect* rect, NSControlSize cocoaControlSize, const float marginSet[][3][4])
333 static int osIndex = leopardOS;
334 int controlSize = EnumSizeForCocoaSize(cocoaControlSize);
335 const float* buttonMargins = marginSet[osIndex][controlSize];
336 rect->origin.x -= buttonMargins[leftMargin];
337 rect->origin.y -= buttonMargins[bottomMargin];
338 rect->size.width += buttonMargins[leftMargin] + buttonMargins[rightMargin];
339 rect->size.height += buttonMargins[bottomMargin] + buttonMargins[topMargin];
342 static NSWindow* NativeWindowForFrame(nsIFrame* aFrame,
343 nsIWidget** aTopLevelWidget = NULL)
348 nsIWidget* widget = aFrame->GetNearestWidget();
352 nsIWidget* topLevelWidget = widget->GetTopLevelWidget();
354 *aTopLevelWidget = topLevelWidget;
356 return (NSWindow*)topLevelWidget->GetNativeData(NS_NATIVE_WINDOW);
359 static BOOL FrameIsInActiveWindow(nsIFrame* aFrame)
361 nsIWidget* topLevelWidget = NULL;
362 NSWindow* win = NativeWindowForFrame(aFrame, &topLevelWidget);
363 if (!topLevelWidget || !win)
366 // XUL popups, e.g. the toolbar customization popup, can't become key windows,
367 // but controls in these windows should still get the active look.
368 nsWindowType windowType;
369 topLevelWidget->GetWindowType(windowType);
370 if (windowType == eWindowType_popup)
373 return [win isKeyWindow];
374 return [win isMainWindow] && ![win attachedSheet];
377 // Toolbar controls and content controls respond to different window
378 // activeness states.
379 static BOOL IsActive(nsIFrame* aFrame, BOOL aIsToolbarControl)
381 if (aIsToolbarControl)
382 return [NativeWindowForFrame(aFrame) isMainWindow];
383 return FrameIsInActiveWindow(aFrame);
386 NS_IMPL_ISUPPORTS_INHERITED1(nsNativeThemeCocoa, nsNativeTheme, nsITheme)
389 nsNativeThemeCocoa::nsNativeThemeCocoa()
391 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
393 // provide a local autorelease pool, as this is called during startup
394 // before the main event-loop pool is in place
395 nsAutoreleasePool pool;
397 mPushButtonCell = [[NSButtonCell alloc] initTextCell:nil];
398 [mPushButtonCell setButtonType:NSMomentaryPushInButton];
399 [mPushButtonCell setHighlightsBy:NSPushInCellMask];
401 mRadioButtonCell = [[NSButtonCell alloc] initTextCell:nil];
402 [mRadioButtonCell setButtonType:NSRadioButton];
404 mCheckboxCell = [[NSButtonCell alloc] initTextCell:nil];
405 [mCheckboxCell setButtonType:NSSwitchButton];
406 [mCheckboxCell setAllowsMixedState:YES];
408 #if defined(__x86_64__)
409 mSearchFieldCell = [[SearchFieldCellWithFocusRing alloc] initTextCell:@""];
411 mSearchFieldCell = [[ContextAwareSearchFieldCell alloc] initTextCell:@""];
413 [mSearchFieldCell setBezelStyle:NSTextFieldRoundedBezel];
414 [mSearchFieldCell setBezeled:YES];
415 [mSearchFieldCell setEditable:YES];
416 [mSearchFieldCell setFocusRingType:NSFocusRingTypeExterior];
418 mDropdownCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
420 mComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
421 [mComboBoxCell setBezeled:YES];
422 [mComboBoxCell setEditable:YES];
423 [mComboBoxCell setFocusRingType:NSFocusRingTypeExterior];
425 mProgressBarCell = [[NSProgressBarCell alloc] init];
427 mMeterBarCell = [[NSLevelIndicatorCell alloc]
428 initWithLevelIndicatorStyle:NSContinuousCapacityLevelIndicatorStyle];
430 mCellDrawView = [[CellDrawView alloc] init];
432 NS_OBJC_END_TRY_ABORT_BLOCK;
435 nsNativeThemeCocoa::~nsNativeThemeCocoa()
437 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
439 [mMeterBarCell release];
440 [mProgressBarCell release];
441 [mPushButtonCell release];
442 [mRadioButtonCell release];
443 [mCheckboxCell release];
444 [mSearchFieldCell release];
445 [mDropdownCell release];
446 [mComboBoxCell release];
447 [mCellDrawView release];
449 NS_OBJC_END_TRY_ABORT_BLOCK;
452 // Limit on the area of the target rect (in pixels^2) in
453 // DrawCellWithScaling(), DrawButton() and DrawScrollbar(), above which we
454 // don't draw the object into a bitmap buffer. This is to avoid crashes in
455 // [NSGraphicsContext graphicsContextWithGraphicsPort:flipped:] and
456 // CGContextDrawImage(), and also to avoid very poor drawing performance in
457 // CGContextDrawImage() when it scales the bitmap (particularly if xscale or
458 // yscale is less than but near 1 -- e.g. 0.9). This value was determined
459 // by trial and error, on OS X 10.4.11 and 10.5.4, and on systems with
460 // different amounts of RAM.
461 #define BITMAP_MAX_AREA 500000
464 GetBackingScaleFactorForRendering(CGContextRef cgContext)
466 CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
467 CGRect transformedUserSpacePixel = CGRectApplyAffineTransform(CGRectMake(0, 0, 1, 1), ctm);
468 float maxScale = NS_MAX(fabs(transformedUserSpacePixel.size.width),
469 fabs(transformedUserSpacePixel.size.height));
470 return maxScale > 1.0 ? 2 : 1;
474 * Draw the given NSCell into the given cgContext.
476 * destRect - the size and position of the resulting control rectangle
477 * controlSize - the NSControlSize which will be given to the NSCell before
478 * asking it to render
479 * naturalSize - The natural dimensions of this control.
480 * If the control rect size is not equal to either of these, a scale
481 * will be applied to the context so that rendering the control at the
482 * natural size will result in it filling the destRect space.
483 * If a control has no natural dimensions in either/both axes, pass 0.0f.
484 * minimumSize - The minimum dimensions of this control.
485 * If the control rect size is less than the minimum for a given axis,
486 * a scale will be applied to the context so that the minimum is used
487 * for drawing. If a control has no minimum dimensions in either/both
489 * marginSet - an array of margins; a multidimensional array of [2][3][4],
490 * with the first dimension being the OS version (Tiger or Leopard),
491 * the second being the control size (mini, small, regular), and the third
492 * being the 4 margin values (left, top, right, bottom).
493 * view - The NSView that we're drawing into. As far as I can tell, it doesn't
494 * matter if this is really the right view; it just has to return YES when
495 * asked for isFlipped. Otherwise we'll get drawing bugs on 10.4.
496 * mirrorHorizontal - whether to mirror the cell horizontally
498 static void DrawCellWithScaling(NSCell *cell,
499 CGContextRef cgContext,
500 const HIRect& destRect,
501 NSControlSize controlSize,
504 const float marginSet[][3][4],
506 BOOL mirrorHorizontal)
508 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
510 NSRect drawRect = NSMakeRect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height);
512 if (naturalSize.width != 0.0f)
513 drawRect.size.width = naturalSize.width;
514 if (naturalSize.height != 0.0f)
515 drawRect.size.height = naturalSize.height;
517 // Keep aspect ratio when scaling if one dimension is free.
518 if (naturalSize.width == 0.0f && naturalSize.height != 0.0f)
519 drawRect.size.width = destRect.size.width * naturalSize.height / destRect.size.height;
520 if (naturalSize.height == 0.0f && naturalSize.width != 0.0f)
521 drawRect.size.height = destRect.size.height * naturalSize.width / destRect.size.width;
523 // Honor minimum sizes.
524 if (drawRect.size.width < minimumSize.width)
525 drawRect.size.width = minimumSize.width;
526 if (drawRect.size.height < minimumSize.height)
527 drawRect.size.height = minimumSize.height;
529 [NSGraphicsContext saveGraphicsState];
531 // Only skip the buffer if the area of our cell (in pixels^2) is too large.
532 if (drawRect.size.width * drawRect.size.height > BITMAP_MAX_AREA) {
533 // Inflate the rect Gecko gave us by the margin for the control.
534 InflateControlRect(&drawRect, controlSize, marginSet);
536 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
537 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:cgContext flipped:YES]];
539 [cell drawWithFrame:drawRect inView:view];
541 [NSGraphicsContext setCurrentContext:savedContext];
544 float w = ceil(drawRect.size.width);
545 float h = ceil(drawRect.size.height);
546 NSRect tmpRect = NSMakeRect(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, w, h);
548 // inflate to figure out the frame we need to tell NSCell to draw in, to get something that's 0,0,w,h
549 InflateControlRect(&tmpRect, controlSize, marginSet);
551 // and then, expand by MAX_FOCUS_RING_WIDTH size to make sure we can capture any focus ring
552 w += MAX_FOCUS_RING_WIDTH * 2.0;
553 h += MAX_FOCUS_RING_WIDTH * 2.0;
555 int backingScaleFactor = GetBackingScaleFactorForRendering(cgContext);
556 CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
557 CGContextRef ctx = CGBitmapContextCreate(NULL,
558 (int) w * backingScaleFactor, (int) h * backingScaleFactor,
559 8, (int) w * backingScaleFactor * 4,
560 rgb, kCGImageAlphaPremultipliedFirst);
561 CGColorSpaceRelease(rgb);
563 // We need to flip the image twice in order to avoid drawing bugs on 10.4, see bug 465069.
564 // This is the first flip transform, applied to cgContext.
565 CGContextScaleCTM(cgContext, 1.0f, -1.0f);
566 CGContextTranslateCTM(cgContext, 0.0f, -(2.0 * destRect.origin.y + destRect.size.height));
567 if (mirrorHorizontal) {
568 CGContextScaleCTM(cgContext, -1.0f, 1.0f);
569 CGContextTranslateCTM(cgContext, -(2.0 * destRect.origin.x + destRect.size.width), 0.0f);
572 NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
573 [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES]];
575 CGContextScaleCTM(ctx, backingScaleFactor, backingScaleFactor);
577 // This is the second flip transform, applied to ctx.
578 CGContextScaleCTM(ctx, 1.0f, -1.0f);
579 CGContextTranslateCTM(ctx, 0.0f, -(2.0 * tmpRect.origin.y + tmpRect.size.height));
581 [cell drawWithFrame:tmpRect inView:view];
583 [NSGraphicsContext setCurrentContext:savedContext];
585 CGImageRef img = CGBitmapContextCreateImage(ctx);
587 // Drop the image into the original destination rectangle, scaling to fit
588 // Only scale MAX_FOCUS_RING_WIDTH by xscale/yscale when the resulting rect
589 // doesn't extend beyond the overflow rect
590 float xscale = destRect.size.width / drawRect.size.width;
591 float yscale = destRect.size.height / drawRect.size.height;
592 float scaledFocusRingX = xscale < 1.0f ? MAX_FOCUS_RING_WIDTH * xscale : MAX_FOCUS_RING_WIDTH;
593 float scaledFocusRingY = yscale < 1.0f ? MAX_FOCUS_RING_WIDTH * yscale : MAX_FOCUS_RING_WIDTH;
594 CGContextDrawImage(cgContext, CGRectMake(destRect.origin.x - scaledFocusRingX,
595 destRect.origin.y - scaledFocusRingY,
596 destRect.size.width + scaledFocusRingX * 2,
597 destRect.size.height + scaledFocusRingY * 2),
601 CGContextRelease(ctx);
604 [NSGraphicsContext restoreGraphicsState];
606 #if DRAW_IN_FRAME_DEBUG
607 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
608 CGContextFillRect(cgContext, destRect);
611 NS_OBJC_END_TRY_ABORT_BLOCK;
614 struct CellRenderSettings {
615 // The natural dimensions of the control.
616 // If a control has no natural dimensions in either/both axes, set to 0.0f.
617 NSSize naturalSizes[3];
619 // The minimum dimensions of the control.
620 // If a control has no minimum dimensions in either/both axes, set to 0.0f.
621 NSSize minimumSizes[3];
623 // A three-dimensional array,
624 // with the first dimension being the OS version (only Leopard for the moment),
625 // the second being the control size (mini, small, regular), and the third
626 // being the 4 margin values (left, top, right, bottom).
627 float margins[1][3][4];
631 * This is a helper method that returns the required NSControlSize given a size
632 * and the size of the three controls plus a tolerance.
633 * size - The width or the height of the element to draw.
634 * sizes - An array with the all the width/height of the element for its
636 * tolerance - The tolerance as passed to DrawCellWithSnapping.
637 * NOTE: returns NSRegularControlSize if all values in 'sizes' are zero.
639 static NSControlSize FindControlSize(CGFloat size, const CGFloat* sizes, CGFloat tolerance)
641 for (uint32_t i = miniControlSize; i <= regularControlSize; ++i) {
648 for (uint32_t j = i+1; j <= regularControlSize; ++j) {
655 // If it's the latest value, we pick it.
657 return CocoaSizeForEnum(i);
660 if (size <= sizes[i] + tolerance && size < next) {
661 return CocoaSizeForEnum(i);
665 // If we are here, that means sizes[] was an array with only empty values
666 // or the algorithm above is wrong.
667 // The former can happen but the later would be wrong.
668 NS_ASSERTION(sizes[0] == 0 && sizes[1] == 0 && sizes[2] == 0,
669 "We found no control! We shouldn't be there!");
670 return CocoaSizeForEnum(regularControlSize);
674 * Draw the given NSCell into the given cgContext with a nice control size.
676 * This function is similar to DrawCellWithScaling, but it decides what
677 * control size to use based on the destRect's size.
678 * Scaling is only applied when the difference between the destRect's size
679 * and the next smaller natural size is greater than snapTolerance. Otherwise
680 * it snaps to the next smaller control size without scaling because unscaled
681 * controls look nicer.
683 static void DrawCellWithSnapping(NSCell *cell,
684 CGContextRef cgContext,
685 const HIRect& destRect,
686 const CellRenderSettings settings,
687 float verticalAlignFactor,
689 BOOL mirrorHorizontal,
690 float snapTolerance = 2.0f)
692 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
694 const float rectWidth = destRect.size.width, rectHeight = destRect.size.height;
695 const NSSize *sizes = settings.naturalSizes;
696 const NSSize miniSize = sizes[EnumSizeForCocoaSize(NSMiniControlSize)];
697 const NSSize smallSize = sizes[EnumSizeForCocoaSize(NSSmallControlSize)];
698 const NSSize regularSize = sizes[EnumSizeForCocoaSize(NSRegularControlSize)];
700 HIRect drawRect = destRect;
702 CGFloat controlWidths[3] = { miniSize.width, smallSize.width, regularSize.width };
703 NSControlSize controlSizeX = FindControlSize(rectWidth, controlWidths, snapTolerance);
704 CGFloat controlHeights[3] = { miniSize.height, smallSize.height, regularSize.height };
705 NSControlSize controlSizeY = FindControlSize(rectHeight, controlHeights, snapTolerance);
707 NSControlSize controlSize = NSRegularControlSize;
710 // At some sizes, don't scale but snap.
711 const NSControlSize smallerControlSize =
712 EnumSizeForCocoaSize(controlSizeX) < EnumSizeForCocoaSize(controlSizeY) ?
713 controlSizeX : controlSizeY;
714 const int smallerControlSizeIndex = EnumSizeForCocoaSize(smallerControlSize);
715 const NSSize size = sizes[smallerControlSizeIndex];
716 float diffWidth = size.width ? rectWidth - size.width : 0.0f;
717 float diffHeight = size.height ? rectHeight - size.height : 0.0f;
718 if (diffWidth >= 0.0f && diffHeight >= 0.0f &&
719 diffWidth <= snapTolerance && diffHeight <= snapTolerance) {
720 // Snap to the smaller control size.
721 controlSize = smallerControlSize;
722 sizeIndex = smallerControlSizeIndex;
723 // Resize and center the drawRect.
724 if (sizes[sizeIndex].width) {
725 drawRect.origin.x += ceil((destRect.size.width - sizes[sizeIndex].width) / 2);
726 drawRect.size.width = sizes[sizeIndex].width;
728 if (sizes[sizeIndex].height) {
729 drawRect.origin.y += floor((destRect.size.height - sizes[sizeIndex].height) * verticalAlignFactor);
730 drawRect.size.height = sizes[sizeIndex].height;
733 // Use the larger control size.
734 controlSize = EnumSizeForCocoaSize(controlSizeX) > EnumSizeForCocoaSize(controlSizeY) ?
735 controlSizeX : controlSizeY;
736 sizeIndex = EnumSizeForCocoaSize(controlSize);
739 [cell setControlSize:controlSize];
741 NSSize minimumSize = settings.minimumSizes ? settings.minimumSizes[sizeIndex] : NSZeroSize;
742 DrawCellWithScaling(cell, cgContext, drawRect, controlSize, sizes[sizeIndex],
743 minimumSize, settings.margins, view, mirrorHorizontal);
745 NS_OBJC_END_TRY_ABORT_BLOCK;
748 static float VerticalAlignFactor(nsIFrame *aFrame)
751 return 0.5f; // default: center
753 const nsStyleCoord& va = aFrame->GetStyleTextReset()->mVerticalAlign;
754 uint8_t intval = (va.GetUnit() == eStyleUnit_Enumerated)
756 : NS_STYLE_VERTICAL_ALIGN_MIDDLE;
758 case NS_STYLE_VERTICAL_ALIGN_TOP:
759 case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
762 case NS_STYLE_VERTICAL_ALIGN_SUB:
763 case NS_STYLE_VERTICAL_ALIGN_SUPER:
764 case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
765 case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
768 case NS_STYLE_VERTICAL_ALIGN_BASELINE:
769 case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
770 case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
774 NS_NOTREACHED("invalid vertical-align");
779 // These are the sizes that Gecko needs to request to draw if it wants
780 // to get a standard-sized Aqua radio button drawn. Note that the rects
781 // that draw these are actually a little bigger.
782 static const CellRenderSettings radioSettings = {
784 NSMakeSize(11, 11), // mini
785 NSMakeSize(13, 13), // small
786 NSMakeSize(16, 16) // regular
789 NSZeroSize, NSZeroSize, NSZeroSize
793 {0, 0, 0, 0}, // mini
794 {0, 1, 1, 1}, // small
795 {0, 0, 0, 0} // regular
800 static const CellRenderSettings checkboxSettings = {
802 NSMakeSize(11, 11), // mini
803 NSMakeSize(13, 13), // small
804 NSMakeSize(16, 16) // regular
807 NSZeroSize, NSZeroSize, NSZeroSize
811 {0, 1, 0, 0}, // mini
812 {0, 1, 0, 1}, // small
813 {0, 1, 0, 1} // regular
819 nsNativeThemeCocoa::DrawCheckboxOrRadio(CGContextRef cgContext, bool inCheckbox,
820 const HIRect& inBoxRect, bool inSelected,
821 nsEventStates inState, nsIFrame* aFrame)
823 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
825 NSButtonCell *cell = inCheckbox ? mCheckboxCell : mRadioButtonCell;
826 NSCellStateValue state = inSelected ? NSOnState : NSOffState;
828 // Check if we have an indeterminate checkbox
829 if (inCheckbox && GetIndeterminate(aFrame))
830 state = NSMixedState;
832 [cell setEnabled:!IsDisabled(aFrame, inState)];
833 [cell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS)];
834 [cell setState:state];
835 [cell setHighlighted:inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
836 [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
838 // Ensure that the control is square.
839 float length = NS_MIN(inBoxRect.size.width, inBoxRect.size.height);
840 HIRect drawRect = CGRectMake(inBoxRect.origin.x + (int)((inBoxRect.size.width - length) / 2.0f),
841 inBoxRect.origin.y + (int)((inBoxRect.size.height - length) / 2.0f),
844 DrawCellWithSnapping(cell, cgContext, drawRect,
845 inCheckbox ? checkboxSettings : radioSettings,
846 VerticalAlignFactor(aFrame), mCellDrawView, NO);
848 NS_OBJC_END_TRY_ABORT_BLOCK;
851 static const CellRenderSettings searchFieldSettings = {
853 NSMakeSize(0, 16), // mini
854 NSMakeSize(0, 19), // small
855 NSMakeSize(0, 22) // regular
858 NSMakeSize(32, 0), // mini
859 NSMakeSize(38, 0), // small
860 NSMakeSize(44, 0) // regular
864 {0, 0, 0, 0}, // mini
865 {0, 0, 0, 0}, // small
866 {0, 0, 0, 0} // regular
872 nsNativeThemeCocoa::DrawSearchField(CGContextRef cgContext, const HIRect& inBoxRect,
873 nsIFrame* aFrame, nsEventStates inState)
875 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
877 ContextAwareSearchFieldCell* cell = mSearchFieldCell;
878 [cell setContext:aFrame];
879 [cell setEnabled:!IsDisabled(aFrame, inState)];
880 // NOTE: this could probably use inState
881 [cell setShowsFirstResponder:IsFocused(aFrame)];
883 DrawCellWithSnapping(cell, cgContext, inBoxRect, searchFieldSettings,
884 VerticalAlignFactor(aFrame), mCellDrawView,
887 [cell setContext:nullptr];
889 NS_OBJC_END_TRY_ABORT_BLOCK;
892 static const CellRenderSettings pushButtonSettings = {
894 NSMakeSize(0, 16), // mini
895 NSMakeSize(0, 19), // small
896 NSMakeSize(0, 22) // regular
899 NSMakeSize(18, 0), // mini
900 NSMakeSize(26, 0), // small
901 NSMakeSize(30, 0) // regular
905 {0, 0, 0, 0}, // mini
906 {4, 0, 4, 1}, // small
907 {5, 0, 5, 2} // regular
912 // The height at which we start doing square buttons instead of rounded buttons
913 // Rounded buttons look bad if drawn at a height greater than 26, so at that point
914 // we switch over to doing square buttons which looks fine at any size.
915 #define DO_SQUARE_BUTTON_HEIGHT 26
918 nsNativeThemeCocoa::DrawPushButton(CGContextRef cgContext, const HIRect& inBoxRect,
919 nsEventStates inState, nsIFrame* aFrame)
921 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
923 BOOL isActive = FrameIsInActiveWindow(aFrame);
924 BOOL isDisabled = IsDisabled(aFrame, inState);
926 [mPushButtonCell setEnabled:!isDisabled];
927 [mPushButtonCell setHighlighted:isActive &&
928 inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)];
929 [mPushButtonCell setShowsFirstResponder:inState.HasState(NS_EVENT_STATE_FOCUS) && !isDisabled && isActive];
931 // If the button is tall enough, draw the square button style so that buttons with
932 // non-standard content look good. Otherwise draw normal rounded aqua buttons.
933 if (inBoxRect.size.height > DO_SQUARE_BUTTON_HEIGHT) {
934 [mPushButtonCell setBezelStyle:NSShadowlessSquareBezelStyle];
935 DrawCellWithScaling(mPushButtonCell, cgContext, inBoxRect, NSRegularControlSize,
936 NSZeroSize, NSMakeSize(14, 0), NULL,
937 mCellDrawView, IsFrameRTL(aFrame));
939 [mPushButtonCell setBezelStyle:NSRoundedBezelStyle];
941 DrawCellWithSnapping(mPushButtonCell, cgContext, inBoxRect, pushButtonSettings,
942 0.5f, mCellDrawView, IsFrameRTL(aFrame), 1.0f);
945 #if DRAW_IN_FRAME_DEBUG
946 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
947 CGContextFillRect(cgContext, inBoxRect);
950 NS_OBJC_END_TRY_ABORT_BLOCK;
953 typedef void (*RenderHIThemeControlFunction)(CGContextRef cgContext, const HIRect& aRenderRect, void* aData);
956 RenderTransformedHIThemeControl(CGContextRef aCGContext, const HIRect& aRect,
957 RenderHIThemeControlFunction aFunc, void* aData,
958 BOOL mirrorHorizontally = NO)
960 CGAffineTransform savedCTM = CGContextGetCTM(aCGContext);
961 CGContextTranslateCTM(aCGContext, aRect.origin.x, aRect.origin.y);
964 HIRect drawRect = aRect;
965 drawRect.origin = CGPointZero;
967 if (!mirrorHorizontally && savedCTM.a == 1.0f && savedCTM.b == 0.0f &&
968 savedCTM.c == 0.0f && (savedCTM.d == 1.0f || savedCTM.d == -1.0f)) {
974 // Fall back to no bitmap buffer if the area of our control (in pixels^2)
976 if (drawDirect || (aRect.size.width * aRect.size.height > BITMAP_MAX_AREA)) {
977 aFunc(aCGContext, drawRect, aData);
979 // Inflate the buffer to capture focus rings.
980 int w = ceil(drawRect.size.width) + 2 * MAX_FOCUS_RING_WIDTH;
981 int h = ceil(drawRect.size.height) + 2 * MAX_FOCUS_RING_WIDTH;
983 int backingScaleFactor = GetBackingScaleFactorForRendering(aCGContext);
984 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
985 CGContextRef bitmapctx = CGBitmapContextCreate(NULL,
986 w * backingScaleFactor,
987 h * backingScaleFactor,
989 w * backingScaleFactor * 4,
991 kCGImageAlphaPremultipliedFirst);
992 CGColorSpaceRelease(colorSpace);
994 CGContextScaleCTM(bitmapctx, backingScaleFactor, backingScaleFactor);
995 CGContextTranslateCTM(bitmapctx, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
997 // HITheme always wants to draw into a flipped context, or things
999 CGContextTranslateCTM(bitmapctx, 0.0f, aRect.size.height);
1000 CGContextScaleCTM(bitmapctx, 1.0f, -1.0f);
1002 aFunc(bitmapctx, drawRect, aData);
1004 CGImageRef bitmap = CGBitmapContextCreateImage(bitmapctx);
1006 CGAffineTransform ctm = CGContextGetCTM(aCGContext);
1008 // We need to unflip, so that we can do a DrawImage without getting a flipped image.
1009 CGContextTranslateCTM(aCGContext, 0.0f, aRect.size.height);
1010 CGContextScaleCTM(aCGContext, 1.0f, -1.0f);
1012 if (mirrorHorizontally) {
1013 CGContextTranslateCTM(aCGContext, aRect.size.width, 0);
1014 CGContextScaleCTM(aCGContext, -1.0f, 1.0f);
1017 HIRect inflatedDrawRect = CGRectMake(-MAX_FOCUS_RING_WIDTH, -MAX_FOCUS_RING_WIDTH, w, h);
1018 CGContextDrawImage(aCGContext, inflatedDrawRect, bitmap);
1020 CGContextSetCTM(aCGContext, ctm);
1022 CGImageRelease(bitmap);
1023 CGContextRelease(bitmapctx);
1026 CGContextSetCTM(aCGContext, savedCTM);
1030 RenderButton(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1032 HIThemeButtonDrawInfo* bdi = (HIThemeButtonDrawInfo*)aData;
1033 HIThemeDrawButton(&aRenderRect, bdi, cgContext, kHIThemeOrientationNormal, NULL);
1037 nsNativeThemeCocoa::DrawButton(CGContextRef cgContext, ThemeButtonKind inKind,
1038 const HIRect& inBoxRect, bool inIsDefault,
1039 ThemeButtonValue inValue, ThemeButtonAdornment inAdornment,
1040 nsEventStates inState, nsIFrame* aFrame)
1042 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1044 BOOL isActive = FrameIsInActiveWindow(aFrame);
1045 BOOL isDisabled = IsDisabled(aFrame, inState);
1047 HIThemeButtonDrawInfo bdi;
1050 bdi.value = inValue;
1051 bdi.adornment = inAdornment;
1054 bdi.state = kThemeStateUnavailable;
1056 else if (inState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER)) {
1057 bdi.state = kThemeStatePressed;
1060 if (inKind == kThemeArrowButton)
1061 bdi.state = kThemeStateUnavailable; // these are always drawn as unavailable
1062 else if (!isActive && inKind == kThemeListHeaderButton)
1063 bdi.state = kThemeStateInactive;
1065 bdi.state = kThemeStateActive;
1068 if (inState.HasState(NS_EVENT_STATE_FOCUS) && isActive)
1069 bdi.adornment |= kThemeAdornmentFocus;
1071 if (inIsDefault && !isDisabled && isActive &&
1072 !inState.HasState(NS_EVENT_STATE_ACTIVE)) {
1073 bdi.adornment |= kThemeAdornmentDefault;
1074 bdi.animation.time.start = 0;
1075 bdi.animation.time.current = CFAbsoluteTimeGetCurrent();
1078 HIRect drawFrame = inBoxRect;
1080 if (inKind == kThemePushButton) {
1081 drawFrame.size.height -= 2;
1082 if (inBoxRect.size.height < pushButtonSettings.naturalSizes[smallControlSize].height) {
1083 bdi.kind = kThemePushButtonMini;
1085 else if (inBoxRect.size.height < pushButtonSettings.naturalSizes[regularControlSize].height) {
1086 bdi.kind = kThemePushButtonSmall;
1087 drawFrame.origin.y -= 1;
1088 drawFrame.origin.x += 1;
1089 drawFrame.size.width -= 2;
1092 else if (inKind == kThemeListHeaderButton) {
1093 CGContextClipToRect(cgContext, inBoxRect);
1094 // Always remove the top border.
1095 drawFrame.origin.y -= 1;
1096 drawFrame.size.height += 1;
1097 // Remove the left border in LTR mode and the right border in RTL mode.
1098 drawFrame.size.width += 1;
1099 bool isLast = IsLastTreeHeaderCell(aFrame);
1101 drawFrame.size.width += 1; // Also remove the other border.
1102 if (!IsFrameRTL(aFrame) || isLast)
1103 drawFrame.origin.x -= 1;
1106 RenderTransformedHIThemeControl(cgContext, drawFrame, RenderButton, &bdi,
1107 IsFrameRTL(aFrame));
1109 #if DRAW_IN_FRAME_DEBUG
1110 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1111 CGContextFillRect(cgContext, inBoxRect);
1114 NS_OBJC_END_TRY_ABORT_BLOCK;
1117 static const CellRenderSettings dropdownSettings = {
1119 NSMakeSize(0, 16), // mini
1120 NSMakeSize(0, 19), // small
1121 NSMakeSize(0, 22) // regular
1124 NSMakeSize(18, 0), // mini
1125 NSMakeSize(38, 0), // small
1126 NSMakeSize(44, 0) // regular
1130 {1, 1, 2, 1}, // mini
1131 {3, 0, 3, 1}, // small
1132 {3, 0, 3, 0} // regular
1137 static const CellRenderSettings editableMenulistSettings = {
1139 NSMakeSize(0, 15), // mini
1140 NSMakeSize(0, 18), // small
1141 NSMakeSize(0, 21) // regular
1144 NSMakeSize(18, 0), // mini
1145 NSMakeSize(38, 0), // small
1146 NSMakeSize(44, 0) // regular
1150 {0, 0, 2, 2}, // mini
1151 {0, 0, 3, 2}, // small
1152 {0, 1, 3, 3} // regular
1158 nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect,
1159 nsEventStates inState, uint8_t aWidgetType,
1162 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1164 [mDropdownCell setPullsDown:(aWidgetType == NS_THEME_BUTTON)];
1166 BOOL isEditable = (aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD);
1167 NSCell* cell = isEditable ? (NSCell*)mComboBoxCell : (NSCell*)mDropdownCell;
1169 [cell setEnabled:!IsDisabled(aFrame, inState)];
1170 [cell setShowsFirstResponder:(IsFocused(aFrame) || inState.HasState(NS_EVENT_STATE_FOCUS))];
1171 [cell setHighlighted:IsOpenButton(aFrame)];
1172 [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint] : NSClearControlTint)];
1174 const CellRenderSettings& settings = isEditable ? editableMenulistSettings : dropdownSettings;
1175 DrawCellWithSnapping(cell, cgContext, inBoxRect, settings,
1176 0.5f, mCellDrawView, IsFrameRTL(aFrame));
1178 NS_OBJC_END_TRY_ABORT_BLOCK;
1182 nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind,
1183 const HIRect& inBoxRect, ThemeDrawState inDrawState,
1184 ThemeButtonAdornment inAdornment,
1185 nsEventStates inState, nsIFrame* aFrame)
1187 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1189 HIThemeButtonDrawInfo bdi;
1192 bdi.value = kThemeButtonOff;
1193 bdi.adornment = inAdornment;
1195 if (IsDisabled(aFrame, inState))
1196 bdi.state = kThemeStateUnavailable;
1198 bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive;
1200 HIThemeDrawButton(&inBoxRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL);
1202 NS_OBJC_END_TRY_ABORT_BLOCK;
1206 nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind,
1207 const HIRect& inBoxRect, bool inDisabled,
1208 nsEventStates inState)
1210 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1212 HIThemeFrameDrawInfo fdi;
1216 // We don't ever set an inactive state for this because it doesn't
1217 // look right (see other apps).
1218 fdi.state = inDisabled ? kThemeStateUnavailable : kThemeStateActive;
1220 // for some reason focus rings on listboxes draw incorrectly
1221 if (inKind == kHIThemeFrameListBox)
1224 fdi.isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1226 // HIThemeDrawFrame takes the rect for the content area of the frame, not
1227 // the bounding rect for the frame. Here we reduce the size of the rect we
1228 // will pass to make it the size of the content.
1229 HIRect drawRect = inBoxRect;
1230 if (inKind == kHIThemeFrameTextFieldSquare) {
1231 SInt32 frameOutset = 0;
1232 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
1233 drawRect.origin.x += frameOutset;
1234 drawRect.origin.y += frameOutset;
1235 drawRect.size.width -= frameOutset * 2;
1236 drawRect.size.height -= frameOutset * 2;
1238 else if (inKind == kHIThemeFrameListBox) {
1239 SInt32 frameOutset = 0;
1240 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
1241 drawRect.origin.x += frameOutset;
1242 drawRect.origin.y += frameOutset;
1243 drawRect.size.width -= frameOutset * 2;
1244 drawRect.size.height -= frameOutset * 2;
1247 #if DRAW_IN_FRAME_DEBUG
1248 CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 0.5, 0.25);
1249 CGContextFillRect(cgContext, inBoxRect);
1252 HIThemeDrawFrame(&drawRect, &fdi, cgContext, HITHEME_ORIENTATION);
1254 NS_OBJC_END_TRY_ABORT_BLOCK;
1257 static const CellRenderSettings progressSettings[2][2] = {
1258 // Vertical progress bar.
1260 // Determined settings.
1264 NSMakeSize(10, 0), // small
1265 NSMakeSize(16, 0) // regular
1268 NSZeroSize, NSZeroSize, NSZeroSize
1272 {0, 0, 0, 0}, // mini
1273 {1, 1, 1, 1}, // small
1274 {1, 1, 1, 1} // regular
1278 // There is no horizontal margin in regular undetermined size.
1282 NSMakeSize(10, 0), // small
1283 NSMakeSize(16, 0) // regular
1286 NSZeroSize, NSZeroSize, NSZeroSize
1290 {0, 0, 0, 0}, // mini
1291 {1, 1, 1, 1}, // small
1292 {1, 0, 1, 0} // regular
1297 // Horizontal progress bar.
1299 // Determined settings.
1303 NSMakeSize(0, 10), // small
1304 NSMakeSize(0, 16) // regular
1307 NSZeroSize, NSZeroSize, NSZeroSize
1311 {0, 0, 0, 0}, // mini
1312 {1, 1, 1, 1}, // small
1313 {1, 1, 1, 1} // regular
1317 // There is no horizontal margin in regular undetermined size.
1321 NSMakeSize(0, 10), // small
1322 NSMakeSize(0, 16) // regular
1325 NSZeroSize, NSZeroSize, NSZeroSize
1329 {0, 0, 0, 0}, // mini
1330 {1, 1, 1, 1}, // small
1331 {0, 1, 0, 1} // regular
1339 nsNativeThemeCocoa::DrawProgress(CGContextRef cgContext, const HIRect& inBoxRect,
1340 bool inIsIndeterminate, bool inIsHorizontal,
1341 double inValue, double inMaxValue,
1344 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1346 NSProgressBarCell* cell = mProgressBarCell;
1348 [cell setValue:inValue];
1349 [cell setMax:inMaxValue];
1350 [cell setIndeterminate:inIsIndeterminate];
1351 [cell setHorizontal:inIsHorizontal];
1352 [cell setControlTint:(FrameIsInActiveWindow(aFrame) ? [NSColor currentControlTint]
1353 : NSClearControlTint)];
1355 DrawCellWithSnapping(cell, cgContext, inBoxRect,
1356 progressSettings[inIsHorizontal][inIsIndeterminate],
1357 VerticalAlignFactor(aFrame), mCellDrawView,
1358 IsFrameRTL(aFrame));
1360 NS_OBJC_END_TRY_ABORT_BLOCK;
1363 static const CellRenderSettings meterSetting = {
1365 NSMakeSize(0, 16), // mini
1366 NSMakeSize(0, 16), // small
1367 NSMakeSize(0, 16) // regular
1370 NSZeroSize, NSZeroSize, NSZeroSize
1374 {1, 1, 1, 1}, // mini
1375 {1, 1, 1, 1}, // small
1376 {1, 1, 1, 1} // regular
1382 nsNativeThemeCocoa::DrawMeter(CGContextRef cgContext, const HIRect& inBoxRect,
1385 NS_OBJC_BEGIN_TRY_ABORT_BLOCK
1387 NS_PRECONDITION(aFrame, "aFrame should not be null here!");
1389 nsCOMPtr<nsIDOMHTMLMeterElement> meterElement =
1390 do_QueryInterface(aFrame->GetContent());
1392 // When using -moz-meterbar on an non meter element, we will not be able to
1393 // get all the needed information so we just draw an empty meter.
1394 if (!meterElement) {
1395 DrawCellWithSnapping(mMeterBarCell, cgContext, inBoxRect,
1396 meterSetting, VerticalAlignFactor(aFrame),
1397 mCellDrawView, IsFrameRTL(aFrame));
1408 // NOTE: if we were allowed to static_cast to nsHTMLMeterElement we would be
1409 // able to use nicer getters...
1410 meterElement->GetValue(&value);
1411 meterElement->GetMin(&min);
1412 meterElement->GetMax(&max);
1413 meterElement->GetLow(&low);
1414 meterElement->GetHigh(&high);
1415 meterElement->GetOptimum(&optimum);
1417 NSLevelIndicatorCell* cell = mMeterBarCell;
1419 [cell setMinValue:min];
1420 [cell setMaxValue:max];
1421 [cell setDoubleValue:value];
1424 * The way HTML and Cocoa defines the meter/indicator widget are different.
1425 * So, we are going to use a trick to get the Cocoa widget showing what we
1426 * are expecting: we set the warningValue or criticalValue to the current
1427 * value when we want to have the widget to be in the warning or critical
1430 nsEventStates states = aFrame->GetContent()->AsElement()->State();
1432 // Reset previously set warning and critical values.
1433 [cell setWarningValue:max+1];
1434 [cell setCriticalValue:max+1];
1436 if (states.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
1437 [cell setWarningValue:value];
1438 } else if (states.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
1439 [cell setCriticalValue:value];
1442 HIRect rect = CGRectStandardize(inBoxRect);
1443 BOOL vertical = IsVerticalMeter(aFrame);
1445 CGContextSaveGState(cgContext);
1449 * Cocoa doesn't provide a vertical meter bar so to show one, we have to
1450 * show a rotated horizontal meter bar.
1451 * Given that we want to show a vertical meter bar, we assume that the rect
1452 * has vertical dimensions but we can't correctly draw a meter widget inside
1453 * such a rectangle so we need to inverse width and height (and re-position)
1454 * to get a rectangle with horizontal dimensions.
1455 * Finally, we want to show a vertical meter so we want to rotate the result
1456 * so it is vertical. We do that by changing the context.
1458 CGFloat tmp = rect.size.width;
1459 rect.size.width = rect.size.height;
1460 rect.size.height = tmp;
1461 rect.origin.x += rect.size.height / 2.f - rect.size.width / 2.f;
1462 rect.origin.y += rect.size.width / 2.f - rect.size.height / 2.f;
1464 CGContextTranslateCTM(cgContext, CGRectGetMidX(rect), CGRectGetMidY(rect));
1465 CGContextRotateCTM(cgContext, -M_PI / 2.f);
1466 CGContextTranslateCTM(cgContext, -CGRectGetMidX(rect), -CGRectGetMidY(rect));
1469 DrawCellWithSnapping(cell, cgContext, rect,
1470 meterSetting, VerticalAlignFactor(aFrame),
1471 mCellDrawView, !vertical && IsFrameRTL(aFrame));
1473 CGContextRestoreGState(cgContext);
1475 NS_OBJC_END_TRY_ABORT_BLOCK
1479 nsNativeThemeCocoa::DrawTabPanel(CGContextRef cgContext, const HIRect& inBoxRect,
1482 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1484 HIThemeTabPaneDrawInfo tpdi;
1487 tpdi.state = FrameIsInActiveWindow(aFrame) ? kThemeStateActive : kThemeStateInactive;
1488 tpdi.direction = kThemeTabNorth;
1489 tpdi.size = kHIThemeTabSizeNormal;
1490 tpdi.kind = kHIThemeTabKindNormal;
1492 HIThemeDrawTabPane(&inBoxRect, &tpdi, cgContext, HITHEME_ORIENTATION);
1494 NS_OBJC_END_TRY_ABORT_BLOCK;
1498 nsNativeThemeCocoa::DrawScale(CGContextRef cgContext, const HIRect& inBoxRect,
1499 nsEventStates inState, bool inIsVertical,
1500 bool inIsReverse, int32_t inCurrentValue,
1501 int32_t inMinValue, int32_t inMaxValue,
1504 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1506 HIThemeTrackDrawInfo tdi;
1509 tdi.kind = kThemeMediumSlider;
1510 tdi.bounds = inBoxRect;
1511 tdi.min = inMinValue;
1512 tdi.max = inMaxValue;
1513 tdi.value = inCurrentValue;
1514 tdi.attributes = kThemeTrackShowThumb;
1516 tdi.attributes |= kThemeTrackHorizontal;
1518 tdi.attributes |= kThemeTrackRightToLeft;
1519 if (inState.HasState(NS_EVENT_STATE_FOCUS))
1520 tdi.attributes |= kThemeTrackHasFocus;
1521 if (IsDisabled(aFrame, inState))
1522 tdi.enableState = kThemeTrackDisabled;
1524 tdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1525 tdi.trackInfo.slider.thumbDir = kThemeThumbPlain;
1526 tdi.trackInfo.slider.pressState = 0;
1528 HIThemeDrawTrack(&tdi, NULL, cgContext, HITHEME_ORIENTATION);
1530 NS_OBJC_END_TRY_ABORT_BLOCK;
1534 nsNativeThemeCocoa::SeparatorResponsibility(nsIFrame* aBefore, nsIFrame* aAfter)
1536 // Usually a separator is drawn by the segment to the right of the
1537 // separator, but pressed and selected segments have higher priority.
1538 if (!aBefore || !aAfter)
1540 if (IsSelectedButton(aAfter))
1542 if (IsSelectedButton(aBefore) || IsPressedButton(aBefore))
1548 nsNativeThemeCocoa::SeparatorAdjustedRect(CGRect aRect, nsIFrame* aLeft,
1549 nsIFrame* aCurrent, nsIFrame* aRight)
1551 // A separator between two segments should always be located in the leftmost
1552 // pixel column of the segment to the right of the separator, regardless of
1553 // who ends up drawing it.
1554 // CoreUI draws the separators inside the drawing rect.
1555 if (aLeft && SeparatorResponsibility(aLeft, aCurrent) == aLeft) {
1556 // The left button draws the separator, so we need to make room for it.
1557 aRect.origin.x += 1;
1558 aRect.size.width -= 1;
1560 if (SeparatorResponsibility(aCurrent, aRight) == aCurrent) {
1561 // We draw the right separator, so we need to extend the draw rect into the
1562 // segment to our right.
1563 aRect.size.width += 1;
1568 static NSString* ToolbarButtonPosition(BOOL aIsFirst, BOOL aIsLast)
1572 return @"kCUISegmentPositionOnly";
1573 return @"kCUISegmentPositionFirst";
1576 return @"kCUISegmentPositionLast";
1577 return @"kCUISegmentPositionMiddle";
1580 struct SegmentedControlRenderSettings {
1581 const CGFloat* heights;
1582 const NSString* widgetName;
1583 const BOOL ignoresPressedWhenSelected;
1584 const BOOL isToolbarControl;
1587 static const CGFloat tabHeights[3] = { 17, 20, 23 };
1589 static const SegmentedControlRenderSettings tabRenderSettings = {
1590 tabHeights, @"tab", YES, NO
1593 static const CGFloat toolbarButtonHeights[3] = { 15, 18, 22 };
1595 static const SegmentedControlRenderSettings toolbarButtonRenderSettings = {
1596 toolbarButtonHeights, @"kCUIWidgetButtonSegmentedSCurve", NO, YES
1600 nsNativeThemeCocoa::DrawSegment(CGContextRef cgContext, const HIRect& inBoxRect,
1601 nsEventStates inState, nsIFrame* aFrame,
1602 const SegmentedControlRenderSettings& aSettings)
1604 BOOL isActive = IsActive(aFrame, aSettings.isToolbarControl);
1605 BOOL isFocused = inState.HasState(NS_EVENT_STATE_FOCUS);
1606 BOOL isSelected = IsSelectedButton(aFrame);
1607 BOOL isPressed = IsPressedButton(aFrame);
1608 if (isSelected && aSettings.ignoresPressedWhenSelected) {
1612 BOOL isRTL = IsFrameRTL(aFrame);
1613 nsIFrame* left = GetAdjacentSiblingFrameWithSameAppearance(aFrame, isRTL);
1614 nsIFrame* right = GetAdjacentSiblingFrameWithSameAppearance(aFrame, !isRTL);
1615 CGRect drawRect = SeparatorAdjustedRect(inBoxRect, left, aFrame, right);
1616 if (drawRect.size.width * drawRect.size.height > CUIDRAW_MAX_AREA) {
1619 BOOL drawLeftSeparator = SeparatorResponsibility(left, aFrame) == aFrame;
1620 BOOL drawRightSeparator = SeparatorResponsibility(aFrame, right) == aFrame;
1621 NSControlSize controlSize = FindControlSize(drawRect.size.height, aSettings.heights, 4.0f);
1623 CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext,
1624 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
1625 aSettings.widgetName, @"widget",
1626 ToolbarButtonPosition(!left, !right), @"kCUIPositionKey",
1627 [NSNumber numberWithBool:drawLeftSeparator], @"kCUISegmentLeadingSeparatorKey",
1628 [NSNumber numberWithBool:drawRightSeparator], @"kCUISegmentTrailingSeparatorKey",
1629 [NSNumber numberWithBool:isSelected], @"value",
1630 (isPressed ? @"pressed" : (isActive ? @"normal" : @"inactive")), @"state",
1631 [NSNumber numberWithBool:isFocused], @"focus",
1632 CUIControlSizeForCocoaSize(controlSize), @"size",
1633 [NSNumber numberWithBool:YES], @"is.flipped",
1634 @"up", @"direction",
1640 ConvertToPressState(nsEventStates aButtonState, UInt8 aPressState)
1642 // If the button is pressed, return the press state passed in. Otherwise, return 0.
1643 return aButtonState.HasAllStates(NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_HOVER) ? aPressState : 0;
1647 nsNativeThemeCocoa::GetScrollbarPressStates(nsIFrame *aFrame, nsEventStates aButtonStates[])
1649 static nsIContent::AttrValuesArray attributeValues[] = {
1650 &nsGkAtoms::scrollbarUpTop,
1651 &nsGkAtoms::scrollbarDownTop,
1652 &nsGkAtoms::scrollbarUpBottom,
1653 &nsGkAtoms::scrollbarDownBottom,
1657 // Get the state of any scrollbar buttons in our child frames
1658 for (nsIFrame *childFrame = aFrame->GetFirstPrincipalChild();
1660 childFrame = childFrame->GetNextSibling()) {
1662 nsIContent *childContent = childFrame->GetContent();
1663 if (!childContent) continue;
1664 int32_t attrIndex = childContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::sbattr,
1665 attributeValues, eCaseMatters);
1666 if (attrIndex < 0) continue;
1668 aButtonStates[attrIndex] = GetContentState(childFrame, NS_THEME_BUTTON);
1672 // Both of the following sets of numbers were derived by loading the testcase in
1673 // bmo bug 380185 in Safari and observing its behavior for various heights of scrollbar.
1674 // These magic numbers are the minimum sizes we can draw a scrollbar and still
1675 // have room for everything to display, including the thumb
1676 #define MIN_SCROLLBAR_SIZE_WITH_THUMB 61
1677 #define MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB 49
1678 // And these are the minimum sizes if we don't draw the thumb
1679 #define MIN_SCROLLBAR_SIZE 56
1680 #define MIN_SMALL_SCROLLBAR_SIZE 46
1683 nsNativeThemeCocoa::GetScrollbarDrawInfo(HIThemeTrackDrawInfo& aTdi, nsIFrame *aFrame,
1684 const CGSize& aSize, bool aShouldGetButtonStates)
1686 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1688 int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
1689 int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
1690 int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
1691 int32_t thumbSize = CheckIntAttr(aFrame, nsGkAtoms::pageincrement, 10);
1693 bool isHorizontal = aFrame->GetContent()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::orient,
1694 nsGkAtoms::horizontal, eCaseMatters);
1695 bool isSmall = aFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL;
1698 aTdi.kind = isSmall ? kThemeSmallScrollBar : kThemeMediumScrollBar;
1699 aTdi.bounds.origin = CGPointZero;
1700 aTdi.bounds.size = aSize;
1703 aTdi.value = curpos;
1704 aTdi.attributes = 0;
1705 aTdi.enableState = kThemeTrackActive;
1707 aTdi.attributes |= kThemeTrackHorizontal;
1709 aTdi.trackInfo.scrollbar.viewsize = (SInt32)thumbSize;
1711 // This should be done early on so things like "kThemeTrackNothingToScroll" can
1712 // override the active enable state.
1713 aTdi.enableState = FrameIsInActiveWindow(aFrame) ? kThemeTrackActive : kThemeTrackInactive;
1715 /* Only display features if we have enough room for them.
1716 * Gecko still maintains the scrollbar info; this is just a visual issue (bug 380185).
1718 int32_t longSideLength = (int32_t)(isHorizontal ? (aSize.width) : (aSize.height));
1719 if (longSideLength >= (isSmall ? MIN_SMALL_SCROLLBAR_SIZE_WITH_THUMB : MIN_SCROLLBAR_SIZE_WITH_THUMB)) {
1720 aTdi.attributes |= kThemeTrackShowThumb;
1722 else if (longSideLength < (isSmall ? MIN_SMALL_SCROLLBAR_SIZE : MIN_SCROLLBAR_SIZE)) {
1723 aTdi.enableState = kThemeTrackNothingToScroll;
1727 aTdi.trackInfo.scrollbar.pressState = 0;
1729 // Only go get these scrollbar button states if we need it. For example,
1730 // there's no reason to look up scrollbar button states when we're only
1731 // creating a TrackDrawInfo to determine the size of the thumb. There's
1732 // also no reason to do this on Lion or later, whose scrollbars have no
1734 if (aShouldGetButtonStates && !nsCocoaFeatures::OnLionOrLater()) {
1735 nsEventStates buttonStates[4];
1736 GetScrollbarPressStates(aFrame, buttonStates);
1737 NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
1738 // It seems that unless all four buttons are showing, kThemeTopOutsideArrowPressed is the correct constant for
1739 // the up scrollbar button.
1740 if ([buttonPlacement isEqualToString:@"DoubleBoth"]) {
1741 aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1742 ConvertToPressState(buttonStates[1], kThemeTopInsideArrowPressed) |
1743 ConvertToPressState(buttonStates[2], kThemeBottomInsideArrowPressed) |
1744 ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1746 aTdi.trackInfo.scrollbar.pressState = ConvertToPressState(buttonStates[0], kThemeTopOutsideArrowPressed) |
1747 ConvertToPressState(buttonStates[1], kThemeBottomOutsideArrowPressed) |
1748 ConvertToPressState(buttonStates[2], kThemeTopOutsideArrowPressed) |
1749 ConvertToPressState(buttonStates[3], kThemeBottomOutsideArrowPressed);
1753 NS_OBJC_END_TRY_ABORT_BLOCK;
1757 RenderScrollbar(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1759 HIThemeTrackDrawInfo* tdi = (HIThemeTrackDrawInfo*)aData;
1760 HIThemeDrawTrack(tdi, NULL, cgContext, HITHEME_ORIENTATION);
1764 nsNativeThemeCocoa::DrawScrollbar(CGContextRef aCGContext, const HIRect& aBoxRect, nsIFrame *aFrame)
1766 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1768 HIThemeTrackDrawInfo tdi;
1769 GetScrollbarDrawInfo(tdi, aFrame, aBoxRect.size, true); // True means we want the press states
1770 RenderTransformedHIThemeControl(aCGContext, aBoxRect, RenderScrollbar, &tdi);
1772 NS_OBJC_END_TRY_ABORT_BLOCK;
1776 nsNativeThemeCocoa::GetParentScrollbarFrame(nsIFrame *aFrame)
1778 // Walk our parents to find a scrollbar frame
1779 nsIFrame *scrollbarFrame = aFrame;
1781 if (scrollbarFrame->GetType() == nsGkAtoms::scrollbarFrame) break;
1782 } while ((scrollbarFrame = scrollbarFrame->GetParent()));
1784 // We return null if we can't find a parent scrollbar frame
1785 return scrollbarFrame;
1789 ToolbarCanBeUnified(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow)
1791 if (![aWindow isKindOfClass:[ToolbarWindow class]] ||
1792 [(ToolbarWindow*)aWindow drawsContentsIntoWindowFrame])
1795 float unifiedToolbarHeight = [(ToolbarWindow*)aWindow unifiedToolbarHeight];
1796 CGAffineTransform ctm = CGContextGetUserSpaceToDeviceSpaceTransform(cgContext);
1797 CGRect deviceRect = CGRectApplyAffineTransform(inBoxRect, ctm);
1798 return inBoxRect.origin.x == 0 &&
1799 deviceRect.size.width >= [aWindow frame].size.width &&
1800 inBoxRect.origin.y <= 0.0 &&
1801 floor(inBoxRect.origin.y + inBoxRect.size.height) <= unifiedToolbarHeight;
1805 nsNativeThemeCocoa::DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect,
1808 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1810 float titlebarHeight = [(ToolbarWindow*)aWindow titlebarHeight];
1811 float unifiedHeight = titlebarHeight + inBoxRect.size.height;
1813 BOOL isMain = [aWindow isMainWindow];
1815 CGContextSaveGState(cgContext);
1816 CGContextClipToRect(cgContext, inBoxRect);
1818 CGRect drawRect = CGRectOffset(inBoxRect, 0, -titlebarHeight);
1819 if (drawRect.size.width * drawRect.size.height <= CUIDRAW_MAX_AREA) {
1820 CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext,
1821 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
1822 @"kCUIWidgetWindowFrame", @"widget",
1823 @"regularwin", @"windowtype",
1824 (isMain ? @"normal" : @"inactive"), @"state",
1825 [NSNumber numberWithInt:unifiedHeight], @"kCUIWindowFrameUnifiedTitleBarHeightKey",
1826 [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawTitleSeparatorKey",
1827 [NSNumber numberWithBool:YES], @"is.flipped",
1832 CGContextRestoreGState(cgContext);
1834 NS_OBJC_END_TRY_ABORT_BLOCK;
1838 nsNativeThemeCocoa::DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect,
1841 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1843 if (inBoxRect.size.height < 2.0f)
1846 CGContextSaveGState(cgContext);
1847 CGContextClipToRect(cgContext, inBoxRect);
1849 // kCUIWidgetWindowFrame draws a complete window frame with both title bar
1850 // and bottom bar. We only want the bottom bar, so we extend the draw rect
1851 // upwards to make space for the title bar, and then we clip it away.
1852 CGRect drawRect = inBoxRect;
1853 const int extendUpwards = 40;
1854 drawRect.origin.y -= extendUpwards;
1855 drawRect.size.height += extendUpwards;
1856 if (drawRect.size.width * drawRect.size.height <= CUIDRAW_MAX_AREA) {
1857 CUIDraw([NSWindow coreUIRenderer], drawRect, cgContext,
1858 (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:
1859 @"kCUIWidgetWindowFrame", @"widget",
1860 @"regularwin", @"windowtype",
1861 (IsActive(aFrame, YES) ? @"normal" : @"inactive"), @"state",
1862 [NSNumber numberWithInt:inBoxRect.size.height], @"kCUIWindowFrameBottomBarHeightKey",
1863 [NSNumber numberWithBool:YES], @"kCUIWindowFrameDrawBottomBarSeparatorKey",
1864 [NSNumber numberWithBool:YES], @"is.flipped",
1869 CGContextRestoreGState(cgContext);
1871 NS_OBJC_END_TRY_ABORT_BLOCK;
1875 RenderResizer(CGContextRef cgContext, const HIRect& aRenderRect, void* aData)
1877 HIThemeGrowBoxDrawInfo* drawInfo = (HIThemeGrowBoxDrawInfo*)aData;
1878 HIThemeDrawGrowBox(&CGPointZero, drawInfo, cgContext, kHIThemeOrientationNormal);
1882 nsNativeThemeCocoa::DrawResizer(CGContextRef cgContext, const HIRect& aRect,
1885 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
1887 HIThemeGrowBoxDrawInfo drawInfo;
1888 drawInfo.version = 0;
1889 drawInfo.state = kThemeStateActive;
1890 drawInfo.kind = kHIThemeGrowBoxKindNormal;
1891 drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
1892 drawInfo.size = kHIThemeGrowBoxSizeNormal;
1894 RenderTransformedHIThemeControl(cgContext, aRect, RenderResizer, &drawInfo,
1895 IsFrameRTL(aFrame));
1897 NS_OBJC_END_TRY_ABORT_BLOCK;
1901 IsHiDPIContext(nsDeviceContext* aContext)
1903 return nsPresContext::AppUnitsPerCSSPixel() >=
1904 2 * aContext->UnscaledAppUnitsPerDevPixel();
1908 nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext,
1910 uint8_t aWidgetType,
1911 const nsRect& aRect,
1912 const nsRect& aDirtyRect)
1914 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
1916 // setup to draw into the correct port
1917 int32_t p2a = aContext->AppUnitsPerDevPixel();
1919 gfxRect nativeDirtyRect(aDirtyRect.x, aDirtyRect.y,
1920 aDirtyRect.width, aDirtyRect.height);
1921 gfxRect nativeWidgetRect(aRect.x, aRect.y, aRect.width, aRect.height);
1922 nativeWidgetRect.ScaleInverse(gfxFloat(p2a));
1923 nativeDirtyRect.ScaleInverse(gfxFloat(p2a));
1924 nativeWidgetRect.Round();
1925 if (nativeWidgetRect.IsEmpty())
1926 return NS_OK; // Don't attempt to draw invisible widgets.
1928 gfxContext* thebesCtx = aContext->ThebesContext();
1930 return NS_ERROR_FAILURE;
1932 gfxContextMatrixAutoSaveRestore save(thebesCtx);
1934 bool hidpi = IsHiDPIContext(aContext->DeviceContext());
1936 // Use high-resolution drawing.
1937 nativeWidgetRect.ScaleInverse(2.0f);
1938 nativeDirtyRect.ScaleInverse(2.0f);
1939 thebesCtx->Scale(2.0f, 2.0f);
1942 gfxQuartzNativeDrawing nativeDrawing(thebesCtx, nativeDirtyRect,
1943 hidpi ? 2.0f : 1.0f);
1945 CGContextRef cgContext = nativeDrawing.BeginNativeDrawing();
1946 if (cgContext == nullptr) {
1947 // The Quartz surface handles 0x0 surfaces by internally
1948 // making all operations no-ops; there's no cgcontext created for them.
1949 // Unfortunately, this means that callers that want to render
1950 // directly to the CGContext need to be aware of this quirk.
1955 if (1 /*aWidgetType == NS_THEME_TEXTFIELD*/) {
1956 fprintf(stderr, "Native theme drawing widget %d [%p] dis:%d in rect [%d %d %d %d]\n",
1957 aWidgetType, aFrame, IsDisabled(aFrame), aRect.x, aRect.y, aRect.width, aRect.height);
1958 fprintf(stderr, "Cairo matrix: [%f %f %f %f %f %f]\n",
1959 mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0);
1960 fprintf(stderr, "Native theme xform[0]: [%f %f %f %f %f %f]\n",
1961 mm0.a, mm0.b, mm0.c, mm0.d, mm0.tx, mm0.ty);
1962 CGAffineTransform mm = CGContextGetCTM(cgContext);
1963 fprintf(stderr, "Native theme xform[1]: [%f %f %f %f %f %f]\n",
1964 mm.a, mm.b, mm.c, mm.d, mm.tx, mm.ty);
1968 CGRect macRect = CGRectMake(nativeWidgetRect.X(), nativeWidgetRect.Y(),
1969 nativeWidgetRect.Width(), nativeWidgetRect.Height());
1972 fprintf(stderr, " --> macRect %f %f %f %f\n",
1973 macRect.origin.x, macRect.origin.y, macRect.size.width, macRect.size.height);
1974 CGRect bounds = CGContextGetClipBoundingBox(cgContext);
1975 fprintf(stderr, " --> clip bounds: %f %f %f %f\n",
1976 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);
1978 //CGContextSetRGBFillColor(cgContext, 0.0, 0.0, 1.0, 0.1);
1979 //CGContextFillRect(cgContext, bounds);
1982 nsEventStates eventState = GetContentState(aFrame, aWidgetType);
1984 switch (aWidgetType) {
1985 case NS_THEME_DIALOG: {
1986 HIThemeSetFill(kThemeBrushDialogBackgroundActive, NULL, cgContext, HITHEME_ORIENTATION);
1987 CGContextFillRect(cgContext, macRect);
1991 case NS_THEME_MENUPOPUP: {
1992 HIThemeMenuDrawInfo mdi;
1993 memset(&mdi, 0, sizeof(mdi));
1995 mdi.menuType = IsDisabled(aFrame, eventState) ?
1996 static_cast<ThemeMenuType>(kThemeMenuTypeInactive) :
1997 static_cast<ThemeMenuType>(kThemeMenuTypePopUp);
1999 bool isLeftOfParent = false;
2000 if (IsSubmenu(aFrame, &isLeftOfParent) && !isLeftOfParent) {
2001 mdi.menuType = kThemeMenuTypeHierarchical;
2004 // The rounded corners draw outside the frame.
2005 CGRect deflatedRect = CGRectMake(macRect.origin.x, macRect.origin.y + 4,
2006 macRect.size.width, macRect.size.height - 8);
2007 HIThemeDrawMenuBackground(&deflatedRect, &mdi, cgContext, HITHEME_ORIENTATION);
2011 case NS_THEME_MENUITEM: {
2012 if (thebesCtx->OriginalSurface()->GetContentType() == gfxASurface::CONTENT_COLOR_ALPHA) {
2013 // Clear the background to get correct transparency.
2014 CGContextClearRect(cgContext, macRect);
2017 // maybe use kThemeMenuItemHierBackground or PopUpBackground instead of just Plain?
2018 HIThemeMenuItemDrawInfo drawInfo;
2019 memset(&drawInfo, 0, sizeof(drawInfo));
2020 drawInfo.version = 0;
2021 drawInfo.itemType = kThemeMenuItemPlain;
2022 drawInfo.state = (IsDisabled(aFrame, eventState) ?
2023 static_cast<ThemeMenuState>(kThemeMenuDisabled) :
2024 CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
2025 static_cast<ThemeMenuState>(kThemeMenuSelected) :
2026 static_cast<ThemeMenuState>(kThemeMenuActive));
2028 // XXX pass in the menu rect instead of always using the item rect
2030 HIThemeDrawMenuItem(&macRect, &macRect, &drawInfo, cgContext, HITHEME_ORIENTATION, &ignored);
2034 case NS_THEME_MENUSEPARATOR: {
2035 ThemeMenuState menuState;
2036 if (IsDisabled(aFrame, eventState)) {
2037 menuState = kThemeMenuDisabled;
2040 menuState = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive) ?
2041 kThemeMenuSelected : kThemeMenuActive;
2044 HIThemeMenuItemDrawInfo midi = { 0, kThemeMenuItemPlain, menuState };
2045 HIThemeDrawMenuSeparator(&macRect, &macRect, &midi, cgContext, HITHEME_ORIENTATION);
2049 case NS_THEME_TOOLTIP:
2050 CGContextSetRGBFillColor(cgContext, 0.996, 1.000, 0.792, 0.950);
2051 CGContextFillRect(cgContext, macRect);
2054 case NS_THEME_CHECKBOX:
2055 case NS_THEME_RADIO: {
2056 bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
2057 DrawCheckboxOrRadio(cgContext, isCheckbox, macRect, GetCheckedOrSelected(aFrame, !isCheckbox),
2058 eventState, aFrame);
2062 case NS_THEME_BUTTON:
2063 if (IsDefaultButton(aFrame)) {
2064 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 10)) {
2065 NS_WARNING("Unable to animate button!");
2067 DrawButton(cgContext, kThemePushButton, macRect, true,
2068 kThemeButtonOff, kThemeAdornmentNone, eventState, aFrame);
2069 } else if (IsButtonTypeMenu(aFrame)) {
2070 DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
2072 DrawPushButton(cgContext, macRect, eventState, aFrame);
2076 case NS_THEME_BUTTON_BEVEL:
2077 DrawButton(cgContext, kThemeMediumBevelButton, macRect,
2078 IsDefaultButton(aFrame), kThemeButtonOff, kThemeAdornmentNone,
2079 eventState, aFrame);
2082 case NS_THEME_SPINNER: {
2083 ThemeDrawState state = kThemeStateActive;
2084 nsIContent* content = aFrame->GetContent();
2085 if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2086 NS_LITERAL_STRING("up"), eCaseMatters)) {
2087 state = kThemeStatePressedUp;
2089 else if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state,
2090 NS_LITERAL_STRING("down"), eCaseMatters)) {
2091 state = kThemeStatePressedDown;
2094 DrawSpinButtons(cgContext, kThemeIncDecButton, macRect, state,
2095 kThemeAdornmentNone, eventState, aFrame);
2099 case NS_THEME_TOOLBAR_BUTTON:
2100 DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings);
2103 case NS_THEME_TOOLBAR_SEPARATOR: {
2104 HIThemeSeparatorDrawInfo sdi = { 0, kThemeStateActive };
2105 HIThemeDrawSeparator(&macRect, &sdi, cgContext, HITHEME_ORIENTATION);
2109 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2110 case NS_THEME_TOOLBAR: {
2111 NSWindow* win = NativeWindowForFrame(aFrame);
2112 if (ToolbarCanBeUnified(cgContext, macRect, win)) {
2113 DrawUnifiedToolbar(cgContext, macRect, win);
2116 BOOL isMain = [win isMainWindow];
2117 CGRect drawRect = macRect;
2120 drawRect.size.height = 1.0f;
2121 DrawNativeGreyColorInRect(cgContext, toolbarTopBorderGrey, drawRect, isMain);
2124 drawRect.origin.y += drawRect.size.height;
2125 drawRect.size.height = macRect.size.height - 2.0f;
2126 DrawNativeGreyColorInRect(cgContext, toolbarFillGrey, drawRect, isMain);
2129 drawRect.origin.y += drawRect.size.height;
2130 drawRect.size.height = 1.0f;
2131 DrawNativeGreyColorInRect(cgContext, toolbarBottomBorderGrey, drawRect, isMain);
2135 case NS_THEME_TOOLBOX: {
2136 HIThemeHeaderDrawInfo hdi = { 0, kThemeStateActive, kHIThemeHeaderKindWindow };
2137 HIThemeDrawHeader(&macRect, &hdi, cgContext, HITHEME_ORIENTATION);
2141 case NS_THEME_STATUSBAR:
2142 DrawStatusBar(cgContext, macRect, aFrame);
2145 case NS_THEME_DROPDOWN:
2146 case NS_THEME_DROPDOWN_TEXTFIELD:
2147 DrawDropdown(cgContext, macRect, eventState, aWidgetType, aFrame);
2150 case NS_THEME_DROPDOWN_BUTTON:
2151 DrawButton(cgContext, kThemeArrowButton, macRect, false, kThemeButtonOn,
2152 kThemeAdornmentArrowDownArrow, eventState, aFrame);
2155 case NS_THEME_GROUPBOX: {
2156 HIThemeGroupBoxDrawInfo gdi = { 0, kThemeStateActive, kHIThemeGroupBoxKindPrimary };
2157 HIThemeDrawGroupBox(&macRect, &gdi, cgContext, HITHEME_ORIENTATION);
2161 case NS_THEME_TEXTFIELD:
2162 // HIThemeSetFill is not available on 10.3
2163 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2164 CGContextFillRect(cgContext, macRect);
2166 // XUL textboxes set the native appearance on the containing box, while
2167 // concrete focus is set on the html:input element within it. We can
2168 // though, check the focused attribute of xul textboxes in this case.
2169 // On Mac, focus rings are always shown for textboxes, so we do not need
2170 // to check the window's focus ring state here
2171 if (aFrame->GetContent()->IsXUL() && IsFocused(aFrame)) {
2172 eventState |= NS_EVENT_STATE_FOCUS;
2175 DrawFrame(cgContext, kHIThemeFrameTextFieldSquare, macRect,
2176 IsDisabled(aFrame, eventState) || IsReadOnly(aFrame), eventState);
2179 case NS_THEME_SEARCHFIELD:
2180 DrawSearchField(cgContext, macRect, aFrame, eventState);
2183 case NS_THEME_PROGRESSBAR:
2184 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
2185 NS_WARNING("Unable to animate progressbar!");
2187 DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
2188 aFrame->GetStyleDisplay()->mOrient != NS_STYLE_ORIENT_VERTICAL,
2189 GetProgressValue(aFrame), GetProgressMaxValue(aFrame), aFrame);
2192 case NS_THEME_PROGRESSBAR_VERTICAL:
2193 DrawProgress(cgContext, macRect, IsIndeterminateProgress(aFrame, eventState),
2194 false, GetProgressValue(aFrame),
2195 GetProgressMaxValue(aFrame), aFrame);
2198 case NS_THEME_METERBAR:
2199 DrawMeter(cgContext, macRect, aFrame);
2202 case NS_THEME_PROGRESSBAR_CHUNK:
2203 case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2204 case NS_THEME_METERBAR_CHUNK:
2205 // Do nothing: progress and meter bars cases will draw chunks.
2208 case NS_THEME_TREEVIEW_TWISTY:
2209 DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
2210 kThemeDisclosureRight, kThemeAdornmentNone, eventState, aFrame);
2213 case NS_THEME_TREEVIEW_TWISTY_OPEN:
2214 DrawButton(cgContext, kThemeDisclosureButton, macRect, false,
2215 kThemeDisclosureDown, kThemeAdornmentNone, eventState, aFrame);
2218 case NS_THEME_TREEVIEW_HEADER_CELL: {
2219 TreeSortDirection sortDirection = GetTreeSortDirection(aFrame);
2220 DrawButton(cgContext, kThemeListHeaderButton, macRect, false,
2221 sortDirection == eTreeSortDirection_Natural ? kThemeButtonOff : kThemeButtonOn,
2222 sortDirection == eTreeSortDirection_Ascending ?
2223 kThemeAdornmentHeaderButtonSortUp : kThemeAdornmentNone, eventState, aFrame);
2227 case NS_THEME_TREEVIEW_TREEITEM:
2228 case NS_THEME_TREEVIEW:
2229 // HIThemeSetFill is not available on 10.3
2230 // HIThemeSetFill(kThemeBrushWhite, NULL, cgContext, HITHEME_ORIENTATION);
2231 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2232 CGContextFillRect(cgContext, macRect);
2235 case NS_THEME_TREEVIEW_HEADER:
2236 // do nothing, taken care of by individual header cells
2237 case NS_THEME_TREEVIEW_HEADER_SORTARROW:
2238 // do nothing, taken care of by treeview header
2239 case NS_THEME_TREEVIEW_LINE:
2240 // do nothing, these lines don't exist on macos
2243 case NS_THEME_SCALE_HORIZONTAL:
2244 case NS_THEME_SCALE_VERTICAL: {
2245 int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
2246 int32_t minpos = CheckIntAttr(aFrame, nsGkAtoms::minpos, 0);
2247 int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
2251 bool reverse = aFrame->GetContent()->
2252 AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
2253 NS_LITERAL_STRING("reverse"), eCaseMatters);
2254 DrawScale(cgContext, macRect, eventState,
2255 (aWidgetType == NS_THEME_SCALE_VERTICAL), reverse,
2256 curpos, minpos, maxpos, aFrame);
2260 case NS_THEME_SCALE_THUMB_HORIZONTAL:
2261 case NS_THEME_SCALE_THUMB_VERTICAL:
2262 // do nothing, drawn by scale
2265 case NS_THEME_SCROLLBAR_SMALL:
2266 case NS_THEME_SCROLLBAR: {
2267 DrawScrollbar(cgContext, macRect, aFrame);
2270 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2271 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2272 #if SCROLLBARS_VISUAL_DEBUG
2273 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 0, 0.6);
2274 CGContextFillRect(cgContext, macRect);
2277 case NS_THEME_SCROLLBAR_BUTTON_UP:
2278 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2279 #if SCROLLBARS_VISUAL_DEBUG
2280 CGContextSetRGBFillColor(cgContext, 1.0, 0, 0, 0.6);
2281 CGContextFillRect(cgContext, macRect);
2284 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2285 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2286 #if SCROLLBARS_VISUAL_DEBUG
2287 CGContextSetRGBFillColor(cgContext, 0, 1.0, 0, 0.6);
2288 CGContextFillRect(cgContext, macRect);
2291 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2292 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2293 // do nothing, drawn by scrollbar
2296 case NS_THEME_TEXTFIELD_MULTILINE: {
2297 // we have to draw this by hand because there is no HITheme value for it
2298 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2300 CGContextFillRect(cgContext, macRect);
2302 CGContextSetLineWidth(cgContext, 1.0);
2303 CGContextSetShouldAntialias(cgContext, false);
2305 // stroke everything but the top line of the text area
2306 CGContextSetRGBStrokeColor(cgContext, 0.6, 0.6, 0.6, 1.0);
2307 CGContextBeginPath(cgContext);
2308 CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
2309 CGContextAddLineToPoint(cgContext, macRect.origin.x, macRect.origin.y + macRect.size.height);
2310 CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + macRect.size.height);
2311 CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
2312 CGContextStrokePath(cgContext);
2314 // stroke the line across the top of the text area
2315 CGContextSetRGBStrokeColor(cgContext, 0.4510, 0.4510, 0.4510, 1.0);
2316 CGContextBeginPath(cgContext);
2317 CGContextMoveToPoint(cgContext, macRect.origin.x, macRect.origin.y + 1);
2318 CGContextAddLineToPoint(cgContext, macRect.origin.x + macRect.size.width - 1, macRect.origin.y + 1);
2319 CGContextStrokePath(cgContext);
2321 // draw a focus ring
2322 if (eventState.HasState(NS_EVENT_STATE_FOCUS)) {
2323 // We need to bring the rectangle in by 1 pixel on each side.
2324 CGRect cgr = CGRectMake(macRect.origin.x + 1,
2325 macRect.origin.y + 1,
2326 macRect.size.width - 2,
2327 macRect.size.height - 2);
2328 HIThemeDrawFocusRect(&cgr, true, cgContext, kHIThemeOrientationNormal);
2333 case NS_THEME_LISTBOX: {
2334 // We have to draw this by hand because kHIThemeFrameListBox drawing
2335 // is buggy on 10.5, see bug 579259.
2336 CGContextSetRGBFillColor(cgContext, 1.0, 1.0, 1.0, 1.0);
2337 CGContextFillRect(cgContext, macRect);
2339 // #8E8E8E for the top border, #BEBEBE for the rest.
2340 float x = macRect.origin.x, y = macRect.origin.y;
2341 float w = macRect.size.width, h = macRect.size.height;
2342 CGContextSetRGBFillColor(cgContext, 0.557, 0.557, 0.557, 1.0);
2343 CGContextFillRect(cgContext, CGRectMake(x, y, w, 1));
2344 CGContextSetRGBFillColor(cgContext, 0.745, 0.745, 0.745, 1.0);
2345 CGContextFillRect(cgContext, CGRectMake(x, y + 1, 1, h - 1));
2346 CGContextFillRect(cgContext, CGRectMake(x + w - 1, y + 1, 1, h - 1));
2347 CGContextFillRect(cgContext, CGRectMake(x + 1, y + h - 1, w - 2, 1));
2352 DrawSegment(cgContext, macRect, eventState, aFrame, tabRenderSettings);
2355 case NS_THEME_TAB_PANELS:
2356 DrawTabPanel(cgContext, macRect, aFrame);
2359 case NS_THEME_RESIZER:
2360 DrawResizer(cgContext, macRect, aFrame);
2364 nativeDrawing.EndNativeDrawing();
2368 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2372 nsNativeThemeCocoa::RTLAwareMargin(const nsIntMargin& aMargin, nsIFrame* aFrame)
2374 if (IsFrameRTL(aFrame))
2375 return nsIntMargin(aMargin.right, aMargin.top, aMargin.left, aMargin.bottom);
2380 static const nsIntMargin kAquaDropdownBorder(5, 1, 22, 2);
2381 static const nsIntMargin kAquaComboboxBorder(4, 3, 20, 3);
2382 static const nsIntMargin kAquaSearchfieldBorder(19, 3, 5, 2);
2385 nsNativeThemeCocoa::GetWidgetBorder(nsDeviceContext* aContext,
2387 uint8_t aWidgetType,
2388 nsIntMargin* aResult)
2390 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2392 aResult->SizeTo(0, 0, 0, 0);
2394 switch (aWidgetType) {
2395 case NS_THEME_BUTTON:
2397 if (IsButtonTypeMenu(aFrame)) {
2398 *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2400 aResult->SizeTo(7, 1, 7, 3);
2405 case NS_THEME_TOOLBAR_BUTTON:
2407 aResult->SizeTo(4, 1, 4, 1);
2411 case NS_THEME_CHECKBOX:
2412 case NS_THEME_RADIO:
2414 // nsFormControlFrame::GetIntrinsicWidth and nsFormControlFrame::GetIntrinsicHeight
2415 // assume a border width of 2px.
2416 aResult->SizeTo(2, 2, 2, 2);
2420 case NS_THEME_DROPDOWN:
2421 case NS_THEME_DROPDOWN_BUTTON:
2422 *aResult = RTLAwareMargin(kAquaDropdownBorder, aFrame);
2425 case NS_THEME_DROPDOWN_TEXTFIELD:
2426 *aResult = RTLAwareMargin(kAquaComboboxBorder, aFrame);
2429 case NS_THEME_TEXTFIELD:
2431 SInt32 frameOutset = 0;
2432 ::GetThemeMetric(kThemeMetricEditTextFrameOutset, &frameOutset);
2434 SInt32 textPadding = 0;
2435 ::GetThemeMetric(kThemeMetricEditTextWhitespace, &textPadding);
2437 frameOutset += textPadding;
2439 aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2443 case NS_THEME_TEXTFIELD_MULTILINE:
2444 aResult->SizeTo(1, 1, 1, 1);
2447 case NS_THEME_SEARCHFIELD:
2448 *aResult = RTLAwareMargin(kAquaSearchfieldBorder, aFrame);
2451 case NS_THEME_LISTBOX:
2453 SInt32 frameOutset = 0;
2454 ::GetThemeMetric(kThemeMetricListBoxFrameOutset, &frameOutset);
2455 aResult->SizeTo(frameOutset, frameOutset, frameOutset, frameOutset);
2459 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2460 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2462 // On Lion and later, scrollbars have no arrows.
2463 if (!nsCocoaFeatures::OnLionOrLater()) {
2464 // There's only an endcap to worry about when both arrows are on the bottom
2465 NSString *buttonPlacement = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleScrollBarVariant"];
2466 if (!buttonPlacement || [buttonPlacement isEqualToString:@"DoubleMax"]) {
2467 bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_TRACK_HORIZONTAL);
2469 nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2470 if (!scrollbarFrame) return NS_ERROR_FAILURE;
2471 bool isSmall = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2473 // There isn't a metric for this, so just hardcode a best guess at the value.
2474 // This value is even less exact due to the fact that the endcap is partially concave.
2475 int32_t endcapSize = isSmall ? 5 : 6;
2478 aResult->SizeTo(endcapSize, 0, 0, 0);
2480 aResult->SizeTo(0, endcapSize, 0, 0);
2486 case NS_THEME_STATUSBAR:
2487 aResult->SizeTo(0, 1, 0, 0);
2491 if (IsHiDPIContext(aContext)) {
2492 *aResult = *aResult + *aResult; // doubled
2497 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2500 // Return false here to indicate that CSS padding values should be used. There is
2501 // no reason to make a distinction between padding and border values, just specify
2502 // whatever values you want in GetWidgetBorder and only use this to return true
2503 // if you want to override CSS padding values.
2505 nsNativeThemeCocoa::GetWidgetPadding(nsDeviceContext* aContext,
2507 uint8_t aWidgetType,
2508 nsIntMargin* aResult)
2510 // We don't want CSS padding being used for certain widgets.
2511 // See bug 381639 for an example of why.
2512 switch (aWidgetType) {
2513 case NS_THEME_BUTTON:
2514 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
2515 // and have a meaningful baseline, so they can't have
2516 // author-specified padding.
2517 case NS_THEME_CHECKBOX:
2518 case NS_THEME_RADIO:
2519 aResult->SizeTo(0, 0, 0, 0);
2526 nsNativeThemeCocoa::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
2527 uint8_t aWidgetType, nsRect* aOverflowRect)
2529 int32_t p2a = aContext->AppUnitsPerDevPixel();
2530 switch (aWidgetType) {
2531 case NS_THEME_BUTTON:
2532 case NS_THEME_TOOLBAR_BUTTON:
2533 case NS_THEME_TEXTFIELD:
2534 case NS_THEME_TEXTFIELD_MULTILINE:
2535 case NS_THEME_SEARCHFIELD:
2536 case NS_THEME_LISTBOX:
2537 case NS_THEME_DROPDOWN:
2538 case NS_THEME_DROPDOWN_BUTTON:
2539 case NS_THEME_DROPDOWN_TEXTFIELD:
2540 case NS_THEME_CHECKBOX:
2541 case NS_THEME_RADIO:
2544 // We assume that the above widgets can draw a focus ring that will be less than
2545 // or equal to 4 pixels thick.
2546 nsIntMargin extraSize = nsIntMargin(MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH, MAX_FOCUS_RING_WIDTH);
2547 nsMargin m(NSIntPixelsToAppUnits(extraSize.left, p2a),
2548 NSIntPixelsToAppUnits(extraSize.top, p2a),
2549 NSIntPixelsToAppUnits(extraSize.right, p2a),
2550 NSIntPixelsToAppUnits(extraSize.bottom, p2a));
2551 aOverflowRect->Inflate(m);
2554 case NS_THEME_PROGRESSBAR:
2556 // Progress bars draw a 2 pixel white shadow under their progress indicators
2557 nsMargin m(0, 0, 0, NSIntPixelsToAppUnits(2, p2a));
2558 aOverflowRect->Inflate(m);
2566 static const int32_t kRegularScrollbarThumbMinSize = 22;
2567 static const int32_t kSmallScrollbarThumbMinSize = 19;
2570 nsNativeThemeCocoa::GetMinimumWidgetSize(nsRenderingContext* aContext,
2572 uint8_t aWidgetType,
2574 bool* aIsOverridable)
2576 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
2578 aResult->SizeTo(0,0);
2579 *aIsOverridable = true;
2581 switch (aWidgetType) {
2582 case NS_THEME_BUTTON:
2584 aResult->SizeTo(pushButtonSettings.minimumSizes[miniControlSize].width,
2585 pushButtonSettings.naturalSizes[miniControlSize].height);
2589 case NS_THEME_TOOLBAR_BUTTON:
2591 aResult->SizeTo(0, toolbarButtonHeights[miniControlSize]);
2595 case NS_THEME_SPINNER:
2597 SInt32 buttonHeight = 0, buttonWidth = 0;
2598 ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth);
2599 ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight);
2600 aResult->SizeTo(buttonWidth, buttonHeight);
2601 *aIsOverridable = false;
2605 case NS_THEME_DROPDOWN:
2606 case NS_THEME_DROPDOWN_BUTTON:
2608 SInt32 popupHeight = 0;
2609 ::GetThemeMetric(kThemeMetricPopupButtonHeight, &popupHeight);
2610 aResult->SizeTo(0, popupHeight);
2614 case NS_THEME_TEXTFIELD:
2615 case NS_THEME_TEXTFIELD_MULTILINE:
2616 case NS_THEME_SEARCHFIELD:
2618 // at minimum, we should be tall enough for 9pt text.
2619 // I'm using hardcoded values here because the appearance manager
2620 // values for the frame size are incorrect.
2621 aResult->SizeTo(0, (2 + 2) /* top */ + 9 + (1 + 1) /* bottom */);
2625 case NS_THEME_PROGRESSBAR:
2627 SInt32 barHeight = 0;
2628 ::GetThemeMetric(kThemeMetricNormalProgressBarThickness, &barHeight);
2629 aResult->SizeTo(0, barHeight);
2633 case NS_THEME_TREEVIEW_TWISTY:
2634 case NS_THEME_TREEVIEW_TWISTY_OPEN:
2636 SInt32 twistyHeight = 0, twistyWidth = 0;
2637 ::GetThemeMetric(kThemeMetricDisclosureButtonWidth, &twistyWidth);
2638 ::GetThemeMetric(kThemeMetricDisclosureButtonHeight, &twistyHeight);
2639 aResult->SizeTo(twistyWidth, twistyHeight);
2640 *aIsOverridable = false;
2644 case NS_THEME_TREEVIEW_HEADER:
2645 case NS_THEME_TREEVIEW_HEADER_CELL:
2647 SInt32 headerHeight = 0;
2648 ::GetThemeMetric(kThemeMetricListHeaderHeight, &headerHeight);
2649 aResult->SizeTo(0, headerHeight - 1); // We don't need the top border.
2655 aResult->SizeTo(0, tabHeights[miniControlSize]);
2659 case NS_THEME_SCALE_HORIZONTAL:
2661 SInt32 scaleHeight = 0;
2662 ::GetThemeMetric(kThemeMetricHSliderHeight, &scaleHeight);
2663 aResult->SizeTo(scaleHeight, scaleHeight);
2664 *aIsOverridable = false;
2668 case NS_THEME_SCALE_VERTICAL:
2670 SInt32 scaleWidth = 0;
2671 ::GetThemeMetric(kThemeMetricVSliderWidth, &scaleWidth);
2672 aResult->SizeTo(scaleWidth, scaleWidth);
2673 *aIsOverridable = false;
2677 case NS_THEME_SCROLLBAR_SMALL:
2679 SInt32 scrollbarWidth = 0;
2680 ::GetThemeMetric(kThemeMetricSmallScrollBarWidth, &scrollbarWidth);
2681 aResult->SizeTo(scrollbarWidth, scrollbarWidth);
2682 *aIsOverridable = false;
2686 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2687 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2689 // Find our parent scrollbar frame in order to find out whether we're in
2690 // a small or a large scrollbar.
2691 nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2692 if (!scrollbarFrame)
2693 return NS_ERROR_FAILURE;
2695 bool isSmall = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL);
2696 bool isHorizontal = (aWidgetType == NS_THEME_SCROLLBAR_THUMB_HORIZONTAL);
2697 int32_t& minSize = isHorizontal ? aResult->width : aResult->height;
2698 minSize = isSmall ? kSmallScrollbarThumbMinSize : kRegularScrollbarThumbMinSize;
2702 case NS_THEME_SCROLLBAR:
2703 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2704 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2706 // yeah, i know i'm cheating a little here, but i figure that it
2707 // really doesn't matter if the scrollbar is vertical or horizontal
2708 // and the width metric is a really good metric for every piece
2709 // of the scrollbar.
2711 nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2712 if (!scrollbarFrame) return NS_ERROR_FAILURE;
2714 int32_t themeMetric = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
2715 kThemeMetricSmallScrollBarWidth :
2716 kThemeMetricScrollBarWidth;
2717 SInt32 scrollbarWidth = 0;
2718 ::GetThemeMetric(themeMetric, &scrollbarWidth);
2719 aResult->SizeTo(scrollbarWidth, scrollbarWidth);
2720 *aIsOverridable = false;
2724 case NS_THEME_SCROLLBAR_BUTTON_UP:
2725 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2726 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2727 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2729 nsIFrame *scrollbarFrame = GetParentScrollbarFrame(aFrame);
2730 if (!scrollbarFrame) return NS_ERROR_FAILURE;
2732 // Since there is no NS_THEME_SCROLLBAR_BUTTON_UP_SMALL we need to ask the parent what appearance style it has.
2733 int32_t themeMetric = (scrollbarFrame->GetStyleDisplay()->mAppearance == NS_THEME_SCROLLBAR_SMALL) ?
2734 kThemeMetricSmallScrollBarWidth :
2735 kThemeMetricScrollBarWidth;
2736 SInt32 scrollbarWidth = 0;
2737 ::GetThemeMetric(themeMetric, &scrollbarWidth);
2739 // It seems that for both sizes of scrollbar, the buttons are one pixel "longer".
2740 if (aWidgetType == NS_THEME_SCROLLBAR_BUTTON_LEFT || aWidgetType == NS_THEME_SCROLLBAR_BUTTON_RIGHT)
2741 aResult->SizeTo(scrollbarWidth+1, scrollbarWidth);
2743 aResult->SizeTo(scrollbarWidth, scrollbarWidth+1);
2745 *aIsOverridable = false;
2748 case NS_THEME_RESIZER:
2750 HIThemeGrowBoxDrawInfo drawInfo;
2751 drawInfo.version = 0;
2752 drawInfo.state = kThemeStateActive;
2753 drawInfo.kind = kHIThemeGrowBoxKindNormal;
2754 drawInfo.direction = kThemeGrowRight | kThemeGrowDown;
2755 drawInfo.size = kHIThemeGrowBoxSizeNormal;
2756 HIPoint pnt = { 0, 0 };
2758 HIThemeGetGrowBoxBounds(&pnt, &drawInfo, &bounds);
2759 aResult->SizeTo(bounds.size.width, bounds.size.height);
2760 *aIsOverridable = false;
2764 if (IsHiDPIContext(aContext->DeviceContext())) {
2765 *aResult = *aResult * 2;
2770 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
2774 nsNativeThemeCocoa::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
2775 nsIAtom* aAttribute, bool* aShouldRepaint)
2777 // Some widget types just never change state.
2778 switch (aWidgetType) {
2779 case NS_THEME_TOOLBOX:
2780 case NS_THEME_TOOLBAR:
2781 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2782 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2783 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2784 case NS_THEME_STATUSBAR:
2785 case NS_THEME_STATUSBAR_PANEL:
2786 case NS_THEME_STATUSBAR_RESIZER_PANEL:
2787 case NS_THEME_TOOLTIP:
2788 case NS_THEME_TAB_PANELS:
2789 case NS_THEME_TAB_PANEL:
2790 case NS_THEME_DIALOG:
2791 case NS_THEME_MENUPOPUP:
2792 case NS_THEME_GROUPBOX:
2793 case NS_THEME_PROGRESSBAR_CHUNK:
2794 case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2795 case NS_THEME_PROGRESSBAR:
2796 case NS_THEME_PROGRESSBAR_VERTICAL:
2797 case NS_THEME_METERBAR:
2798 case NS_THEME_METERBAR_CHUNK:
2799 *aShouldRepaint = false;
2803 // XXXdwh Not sure what can really be done here. Can at least guess for
2804 // specific widgets that they're highly unlikely to have certain states.
2805 // For example, a toolbar doesn't care about any states.
2807 // Hover/focus/active changed. Always repaint.
2808 *aShouldRepaint = true;
2810 // Check the attribute to see if it's relevant.
2811 // disabled, checked, dlgtype, default, etc.
2812 *aShouldRepaint = false;
2813 if (aAttribute == nsGkAtoms::disabled ||
2814 aAttribute == nsGkAtoms::checked ||
2815 aAttribute == nsGkAtoms::selected ||
2816 aAttribute == nsGkAtoms::menuactive ||
2817 aAttribute == nsGkAtoms::sortDirection ||
2818 aAttribute == nsGkAtoms::focused ||
2819 aAttribute == nsGkAtoms::_default ||
2820 aAttribute == nsGkAtoms::open)
2821 *aShouldRepaint = true;
2828 nsNativeThemeCocoa::ThemeChanged()
2830 // This is unimplemented because we don't care if gecko changes its theme
2831 // and Mac OS X doesn't have themes.
2836 nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
2837 uint8_t aWidgetType)
2839 // We don't have CSS set up to render non-native scrollbars on Mac OS X so we
2840 // render natively even if native theme support is disabled.
2841 if (aWidgetType != NS_THEME_SCROLLBAR &&
2842 aPresContext && !aPresContext->PresShell()->IsThemeSupportEnabled())
2845 // if this is a dropdown button in a combobox the answer is always no
2846 if (aWidgetType == NS_THEME_DROPDOWN_BUTTON) {
2847 nsIFrame* parentFrame = aFrame->GetParent();
2848 if (parentFrame && (parentFrame->GetType() == nsGkAtoms::comboboxControlFrame))
2852 switch (aWidgetType) {
2853 case NS_THEME_LISTBOX:
2855 case NS_THEME_DIALOG:
2856 case NS_THEME_WINDOW:
2857 case NS_THEME_MENUPOPUP:
2858 case NS_THEME_MENUITEM:
2859 case NS_THEME_MENUSEPARATOR:
2860 case NS_THEME_TOOLTIP:
2862 case NS_THEME_CHECKBOX:
2863 case NS_THEME_CHECKBOX_CONTAINER:
2864 case NS_THEME_RADIO:
2865 case NS_THEME_RADIO_CONTAINER:
2866 case NS_THEME_GROUPBOX:
2867 case NS_THEME_BUTTON:
2868 case NS_THEME_BUTTON_BEVEL:
2869 case NS_THEME_TOOLBAR_BUTTON:
2870 case NS_THEME_SPINNER:
2871 case NS_THEME_TOOLBAR:
2872 case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR:
2873 case NS_THEME_STATUSBAR:
2874 case NS_THEME_TEXTFIELD:
2875 case NS_THEME_TEXTFIELD_MULTILINE:
2876 case NS_THEME_SEARCHFIELD:
2877 //case NS_THEME_TOOLBOX:
2878 //case NS_THEME_TOOLBAR_BUTTON:
2879 case NS_THEME_PROGRESSBAR:
2880 case NS_THEME_PROGRESSBAR_VERTICAL:
2881 case NS_THEME_PROGRESSBAR_CHUNK:
2882 case NS_THEME_PROGRESSBAR_CHUNK_VERTICAL:
2883 case NS_THEME_METERBAR:
2884 case NS_THEME_METERBAR_CHUNK:
2885 case NS_THEME_TOOLBAR_SEPARATOR:
2887 case NS_THEME_TAB_PANELS:
2890 case NS_THEME_TREEVIEW_TWISTY:
2891 case NS_THEME_TREEVIEW_TWISTY_OPEN:
2892 case NS_THEME_TREEVIEW:
2893 case NS_THEME_TREEVIEW_HEADER:
2894 case NS_THEME_TREEVIEW_HEADER_CELL:
2895 case NS_THEME_TREEVIEW_HEADER_SORTARROW:
2896 case NS_THEME_TREEVIEW_TREEITEM:
2897 case NS_THEME_TREEVIEW_LINE:
2899 case NS_THEME_SCALE_HORIZONTAL:
2900 case NS_THEME_SCALE_THUMB_HORIZONTAL:
2901 case NS_THEME_SCALE_VERTICAL:
2902 case NS_THEME_SCALE_THUMB_VERTICAL:
2904 case NS_THEME_SCROLLBAR:
2905 case NS_THEME_SCROLLBAR_SMALL:
2906 case NS_THEME_SCROLLBAR_BUTTON_UP:
2907 case NS_THEME_SCROLLBAR_BUTTON_DOWN:
2908 case NS_THEME_SCROLLBAR_BUTTON_LEFT:
2909 case NS_THEME_SCROLLBAR_BUTTON_RIGHT:
2910 case NS_THEME_SCROLLBAR_THUMB_HORIZONTAL:
2911 case NS_THEME_SCROLLBAR_THUMB_VERTICAL:
2912 case NS_THEME_SCROLLBAR_TRACK_VERTICAL:
2913 case NS_THEME_SCROLLBAR_TRACK_HORIZONTAL:
2915 case NS_THEME_DROPDOWN:
2916 case NS_THEME_DROPDOWN_BUTTON:
2917 case NS_THEME_DROPDOWN_TEXT:
2918 case NS_THEME_DROPDOWN_TEXTFIELD:
2919 return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
2922 case NS_THEME_RESIZER:
2924 nsIFrame* parentFrame = aFrame->GetParent();
2925 if (!parentFrame || parentFrame->GetType() != nsGkAtoms::scrollFrame)
2928 // Note that IsWidgetStyled is not called for resizers on Mac. This is
2929 // because for scrollable containers, the native resizer looks better
2930 // when scrollbars are present even when the style is overriden, and the
2931 // custom transparent resizer looks better when scrollbars are not
2933 nsIScrollableFrame* scrollFrame = do_QueryFrame(parentFrame);
2934 return (scrollFrame && scrollFrame->GetScrollbarVisibility());
2943 nsNativeThemeCocoa::WidgetIsContainer(uint8_t aWidgetType)
2945 // flesh this out at some point
2946 switch (aWidgetType) {
2947 case NS_THEME_DROPDOWN_BUTTON:
2948 case NS_THEME_RADIO:
2949 case NS_THEME_CHECKBOX:
2950 case NS_THEME_PROGRESSBAR:
2951 case NS_THEME_METERBAR:
2959 nsNativeThemeCocoa::ThemeDrawsFocusForWidget(nsPresContext* aPresContext, nsIFrame* aFrame, uint8_t aWidgetType)
2961 if (aWidgetType == NS_THEME_DROPDOWN ||
2962 aWidgetType == NS_THEME_DROPDOWN_TEXTFIELD ||
2963 aWidgetType == NS_THEME_BUTTON ||
2964 aWidgetType == NS_THEME_RADIO ||
2965 aWidgetType == NS_THEME_CHECKBOX)
2972 nsNativeThemeCocoa::ThemeNeedsComboboxDropmarker()
2977 nsITheme::Transparency
2978 nsNativeThemeCocoa::GetWidgetTransparency(nsIFrame* aFrame, uint8_t aWidgetType)
2980 switch (aWidgetType) {
2981 case NS_THEME_MENUPOPUP:
2982 case NS_THEME_TOOLTIP:
2983 return eTransparent;
2985 case NS_THEME_SCROLLBAR_SMALL:
2986 case NS_THEME_SCROLLBAR:
2987 case NS_THEME_STATUSBAR:
2988 // Knowing that scrollbars and statusbars are opaque improves
2989 // performance, because we create layers for them.
2993 return eUnknownTransparency;
2998 nsNativeThemeCocoa::GetProgressValue(nsIFrame* aFrame)
3000 // When we are using the HTML progress element,
3001 // we can get the value from the IDL property.
3003 nsCOMPtr<nsIDOMHTMLProgressElement> progress =
3004 do_QueryInterface(aFrame->GetContent());
3007 progress->GetValue(&value);
3012 return (double)CheckIntAttr(aFrame, nsGkAtoms::value, 0);
3016 nsNativeThemeCocoa::GetProgressMaxValue(nsIFrame* aFrame)
3018 // When we are using the HTML progress element,
3019 // we can get the max from the IDL property.
3021 nsCOMPtr<nsIDOMHTMLProgressElement> progress =
3022 do_QueryInterface(aFrame->GetContent());
3025 progress->GetMax(&max);
3030 return (double)NS_MAX(CheckIntAttr(aFrame, nsGkAtoms::max, 100), 1);